lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <aFpnU8EHGt14UMHC@kekkonen.localdomain>
Date: Tue, 24 Jun 2025 08:52:35 +0000
From: Sakari Ailus <sakari.ailus@...ux.intel.com>
To: Cosmin Tanislav <demonsingur@...il.com>
Cc: Tomi Valkeinen <tomi.valkeinen+renesas@...asonboard.com>,
	Mauro Carvalho Chehab <mchehab@...nel.org>,
	Rob Herring <robh@...nel.org>,
	Niklas Söderlund <niklas.soderlund@...natech.se>,
	Julien Massot <julien.massot@...labora.com>,
	Laurent Pinchart <laurent.pinchart@...asonboard.com>,
	Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
	Linus Walleij <linus.walleij@...aro.org>,
	"open list:MAXIM GMSL2 SERIALIZERS AND DESERIALIZERS" <linux-media@...r.kernel.org>,
	"open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS" <devicetree@...r.kernel.org>,
	open list <linux-kernel@...r.kernel.org>,
	"moderated list:ARM64 PORT (AARCH64 ARCHITECTURE)" <linux-arm-kernel@...ts.infradead.org>,
	"open list:STAGING SUBSYSTEM" <linux-staging@...ts.linux.dev>,
	"open list:GPIO SUBSYSTEM:Keyword:(devm_)?gpio_(request|free|direction|get|set)" <linux-gpio@...r.kernel.org>,
	Cosmin Tanislav <cosmin.tanislav@...log.com>
Subject: Re: [PATCH v4 15/19] media: i2c: add Maxim GMSL2/3 serializer and
 deserializer drivers

Hi Cosmin,

Thanks for the set.

This patch is pretty massive. Could you split it into the serialiser and
deserialiser frameworks and then invididual drivers using it (six-ish
patches in total)?

I have some commets below, too.

On Wed, Jun 18, 2025 at 12:58:51PM +0300, Cosmin Tanislav wrote:
> Add drivers for some of the broad-market Maxim GMSL2 / GMSL3 serializers
> and deserializers.
> 
> These drivers are meant to replace the existing MAX96712 / MAX96714 /
> MAX96717 drivers, while integrating them in a common framework, keeping
> compatibility with existing usecases, and avoiding code duplication,
> while also enabling more features across the whole range.
> 
> These drivers also add support for the following new chips:
>  * MAX96716 (GMSL2)
>  * MAX9296A (GMSL2)
>  * MAX96792A (GMSL3)
>  * MAX9295A (GMSL2)
>  * MAX96793 (GMSL3)
> 
> These drivers enable support for the following new features:
>  * Full Streams API support
>  * .get_frame_desc()
>  * .get_mbus_config()
>  * I2C ATR, I2C MUX
>  * automatic VC remapping
>  * automatic pixel mode / tunnel mode selection
>  * automatic double mode selection
>  * logging of internal state and chip status registers via .log_status()
>  * PHY modes
>  * serializer pinctrl
> 
> Signed-off-by: Cosmin Tanislav <demonsingur@...il.com>
> ---
>  MAINTAINERS                                 |    1 +
>  drivers/media/i2c/Kconfig                   |    2 +
>  drivers/media/i2c/Makefile                  |    1 +
>  drivers/media/i2c/maxim-serdes/Kconfig      |   53 +
>  drivers/media/i2c/maxim-serdes/Makefile     |    6 +
>  drivers/media/i2c/maxim-serdes/max9296a.c   | 1338 ++++++++
>  drivers/media/i2c/maxim-serdes/max96717.c   | 1647 ++++++++++
>  drivers/media/i2c/maxim-serdes/max96724.c   | 1155 +++++++
>  drivers/media/i2c/maxim-serdes/max_des.c    | 3108 +++++++++++++++++++
>  drivers/media/i2c/maxim-serdes/max_des.h    |  152 +
>  drivers/media/i2c/maxim-serdes/max_ser.c    | 2032 ++++++++++++
>  drivers/media/i2c/maxim-serdes/max_ser.h    |  144 +
>  drivers/media/i2c/maxim-serdes/max_serdes.c |  415 +++
>  drivers/media/i2c/maxim-serdes/max_serdes.h |  166 +
>  14 files changed, 10220 insertions(+)
>  create mode 100644 drivers/media/i2c/maxim-serdes/Kconfig
>  create mode 100644 drivers/media/i2c/maxim-serdes/Makefile
>  create mode 100644 drivers/media/i2c/maxim-serdes/max9296a.c
>  create mode 100644 drivers/media/i2c/maxim-serdes/max96717.c
>  create mode 100644 drivers/media/i2c/maxim-serdes/max96724.c
>  create mode 100644 drivers/media/i2c/maxim-serdes/max_des.c
>  create mode 100644 drivers/media/i2c/maxim-serdes/max_des.h
>  create mode 100644 drivers/media/i2c/maxim-serdes/max_ser.c
>  create mode 100644 drivers/media/i2c/maxim-serdes/max_ser.h
>  create mode 100644 drivers/media/i2c/maxim-serdes/max_serdes.c
>  create mode 100644 drivers/media/i2c/maxim-serdes/max_serdes.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 5c8fc3374179..fe5f239d7087 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14769,6 +14769,7 @@ M:	Cosmin Tanislav <cosmin.tanislav@...log.com>
>  L:	linux-media@...r.kernel.org
>  S:	Maintained
>  F:	Documentation/devicetree/bindings/media/i2c/maxim,max9296a.yaml
> +F:	drivers/media/i2c/maxim-serdes/
>  
>  MAXIM MAX11205 DRIVER
>  M:	Ramona Bolboaca <ramona.bolboaca@...log.com>
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index e68202954a8f..2147c429c9a7 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -1699,6 +1699,8 @@ config VIDEO_MAX96717
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called max96717.
>  
> +source "drivers/media/i2c/maxim-serdes/Kconfig"
> +
>  endmenu
>  
>  endif # VIDEO_DEV
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index 5873d29433ee..25a0093d40ec 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -70,6 +70,7 @@ obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o
>  obj-$(CONFIG_VIDEO_MAX9286) += max9286.o
>  obj-$(CONFIG_VIDEO_MAX96714) += max96714.o
>  obj-$(CONFIG_VIDEO_MAX96717) += max96717.o
> +obj-$(CONFIG_VIDEO_MAXIM_SERDES) += maxim-serdes/
>  obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o
>  obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o
>  obj-$(CONFIG_VIDEO_MT9M001) += mt9m001.o
> diff --git a/drivers/media/i2c/maxim-serdes/Kconfig b/drivers/media/i2c/maxim-serdes/Kconfig
> new file mode 100644
> index 000000000000..fafa6a47d5eb
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/Kconfig
> @@ -0,0 +1,53 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +config VIDEO_MAXIM_SERDES
> +	tristate "Maxim GMSL2 Serializer and Deserializer support"
> +	depends on VIDEO_DEV
> +	select I2C_ATR
> +	select I2C_MUX
> +	select MEDIA_CONTROLLER
> +	select V4L2_FWNODE
> +	select VIDEO_V4L2_SUBDEV_API
> +	help
> +	  This driver supports the Maxim GMSL2 common Serializer and Deserializer
> +	  framework.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called max_serdes.
> +
> +config VIDEO_MAX96717
> +	tristate "Maxim MAX96717 GMSL2 Serializer support"
> +	depends on OF && COMMON_CLK
> +	select VIDEO_MAXIM_SERDES
> +	select GENERIC_PINCONF
> +	select GENERIC_PINCTRL_GROUPS
> +	select GENERIC_PINMUX_FUNCTIONS
> +	select GPIOLIB
> +	help
> +	  This driver supports the Maxim MAX96717 GMSL2 Serializer,
> +	  which receives video on a MIPI CSI-2 interface and outputs it
> +	  on a GMSL2 link.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called max96717.
> +
> +config VIDEO_MAX96724
> +	tristate "Maxim MAX96724 Quad GMSL2 Deserializer support"
> +	select VIDEO_MAXIM_SERDES
> +	help
> +	  This driver supports the Maxim MAX96724 Quad GMSL2 Deserializer,
> +	  which converts from four GMSL2/1 to 1, 2 or 4 MIPI D-PHY or
> +	  C-PHY outputs.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called max96724.
> +
> +config VIDEO_MAX9296A
> +	tristate "Maxim MAX9296A Dual GMSL2 Deserializer support"
> +	select VIDEO_MAXIM_SERDES
> +	help
> +	  This driver supports the Maxim MAX9296A Dual GMSL2 Deserializer,
> +	  which converts from two GMSL2/1 to 1 or 2 MIPI D-PHY outputs.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called max9296a.
> diff --git a/drivers/media/i2c/maxim-serdes/Makefile b/drivers/media/i2c/maxim-serdes/Makefile
> new file mode 100644
> index 000000000000..ae306bc33bfb
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/Makefile
> @@ -0,0 +1,6 @@
> +# SPDX-License-Identifier: GPL-2.0
> +max-serdes-objs := max_serdes.o max_ser.o max_des.o
> +obj-$(CONFIG_VIDEO_MAXIM_SERDES) += max-serdes.o
> +obj-$(CONFIG_VIDEO_MAX96717) += max96717.o
> +obj-$(CONFIG_VIDEO_MAX96724) += max96724.o
> +obj-$(CONFIG_VIDEO_MAX9296A) += max9296a.o
> diff --git a/drivers/media/i2c/maxim-serdes/max9296a.c b/drivers/media/i2c/maxim-serdes/max9296a.c
> new file mode 100644
> index 000000000000..aa44e2ce4cf9
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max9296a.c
> @@ -0,0 +1,1338 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Maxim MAX9296A Quad GMSL2 Deserializer Driver
> + *
> + * Copyright (C) 2025 Analog Devices Inc.
> + */
> +
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/of_graph.h>
> +#include <linux/regmap.h>
> +
> +#include <media/mipi-csi2.h>
> +
> +#include "max_des.h"
> +
> +#define MAX9296A_REG0				0x0
> +
> +#define MAX9296A_REG1				0x1
> +#define MAX9296A_REG1_RX_RATE_A			GENMASK(1, 0)
> +#define MAX9296A_REG1_RX_RATE_3GBPS		0b01
> +#define MAX9296A_REG1_RX_RATE_6GBPS		0b10
> +#define MAX9296A_REG1_RX_RATE_12GBPS		0b11
> +
> +#define MAX9296A_REG2				0x2
> +#define MAX9296A_REG2_VID_EN(p)			BIT((p) + 4)
> +
> +#define MAX9296A_REG4				0x4
> +#define MAX9296A_REG4_GMSL3_X(x)		BIT((x) + 6)
> +#define MAX9296A_REG4_RX_RATE_B			GENMASK(1, 0)
> +
> +#define MAX9296A_REG6				0x6
> +#define MAX9296A_REG6_GMSL2_X(x)		BIT((x) + 6)
> +
> +#define MAX9296A_CTRL0				0x10
> +#define MAX9296A_CTRL0_LINK_CFG			GENMASK(1, 0)
> +#define MAX9296A_CTRL0_AUTO_LINK		BIT(4)
> +#define MAX9296A_CTRL0_RESET_ONESHOT		BIT(5)
> +#define MAX9296A_CTRL0_RESET_ALL		BIT(7)
> +
> +#define MAX9296A_CTRL2				0x12
> +#define MAX9296A_CTRL2_RESET_ONESHOT_B		BIT(5)
> +
> +#define MAX9296A_MIPI_TX0(x)			(0x28 + (x) * 0x5000)
> +#define MAX9296A_MIPI_TX0_RX_FEC_EN		BIT(1)
> +
> +#define MAX9296A_IO_CHK0			0x38
> +#define MAX9296A_IO_CHK0_PIN_DRV_EN_0		GENMASK(1, 0)
> +#define MAX9296A_IO_CHK0_PIN_DRV_EN_0_25MHZ	0b00
> +#define MAX9296A_IO_CHK0_PIN_DRV_EN_0_75MHZ	0b01
> +#define MAX9296A_IO_CHK0_PIN_DRV_EN_0_USE_PIPE	0b10
> +
> +#define MAX9296A_RX50(p)			(0x50 + (p))
> +#define MAX9296A_RX50_STR_SEL			GENMASK(1, 0)
> +
> +#define MAX9296A_VIDEO_PIPE_EN			0x160
> +#define MAX9296A_VIDEO_PIPE_EN_MASK(p)		BIT(p)
> +
> +#define MAX9296A_VIDEO_PIPE_SEL			0x161
> +#define MAX9296A_VIDEO_PIPE_SEL_STREAM(p)	(GENMASK(1, 0) << ((p) * 3))
> +#define MAX9296A_VIDEO_PIPE_SEL_LINK(p)		(BIT(2) << ((p) * 3))

BIT(2 + (p) * 3)

I think I might do that for the macro two lines above, too.

> +
> +#define MAX9296A_VPRBS(p)			(0x1fc + (p) * 0x20)
> +#define MAX9296A_VPRBS_VIDEO_LOCK		BIT(0)
> +#define MAX9296A_VPRBS_PATGEN_CLK_SRC		BIT(7)
> +#define MAX9296A_VPRBS_PATGEN_CLK_SRC_150MHZ	0b0
> +#define MAX9296A_VPRBS_PATGEN_CLK_SRC_600MHZ	0b1
> +
> +#define MAX9296A_PATGEN_0			0x240
> +#define MAX9296A_PATGEN_0_VTG_MODE		GENMASK(1, 0)
> +#define MAX9296A_PATGEN_0_VTG_MODE_FREE_RUNNING	0b11
> +#define MAX9296A_PATGEN_0_DE_INV		BIT(2)
> +#define MAX9296A_PATGEN_0_HS_INV		BIT(3)
> +#define MAX9296A_PATGEN_0_VS_INV		BIT(4)
> +#define MAX9296A_PATGEN_0_GEN_DE		BIT(5)
> +#define MAX9296A_PATGEN_0_GEN_HS		BIT(6)
> +#define MAX9296A_PATGEN_0_GEN_VS		BIT(7)
> +
> +#define MAX9296A_PATGEN_1			0x241
> +#define MAX9296A_PATGEN_1_PATGEN_MODE		GENMASK(5, 4)
> +#define MAX9296A_PATGEN_1_PATGEN_MODE_DISABLED	0b00
> +#define MAX9296A_PATGEN_1_PATGEN_MODE_GRADIENT	0b10
> +
> +#define MAX9296A_VS_DLY_2			0x242
> +#define MAX9296A_VS_HIGH_2			0x245
> +#define MAX9296A_VS_LOW_2			0x248
> +#define MAX9296A_V2H_2				0x24b
> +#define MAX9296A_HS_HIGH_1			0x24e
> +#define MAX9296A_HS_LOW_1			0x250
> +#define MAX9296A_HS_CNT_1			0x252
> +#define MAX9296A_V2D_2				0x254
> +#define MAX9296A_DE_HIGH_1			0x257
> +#define MAX9296A_DE_LOW_1			0x259
> +#define MAX9296A_DE_CNT_1			0x25b
> +#define MAX9296A_GRAD_INCR			0x25d
> +
> +#define MAX9296A_BACKTOP12			0x313
> +#define MAX9296A_BACKTOP12_CSI_OUT_EN		BIT(1)
> +
> +#define MAX9296A_BACKTOP21			0x31c
> +#define MAX9296A_BACKTOP21_BPP8DBL(p)		BIT(4 + (p))
> +
> +#define MAX9296A_BACKTOP22(x)			(0x31d + (x) * 0x3)
> +#define MAX9296A_BACKTOP22_PHY_CSI_TX_DPLL	GENMASK(4, 0)
> +#define MAX9296A_BACKTOP22_PHY_CSI_TX_DPLL_EN	BIT(5)
> +
> +#define MAX9296A_BACKTOP24			0x31f
> +#define MAX9296A_BACKTOP24_BPP8DBL_MODE(p)	BIT(4 + (p))
> +
> +#define MAX9296A_BACKTOP32			0x327
> +#define MAX9296A_BACKTOP32_BPP10DBL(p)		BIT(p)
> +#define MAX9296A_BACKTOP32_BPP10DBL_MODE(p)	BIT(4 + (p))
> +
> +#define MAX9296A_BACKTOP33			0x328
> +#define MAX9296A_BACKTOP32_BPP12DBL(p)		BIT(p)
> +
> +#define MAX9296A_MIPI_PHY0			0x330
> +#define MAX9296A_MIPI_PHY0_FORCE_CSI_OUT_EN	BIT(7)
> +
> +#define MAX9296A_MIPI_PHY2			0x332
> +#define MAX9296A_MIPI_PHY2_PHY_STDBY_N(x)	(GENMASK(5, 4) << ((x) * 2))
> +
> +#define MAX9296A_MIPI_PHY3(x)			(0x333 + (x))
> +#define MAX9296A_MIPI_PHY3_PHY_LANE_MAP_4	GENMASK(7, 0)
> +
> +#define MAX9296A_MIPI_PHY5(x)			(0x335 + (x))
> +#define MAX9296A_MIPI_PHY5_PHY_POL_MAP_0_1	GENMASK(1, 0)
> +#define MAX9296A_MIPI_PHY5_PHY_POL_MAP_2_3	GENMASK(4, 3)
> +#define MAX9296A_MIPI_PHY5_PHY_POL_MAP_CLK(x)	((x) == 0 ? BIT(5) : BIT(2))

BIT((x) == 0 ? 5 : 2)

> +
> +#define MAX9296A_MIPI_PHY18			0x342
> +#define MAX9296A_MIPI_PHY18_CSI2_TX_PKT_CNT(x)	(GENMASK(3, 0) << (4 * (x)))
> +
> +#define MAX9296A_MIPI_PHY20(x)			(0x344 + (x))
> +
> +#define MAX9296A_MIPI_TX3(x)			(0x403 + (x) * 0x40)
> +#define MAX9296A_MIPI_TX3_DESKEW_INIT_8X32K	FIELD_PREP(GENMASK(2, 0), 0b001)
> +#define MAX9296A_MIPI_TX3_DESKEW_INIT_AUTO	BIT(7)
> +
> +#define MAX9296A_MIPI_TX4(x)			(0x404 + (x) * 0x40)
> +#define MAX9296A_MIPI_TX4_DESKEW_PER_2K		FIELD_PREP(GENMASK(2, 0), 0b001)
> +#define MAX9296A_MIPI_TX4_DESKEW_PER_AUTO	BIT(7)
> +
> +#define MAX9296A_MIPI_TX10(x)			(0x40a + (x) * 0x40)
> +#define MAX9296A_MIPI_TX10_CSI2_LANE_CNT	GENMASK(7, 6)
> +#define MAX9296A_MIPI_TX10_CSI2_CPHY_EN		BIT(5)
> +
> +#define MAX9296A_MIPI_TX11(p)			(0x40b + (p) * 0x40)
> +#define MAX9296A_MIPI_TX12(p)			(0x40c + (p) * 0x40)
> +
> +#define MAX9296A_MIPI_TX13(p, x)		(0x40d + (p) * 0x40 + (x) * 0x2)
> +#define MAX9296A_MIPI_TX13_MAP_SRC_DT		GENMASK(5, 0)
> +#define MAX9296A_MIPI_TX13_MAP_SRC_VC		GENMASK(7, 6)
> +
> +#define MAX9296A_MIPI_TX14(p, x)		(0x40e + (p) * 0x40 + (x) * 0x2)
> +#define MAX9296A_MIPI_TX14_MAP_DST_DT		GENMASK(5, 0)
> +#define MAX9296A_MIPI_TX14_MAP_DST_VC		GENMASK(7, 6)
> +
> +#define MAX9296A_MIPI_TX45(p, x)		(0x42d + (p) * 0x40 + (x) / 4)
> +#define MAX9296A_MIPI_TX45_MAP_DPHY_DEST(x)	(GENMASK(1, 0) << (2 * ((x) % 4)))
> +
> +#define MAX9296A_MIPI_TX51(x)			(0x433 + (x) * 0x40)
> +#define MAX9296A_MIPI_TX51_ALT_MEM_MAP_12	BIT(0)
> +#define MAX9296A_MIPI_TX51_ALT_MEM_MAP_8	BIT(1)
> +#define MAX9296A_MIPI_TX51_ALT_MEM_MAP_10	BIT(2)
> +#define MAX9296A_MIPI_TX51_ALT2_MEM_MAP_8	BIT(4)
> +
> +#define MAX9296A_MIPI_TX52(x)			(0x434 +  (x) * 0x40)
> +#define MAX9296A_MIPI_TX52_TUN_DEST		BIT(1)
> +#define MAX9296A_MIPI_TX52_TUN_EN		BIT(0)
> +
> +#define MAX9296A_GMSL1_EN			0xf00
> +#define MAX9296A_GMSL1_EN_LINK_EN		GENMASK(1, 0)
> +
> +#define MAX9296A_RLMS3E(x)			(0x143e + (x) * 0x100)
> +#define MAX9296A_RLMS3F(x)			(0x143f + (x) * 0x100)
> +#define MAX9296A_RLMS49(x)			(0x1449 + (x) * 0x100)
> +#define MAX9296A_RLMS7E(x)			(0x147e + (x) * 0x100)
> +#define MAX9296A_RLMS7F(x)			(0x147f + (x) * 0x100)
> +#define MAX9296A_RLMSA3(x)			(0x14a3 + (x) * 0x100)
> +#define MAX9296A_RLMSA5(x)			(0x14a5 + (x) * 0x100)
> +#define MAX9296A_RLMSD8(x)			(0x14d8 + (x) * 0x100)
> +
> +#define MAX9296A_DPLL_0(x)			(0x1c00 + (x) * 0x100)
> +#define MAX9296A_DPLL_0_CONFIG_SOFT_RST_N	BIT(0)
> +
> +#define MAX9296A_PIPES_NUM		4
> +#define MAX9296A_PHYS_NUM		2
> +
> +static const struct regmap_config max9296a_i2c_regmap = {
> +	.reg_bits = 16,
> +	.val_bits = 8,
> +};
> +
> +struct max9296a_priv {
> +	struct max_des des;
> +	const struct max9296a_chip_info *info;
> +
> +	struct device *dev;
> +	struct i2c_client *client;
> +	struct regmap *regmap;
> +
> +	struct gpio_desc *gpiod_pwdn;
> +};
> +
> +struct max9296a_chip_info {
> +	unsigned int max_register;
> +	unsigned int versions;
> +	unsigned int modes;
> +	unsigned int num_pipes;
> +	unsigned int pipe_hw_ids[MAX9296A_PIPES_NUM];
> +	unsigned int phy_hw_ids[MAX9296A_PHYS_NUM];
> +	unsigned int num_phys;
> +	unsigned int num_links;
> +	struct max_phys_configs phys_configs;
> +	bool use_atr;
> +	bool has_per_link_reset;
> +	bool phy0_lanes_0_1_on_second_phy;
> +	bool polarity_on_physical_lanes;
> +	bool needs_single_link_version;
> +	bool needs_unique_stream_id;
> +	bool supports_cphy;
> +	bool supports_phy_log;
> +	bool adjust_rlms;
> +	bool fix_tx_ids;
> +
> +	enum max_gmsl_mode tpg_mode;
> +
> +	int (*set_pipe_stream_id)(struct max_des *des, struct max_des_pipe *pipe,
> +				  unsigned int stream_id);
> +	int (*set_pipe_enable)(struct max_des *des, struct max_des_pipe *pipe,
> +			       bool enable);
> +	int (*set_pipe_link)(struct max_des *des, struct max_des_pipe *pipe,
> +			     struct max_des_link *link);
> +	int (*set_pipe_tunnel_phy)(struct max_des *des, struct max_des_pipe *pipe,
> +				   struct max_des_phy *phy);
> +	int (*set_pipe_tunnel_enable)(struct max_des *des, struct max_des_pipe *pipe,
> +				      bool enable);

Given this many callbacks, having an operations struct for them seems
appropriate.

> +};
> +
> +#define des_to_priv(_des) \
> +	container_of(_des, struct max9296a_priv, des)
> +
> +static int max9296a_wait_for_device(struct max9296a_priv *priv)
> +{
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < 10; i++) {
> +		unsigned int val;
> +
> +		ret = regmap_read(priv->regmap, MAX9296A_REG0, &val);
> +		if (!ret && val)
> +			return 0;
> +
> +		msleep(100);
> +
> +		dev_err(priv->dev, "Retry %u waiting for deserializer: %d\n", i, ret);
> +	}
> +
> +	return ret;
> +}
> +
> +static int max9296a_reset(struct max9296a_priv *priv)
> +{
> +	int ret;
> +
> +	ret = max9296a_wait_for_device(priv);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_set_bits(priv->regmap, MAX9296A_CTRL0,
> +			      MAX9296A_CTRL0_RESET_ALL);
> +	if (ret)
> +		return ret;
> +
> +	msleep(100);
> +
> +	ret = max9296a_wait_for_device(priv);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static unsigned int max9296a_pipe_id(struct max9296a_priv *priv,
> +				     struct max_des_pipe *pipe)
> +{
> +	return priv->info->pipe_hw_ids[pipe->index];
> +}
> +
> +static unsigned int max9296a_phy_id(struct max9296a_priv *priv,
> +				    struct max_des_phy *phy)
> +{
> +	return priv->info->phy_hw_ids[phy->index];
> +}
> +
> +static int max9296a_reg_read(struct max_des *des, unsigned int reg,
> +			     unsigned int *val)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +
> +	return regmap_read(priv->regmap, reg, val);
> +}
> +
> +static int max9296a_reg_write(struct max_des *des, unsigned int reg,
> +			      unsigned int val)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +
> +	return regmap_write(priv->regmap, reg, val);
> +}
> +
> +static int max9626a_log_pipe_status(struct max_des *des,
> +				    struct max_des_pipe *pipe, const char *name)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +	unsigned int index = max9296a_pipe_id(priv, pipe);
> +	unsigned int val;
> +	int ret;
> +
> +	ret = regmap_read(priv->regmap, MAX9296A_VPRBS(index), &val);
> +	if (ret)
> +		return ret;
> +
> +	pr_info("%s: \tvideo_lock: %u\n", name,
> +		!!(val & MAX9296A_VPRBS_VIDEO_LOCK));
> +
> +	return 0;
> +}
> +
> +static int max9296a_log_phy_status(struct max_des *des,
> +				   struct max_des_phy *phy, const char *name)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +	unsigned int index = phy->index;
> +	unsigned int val;
> +	int ret;
> +
> +	if (!priv->info->supports_phy_log)
> +		return 0;
> +
> +	ret = regmap_read(priv->regmap, MAX9296A_MIPI_PHY18, &val);
> +	if (ret)
> +		return ret;
> +
> +	pr_info("%s: \tcsi2_pkt_cnt: %lu\n", name,
> +		field_get(MAX9296A_MIPI_PHY18_CSI2_TX_PKT_CNT(index), val));
> +
> +	ret = regmap_read(priv->regmap, MAX9296A_MIPI_PHY20(index), &val);
> +	if (ret)
> +		return ret;
> +
> +	pr_info("%s: \tphy_pkt_cnt: %u\n", name, val);

dev_info()?

> +
> +	return 0;
> +}
> +
> +static int max9296a_set_enable(struct max_des *des, bool enable)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +
> +	return regmap_assign_bits(priv->regmap, MAX9296A_BACKTOP12,
> +				  MAX9296A_BACKTOP12_CSI_OUT_EN, enable);
> +}
> +
> +static int max9296a_init_phy(struct max_des *des, struct max_des_phy *phy)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +	bool is_cphy = phy->bus_type == V4L2_MBUS_CSI2_CPHY;
> +	unsigned int num_data_lanes = phy->mipi.num_data_lanes;
> +	unsigned int dpll_freq = phy->link_frequency * 2;
> +	unsigned int num_hw_data_lanes;
> +	unsigned int hw_index = max9296a_phy_id(priv, phy);
> +	unsigned int index = phy->index;
> +	unsigned int used_data_lanes = 0;
> +	unsigned int val;

For register values, please use a type that explicitly specifies the number
of bits, e.g. u32.

> +	unsigned int i;
> +	int ret;
> +
> +	if (is_cphy && !priv->info->supports_cphy) {
> +		dev_err(priv->dev, "CPHY not supported\n");
> +		return -EINVAL;
> +	}
> +
> +	num_hw_data_lanes = max_des_phy_hw_data_lanes(des, phy);
> +
> +	/*
> +	 * MAX9296A has four PHYs, but does not support single-PHY configurations,
> +	 * only double-PHY configurations, even when only using two lanes.
> +	 * For PHY 0 + PHY 1, PHY 1 is the master PHY.
> +	 * For PHY 2 + PHY 3, PHY 2 is the master PHY.
> +	 * Clock is always on the master PHY.
> +	 * For first pair of PHYs, first lanes are on the master PHY.
> +	 * For second pair of PHYs, first lanes are on the master PHY too.
> +	 *
> +	 * PHY 0 + 1
> +	 * CLK = PHY 1
> +	 * PHY1 Lane 0 = D0
> +	 * PHY1 Lane 1 = D1
> +	 * PHY0 Lane 0 = D2
> +	 * PHY0 Lane 1 = D3
> +	 *
> +	 * PHY 2 + 3
> +	 * CLK = PHY 2
> +	 * PHY2 Lane 0 = D0
> +	 * PHY2 Lane 1 = D1
> +	 * PHY3 Lane 0 = D2
> +	 * PHY3 Lane 1 = D3
> +	 *
> +	 * MAX96714 only has two PHYs which cannot support single-PHY configurations.
> +	 * Clock is always on the master PHY, first lanes are on PHY 0, even if
> +	 * PHY 1 is the master PHY.
> +	 *
> +	 * PHY 0 + 1
> +	 * CLK = PHY 1
> +	 * PHY0 Lane 0 = D0
> +	 * PHY0 Lane 1 = D1
> +	 * PHY1 Lane 0 = D2
> +	 * PHY1 Lane 1 = D3
> +	 */
> +
> +	/* Configure a lane count. */
> +	ret = regmap_update_bits(priv->regmap, MAX9296A_MIPI_TX10(hw_index),
> +				 MAX9296A_MIPI_TX10_CSI2_LANE_CNT,
> +				 FIELD_PREP(MAX9296A_MIPI_TX10_CSI2_LANE_CNT,
> +					    num_data_lanes - 1));
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX10(hw_index),
> +				 MAX9296A_MIPI_TX10_CSI2_CPHY_EN, is_cphy);
> +	if (ret)
> +		return ret;
> +
> +	/* Configure lane mapping. */
> +	/*
> +	 * The lane of each PHY can be mapped to physical lanes 0, 1, 2, and 3.
> +	 * This mapping is exclusive, multiple lanes, even if unused cannot be
> +	 * mapped to the same physical lane.
> +	 * Each lane mapping is represented as two bits.
> +	 */
> +	val = 0;
> +	for (i = 0; i < num_hw_data_lanes ; i++) {
> +		unsigned int map;
> +
> +		if (i < num_data_lanes)
> +			map = phy->mipi.data_lanes[i] - 1;
> +		else
> +			map = ffz(used_data_lanes);
> +
> +		val |= map << (i * 2);
> +		used_data_lanes |= BIT(map);
> +	}
> +
> +	if (phy->index == 0 && priv->info->phy0_lanes_0_1_on_second_phy)
> +		val = ((val & 0xf) << 4) | ((val >> 4) & 0xf);
> +
> +	ret = regmap_update_bits(priv->regmap, MAX9296A_MIPI_PHY3(index),
> +				 MAX9296A_MIPI_PHY3_PHY_LANE_MAP_4,
> +				 FIELD_PREP(MAX9296A_MIPI_PHY3_PHY_LANE_MAP_4, val));
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * Configure lane polarity.
> +	 *
> +	 * PHY 0 and 1 are on register 0x335.
> +	 * PHY 2 and 3 are on register 0x336.
> +	 *
> +	 * Each PHY has 3 bits of polarity configuration.
> +	 *
> +	 * On MAX9296A, each bit represents the lane polarity of logical lanes.
> +	 * Each of these lanes can be mapped to any physical lane.
> +	 * 0th bit is for lane 0.
> +	 * 1st bit is for lane 1.
> +	 * 2nd bit is for clock lane.
> +	 *
> +	 * On MAX96714, each bit represents the lane polarity of physical lanes.
> +	 * 0th bit for physical lane 0.
> +	 * 1st bit for physical lane 1.
> +	 * 2nd bit for clock lane of PHY 0, the slave PHY, which is unused.
> +	 *
> +	 * 3rd bit for physical lane 2.
> +	 * 4th bit for physical lane 3.
> +	 * 5th bit for clock lane of PHY 1, the master PHY.
> +	 */
> +
> +	val = 0;
> +	for (i = 0; i < num_data_lanes; i++) {

	for (i = 0, val = 0; ...

> +		unsigned int map;
> +
> +		if (!phy->mipi.lane_polarities[i + 1])
> +			continue;
> +
> +		/*
> +		 * The numbers inside the data_lanes array specify the hardware
> +		 * lane each logical lane maps to.
> +		 * If polarity is set for the physical lanes, retrieve the
> +		 * physical lane matching the logical lane from data_lanes.
> +		 * Otherwise, when polarity is set for the logical lanes
> +		 * the index of the polarity can be used.
> +		 */
> +
> +		if (priv->info->polarity_on_physical_lanes)
> +			map = phy->mipi.data_lanes[i] - 1;
> +		else
> +			map = i;
> +
> +		val |= BIT(map);
> +	}
> +
> +	if (phy->index == 0 && priv->info->phy0_lanes_0_1_on_second_phy)
> +		val = ((val & 0x3) << 2) | ((val >> 2) & 0x3);
> +
> +	ret = regmap_update_bits(priv->regmap, MAX9296A_MIPI_PHY5(index),
> +				 MAX9296A_MIPI_PHY5_PHY_POL_MAP_0_1 |
> +				 MAX9296A_MIPI_PHY5_PHY_POL_MAP_2_3,
> +				 FIELD_PREP(MAX9296A_MIPI_PHY5_PHY_POL_MAP_0_1, val) |
> +				 FIELD_PREP(MAX9296A_MIPI_PHY5_PHY_POL_MAP_2_3, val >> 2));
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX9296A_MIPI_PHY5(index),
> +				 MAX9296A_MIPI_PHY5_PHY_POL_MAP_CLK(index),
> +				 phy->mipi.lane_polarities[0]);
> +	if (ret)
> +		return ret;
> +
> +	/* Put DPLL block into reset. */
> +	ret = regmap_clear_bits(priv->regmap, MAX9296A_DPLL_0(hw_index),
> +				MAX9296A_DPLL_0_CONFIG_SOFT_RST_N);
> +	if (ret)
> +		return ret;
> +
> +	/* Set DPLL frequency. */
> +	ret = regmap_update_bits(priv->regmap, MAX9296A_BACKTOP22(index),
> +				 MAX9296A_BACKTOP22_PHY_CSI_TX_DPLL,
> +				 FIELD_PREP(MAX9296A_BACKTOP22_PHY_CSI_TX_DPLL,
> +					    div_u64(dpll_freq, 100000000)));
> +	if (ret)
> +		return ret;
> +
> +	/* Enable DPLL frequency. */
> +	ret = regmap_set_bits(priv->regmap, MAX9296A_BACKTOP22(index),
> +			      MAX9296A_BACKTOP22_PHY_CSI_TX_DPLL_EN);
> +	if (ret)
> +		return ret;
> +
> +	/* Pull DPLL block out of reset. */
> +	ret = regmap_set_bits(priv->regmap, MAX9296A_DPLL_0(hw_index),
> +			      MAX9296A_DPLL_0_CONFIG_SOFT_RST_N);
> +	if (ret)
> +		return ret;
> +
> +	if (dpll_freq > 1500000000ull) {
> +		/* Enable initial deskew with 2 x 32k UI. */
> +		ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX3(hw_index),
> +				   MAX9296A_MIPI_TX3_DESKEW_INIT_AUTO |
> +				   MAX9296A_MIPI_TX3_DESKEW_INIT_8X32K);
> +		if (ret)
> +			return ret;
> +
> +		/* Enable periodic deskew with 2 x 1k UI.. */
> +		ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX4(hw_index),
> +				   MAX9296A_MIPI_TX4_DESKEW_PER_AUTO |
> +				   MAX9296A_MIPI_TX4_DESKEW_PER_2K);
> +		if (ret)
> +			return ret;
> +	} else {
> +		/* Disable initial deskew. */
> +		ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX3(hw_index), 0x0);
> +		if (ret)
> +			return ret;
> +
> +		/* Disable periodic deskew. */
> +		ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX4(hw_index), 0x0);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max9296a_set_phy_mode(struct max_des *des, struct max_des_phy *phy,
> +				 struct max_des_phy_mode *mode)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +	unsigned int phy_id = max9296a_phy_id(priv, phy);
> +	int ret;
> +
> +	/* Set alternate memory map modes. */
> +	ret = regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX51(phy_id),
> +				 MAX9296A_MIPI_TX51_ALT_MEM_MAP_12,
> +				 mode->alt_mem_map12);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX51(phy_id),
> +				 MAX9296A_MIPI_TX51_ALT_MEM_MAP_8,
> +				 mode->alt_mem_map8);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX51(phy_id),
> +				 MAX9296A_MIPI_TX51_ALT_MEM_MAP_10,
> +				 mode->alt_mem_map10);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX51(phy_id),
> +				 MAX9296A_MIPI_TX51_ALT2_MEM_MAP_8,
> +				 mode->alt2_mem_map8);

	return ...;

And drop the rest of the lines.

> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int max9296a_set_phy_active(struct max_des *des, struct max_des_phy *phy,
> +				   bool enable)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +
> +	return regmap_assign_bits(priv->regmap, MAX9296A_MIPI_PHY2,
> +				  MAX9296A_MIPI_PHY2_PHY_STDBY_N(phy->index), enable);
> +}
> +
> +static int max9296a_set_pipe_remap(struct max_des *des,
> +				   struct max_des_pipe *pipe,
> +				   unsigned int i,
> +				   struct max_des_remap *remap)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +	struct max_des_phy *phy = &des->phys[remap->phy];
> +	unsigned int phy_id = max9296a_phy_id(priv, phy);
> +	unsigned int index = max9296a_pipe_id(priv, pipe);
> +	int ret;
> +
> +	/* Set source Data Type and Virtual Channel. */
> +	/* TODO: implement extended Virtual Channel. */
> +	ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX13(index, i),
> +			   FIELD_PREP(MAX9296A_MIPI_TX13_MAP_SRC_DT,
> +				      remap->from_dt) |
> +			   FIELD_PREP(MAX9296A_MIPI_TX13_MAP_SRC_VC,
> +				      remap->from_vc));
> +	if (ret)
> +		return ret;
> +
> +	/* Set destination Data Type and Virtual Channel. */
> +	/* TODO: implement extended Virtual Channel. */
> +	ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX14(index, i),
> +			   FIELD_PREP(MAX9296A_MIPI_TX14_MAP_DST_DT,
> +				      remap->to_dt) |
> +			   FIELD_PREP(MAX9296A_MIPI_TX14_MAP_DST_VC,
> +				      remap->to_vc));
> +	if (ret)
> +		return ret;
> +
> +	/* Set destination PHY. */
> +	return regmap_update_bits(priv->regmap, MAX9296A_MIPI_TX45(index, i),
> +				  MAX9296A_MIPI_TX45_MAP_DPHY_DEST(i),
> +				  field_prep(MAX9296A_MIPI_TX45_MAP_DPHY_DEST(i),
> +					     phy_id));
> +}
> +
> +static int max9296a_set_pipe_remaps_enable(struct max_des *des,
> +					   struct max_des_pipe *pipe,
> +					   unsigned int mask)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +	unsigned int index = max9296a_pipe_id(priv, pipe);
> +	int ret;
> +
> +	ret = regmap_write(priv->regmap, MAX9296A_MIPI_TX11(index), mask);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_write(priv->regmap, MAX9296A_MIPI_TX12(index), mask >> 8);
> +}
> +
> +static int max9296a_set_pipe_enable(struct max_des *des, struct max_des_pipe *pipe,
> +				    bool enable)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +	unsigned int index = max9296a_pipe_id(priv, pipe);
> +
> +	return regmap_assign_bits(priv->regmap, MAX9296A_REG2,
> +				  MAX9296A_REG2_VID_EN(index), enable);
> +}
> +
> +static int max96714_set_pipe_enable(struct max_des *des, struct max_des_pipe *pipe,
> +				    bool enable)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +	unsigned int index = max9296a_pipe_id(priv, pipe);
> +
> +	return regmap_assign_bits(priv->regmap, MAX9296A_VIDEO_PIPE_EN,
> +				  MAX9296A_VIDEO_PIPE_EN_MASK(index - 1), enable);
> +}
> +
> +static int max96714_set_pipe_tunnel_enable(struct max_des *des,
> +					   struct max_des_pipe *pipe, bool enable)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +	unsigned int index = max9296a_pipe_id(priv, pipe);
> +
> +	return regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX52(index),
> +				  MAX9296A_MIPI_TX52_TUN_EN, enable);
> +}
> +
> +static int max9296a_set_pipe_stream_id(struct max_des *des, struct max_des_pipe *pipe,
> +				       unsigned int stream_id)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +	unsigned int index = max9296a_pipe_id(priv, pipe);
> +
> +	return regmap_update_bits(priv->regmap, MAX9296A_RX50(index), MAX9296A_RX50_STR_SEL,
> +				  FIELD_PREP(MAX9296A_RX50_STR_SEL, pipe->stream_id));
> +}
> +
> +static int max96714_set_pipe_stream_id(struct max_des *des, struct max_des_pipe *pipe,
> +				       unsigned int stream_id)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +	unsigned int index = pipe->index;
> +
> +	return regmap_update_bits(priv->regmap, MAX9296A_VIDEO_PIPE_SEL,
> +				  MAX9296A_VIDEO_PIPE_SEL_STREAM(index),
> +				  field_prep(MAX9296A_VIDEO_PIPE_SEL_STREAM(index),
> +					     stream_id));
> +}
> +
> +static int max96716a_set_pipe_link(struct max_des *des, struct max_des_pipe *pipe,
> +				   struct max_des_link *link)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +	unsigned int index = pipe->index;
> +
> +	return regmap_update_bits(priv->regmap, MAX9296A_VIDEO_PIPE_SEL,
> +				  MAX9296A_VIDEO_PIPE_SEL_LINK(index),
> +				  field_prep(MAX9296A_VIDEO_PIPE_SEL_LINK(index),
> +					     link->index));
> +}
> +
> +static int max96716a_set_pipe_tunnel_phy(struct max_des *des,
> +					 struct max_des_pipe *pipe,
> +					 struct max_des_phy *phy)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +	unsigned int index = max9296a_pipe_id(priv, pipe);
> +
> +	return regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX52(index),
> +				  MAX9296A_MIPI_TX52_TUN_DEST, phy->index);
> +}
> +
> +static int max9296a_set_pipe_mode(struct max_des *des,
> +				  struct max_des_pipe *pipe,
> +				  struct max_des_pipe_mode *mode)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +	unsigned int index = max9296a_pipe_id(priv, pipe);
> +	int ret;
> +
> +	/* Set 8bit double mode. */
> +	ret = regmap_assign_bits(priv->regmap, MAX9296A_BACKTOP21,
> +				 MAX9296A_BACKTOP21_BPP8DBL(index), mode->dbl8);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX9296A_BACKTOP24,
> +				 MAX9296A_BACKTOP24_BPP8DBL_MODE(index),
> +				 mode->dbl8mode);
> +	if (ret)
> +		return ret;
> +
> +	/* Set 10bit double mode. */
> +	ret = regmap_assign_bits(priv->regmap, MAX9296A_BACKTOP32,
> +				 MAX9296A_BACKTOP32_BPP10DBL(index), mode->dbl10);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX9296A_BACKTOP32,
> +				 MAX9296A_BACKTOP32_BPP10DBL_MODE(index),
> +				 mode->dbl10mode);
> +	if (ret)
> +		return ret;
> +
> +	/* Set 12bit double mode. */
> +	/* TODO: check support for double mode on MAX96714. */
> +	return regmap_assign_bits(priv->regmap, MAX9296A_BACKTOP33,
> +				  MAX9296A_BACKTOP32_BPP12DBL(index), mode->dbl12);
> +}
> +
> +static int max9296a_reset_link(struct max9296a_priv *priv, unsigned int index)
> +{
> +	unsigned int reg, mask;
> +
> +	if (index == 0) {
> +		reg = MAX9296A_CTRL0;
> +		mask = MAX9296A_CTRL0_RESET_ONESHOT;
> +	} else {
> +		reg = MAX9296A_CTRL2;
> +		mask = MAX9296A_CTRL2_RESET_ONESHOT_B;
> +	}

I might use an array for this.

Is index guaranteed to be 0 or 1?

> +
> +	return regmap_set_bits(priv->regmap, reg, mask);
> +}
> +
> +static int max9296a_init_link_rlms(struct max9296a_priv *priv,
> +				   struct max_des_link *link)
> +{
> +	unsigned int index = link->index;
> +	int ret;
> +
> +	/*
> +	 * These settings are described as required on datasheet page 53
> +	 * for MAX96714.
> +	 */
> +
> +	ret = regmap_write(priv->regmap, MAX9296A_RLMS3E(index), 0xfd);
> +	if (ret)
> +		return ret;

You could also do:

	if (!ret)
		ret = ...;

And return ret at the end. It's one line less per call. Up to you.

> +
> +	ret = regmap_write(priv->regmap, MAX9296A_RLMS3F(index), 0x3d);

What are these magic numbers? Could we have human-readable names for them?

The register names seem pretty opaque, too. Some explanation here would
seem reasonable.

> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(priv->regmap, MAX9296A_RLMS49(index), 0xf5);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(priv->regmap, MAX9296A_RLMS7E(index), 0xa8);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(priv->regmap, MAX9296A_RLMS7F(index), 0x68);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(priv->regmap, MAX9296A_RLMSA3(index), 0x30);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(priv->regmap, MAX9296A_RLMSA5(index), 0x70);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(priv->regmap, MAX9296A_RLMSD8(index), 0x07);
> +	if (ret)
> +		return ret;
> +
> +	return max9296a_reset_link(priv, link->index);
> +}
> +
> +static int max9296a_init_link(struct max_des *des, struct max_des_link *link)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +	int ret;
> +
> +	if (priv->info->adjust_rlms) {
> +		ret = max9296a_init_link_rlms(priv, link);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	/* Set TPG gradient increase. */
> +	ret = regmap_write(priv->regmap, MAX9296A_GRAD_INCR, 0x4);

	return ...;

> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int max9296a_select_links(struct max_des *des, unsigned int mask)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +	int ret;
> +
> +	if (priv->info->num_links == 1)
> +		return 0;
> +
> +	if (!mask) {
> +		dev_err(priv->dev, "Disable all links unsupported\n");
> +		return -EINVAL;
> +	}
> +
> +	ret = regmap_update_bits(priv->regmap, MAX9296A_GMSL1_EN,
> +				 MAX9296A_GMSL1_EN_LINK_EN,
> +				 FIELD_PREP(MAX9296A_GMSL1_EN_LINK_EN, mask));
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_update_bits(priv->regmap, MAX9296A_CTRL0,
> +				 MAX9296A_CTRL0_AUTO_LINK |
> +				 MAX9296A_CTRL0_LINK_CFG |
> +				 MAX9296A_CTRL0_RESET_ONESHOT,
> +				 FIELD_PREP(MAX9296A_CTRL0_LINK_CFG, mask) |
> +				 FIELD_PREP(MAX9296A_CTRL0_RESET_ONESHOT, 1));
> +	if (ret)
> +		return ret;
> +
> +	if (priv->info->has_per_link_reset) {
> +		ret = max9296a_reset_link(priv, 1);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	msleep(200);
> +
> +	return 0;
> +}
> +
> +static int max9296a_set_link_version(struct max_des *des,
> +				     struct max_des_link *link,
> +				     enum max_gmsl_version version)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +	unsigned int index = link->index;
> +	bool gmsl3_en = version == MAX_GMSL_3;
> +	unsigned int reg, mask, val;
> +	int ret;
> +
> +	if (priv->info->needs_single_link_version)
> +		index = 0;
> +
> +	if (index == 0) {
> +		reg = MAX9296A_REG1;
> +		mask = MAX9296A_REG1_RX_RATE_A;
> +	} else {
> +		reg = MAX9296A_REG4;
> +		mask = MAX9296A_REG4_RX_RATE_B;
> +	}
> +
> +	if (version == MAX_GMSL_3)
> +		val = MAX9296A_REG1_RX_RATE_12GBPS;
> +	else if (version == MAX_GMSL_2_6GBPS)
> +		val = MAX9296A_REG1_RX_RATE_6GBPS;
> +	else
> +		val = MAX9296A_REG1_RX_RATE_3GBPS;
> +
> +	ret = regmap_update_bits(priv->regmap, reg, mask, field_prep(mask, val));
> +	if (ret)
> +		return ret;
> +
> +	if (!(priv->info->versions & BIT(MAX_GMSL_3)))
> +		return 0;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX0(index),
> +				 MAX9296A_MIPI_TX0_RX_FEC_EN, gmsl3_en);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX9296A_REG6,
> +				 MAX9296A_REG6_GMSL2_X(index), !gmsl3_en);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_assign_bits(priv->regmap, MAX9296A_REG4,
> +				  MAX9296A_REG4_GMSL3_X(index), gmsl3_en);
> +}
> +
> +static int max9296a_set_tpg_timings(struct max9296a_priv *priv,
> +				    const struct max_tpg_timings *tm)
> +{
> +	const struct reg_sequence regs[] = {
> +		REG_SEQUENCE_3(MAX9296A_VS_DLY_2, tm->vs_dly),
> +		REG_SEQUENCE_3(MAX9296A_VS_HIGH_2, tm->vs_high),
> +		REG_SEQUENCE_3(MAX9296A_VS_LOW_2, tm->vs_low),
> +		REG_SEQUENCE_3(MAX9296A_V2H_2, tm->v2h),
> +		REG_SEQUENCE_2(MAX9296A_HS_HIGH_1, tm->hs_high),
> +		REG_SEQUENCE_2(MAX9296A_HS_LOW_1, tm->hs_low),
> +		REG_SEQUENCE_2(MAX9296A_HS_CNT_1, tm->hs_cnt),
> +		REG_SEQUENCE_3(MAX9296A_V2D_2, tm->v2d),
> +		REG_SEQUENCE_2(MAX9296A_DE_HIGH_1, tm->de_high),
> +		REG_SEQUENCE_2(MAX9296A_DE_LOW_1, tm->de_low),
> +		REG_SEQUENCE_2(MAX9296A_DE_CNT_1, tm->de_cnt),
> +	};
> +	int ret;
> +
> +	ret = regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs));
> +	if (ret)
> +		return ret;
> +
> +	return regmap_write(priv->regmap, MAX9296A_PATGEN_0,
> +			    FIELD_PREP(MAX9296A_PATGEN_0_VTG_MODE,
> +				       MAX9296A_PATGEN_0_VTG_MODE_FREE_RUNNING) |
> +			    FIELD_PREP(MAX9296A_PATGEN_0_DE_INV, tm->de_inv) |
> +			    FIELD_PREP(MAX9296A_PATGEN_0_HS_INV, tm->hs_inv) |
> +			    FIELD_PREP(MAX9296A_PATGEN_0_VS_INV, tm->vs_inv) |
> +			    FIELD_PREP(MAX9296A_PATGEN_0_GEN_DE, tm->gen_de) |
> +			    FIELD_PREP(MAX9296A_PATGEN_0_GEN_HS, tm->gen_hs) |
> +			    FIELD_PREP(MAX9296A_PATGEN_0_GEN_VS, tm->gen_vs));
> +}
> +
> +static int max9296a_set_tpg_clk(struct max9296a_priv *priv, const struct videomode *vm)
> +{
> +	bool patgen_clk_src = 0;
> +	u8 pin_drv_en;
> +	int ret;
> +
> +	if (!vm)
> +		return 0;
> +
> +	switch (vm->pixelclock) {
> +	case 25000000:
> +		pin_drv_en = MAX9296A_IO_CHK0_PIN_DRV_EN_0_25MHZ;
> +		break;
> +	case 75000000:
> +		pin_drv_en = MAX9296A_IO_CHK0_PIN_DRV_EN_0_75MHZ;
> +		break;
> +	case 150000000:
> +		pin_drv_en = MAX9296A_IO_CHK0_PIN_DRV_EN_0_USE_PIPE;
> +		patgen_clk_src = MAX9296A_VPRBS_PATGEN_CLK_SRC_150MHZ;
> +		break;
> +	case 600000000:
> +		pin_drv_en = MAX9296A_IO_CHK0_PIN_DRV_EN_0_USE_PIPE;
> +		patgen_clk_src = MAX9296A_VPRBS_PATGEN_CLK_SRC_600MHZ;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * TPG data is always injected on link 0, which is always routed to
> +	 * pipe 0.
> +	 */
> +	ret = regmap_update_bits(priv->regmap, MAX9296A_VPRBS(0),
> +				 MAX9296A_VPRBS_PATGEN_CLK_SRC,
> +				 FIELD_PREP(MAX9296A_VPRBS_PATGEN_CLK_SRC,
> +					    patgen_clk_src));
> +	if (ret)
> +		return ret;
> +
> +	return regmap_update_bits(priv->regmap, MAX9296A_IO_CHK0,
> +				  MAX9296A_IO_CHK0_PIN_DRV_EN_0,
> +				  FIELD_PREP(MAX9296A_IO_CHK0_PIN_DRV_EN_0,
> +					     pin_drv_en));
> +}
> +
> +static int max9296a_set_tpg(struct max_des *des, const struct max_tpg_entry *entry)
> +{
> +	struct max9296a_priv *priv = des_to_priv(des);
> +	struct max_tpg_timings timings = { 0 };
> +	const struct videomode *vm = NULL;
> +	int ret;
> +
> +	if (entry) {
> +		vm = max_find_tpg_videomode(entry);
> +		if (!vm)
> +			return -EINVAL;
> +
> +		max_get_tpg_timings(vm, &timings);
> +	}
> +
> +	ret = max9296a_set_tpg_timings(priv, &timings);
> +	if (ret)
> +		return ret;
> +
> +	ret = max9296a_set_tpg_clk(priv, vm);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_update_bits(priv->regmap, MAX9296A_PATGEN_1,
> +				 MAX9296A_PATGEN_1_PATGEN_MODE,
> +				 FIELD_PREP(MAX9296A_PATGEN_1_PATGEN_MODE,
> +					    entry ? MAX9296A_PATGEN_1_PATGEN_MODE_GRADIENT
> +						  : MAX9296A_PATGEN_1_PATGEN_MODE_DISABLED));
> +	if (ret)
> +		return ret;
> +
> +	return regmap_assign_bits(priv->regmap, MAX9296A_MIPI_PHY0,
> +				  MAX9296A_MIPI_PHY0_FORCE_CSI_OUT_EN, !!entry);
> +}
> +
> +static const struct max_tpg_entry max9296a_tpg_entries[] = {
> +	MAX_TPG_ENTRY_640X480P60_RGB888,
> +	MAX_TPG_ENTRY_1920X1080P60_RGB888,
> +};
> +
> +static const struct max_des_ops max9296a_ops = {
> +	.num_remaps_per_pipe = 16,
> +	.tpg_entries = {
> +		.num_entries = ARRAY_SIZE(max9296a_tpg_entries),
> +		.entries = max9296a_tpg_entries,
> +	},
> +	.reg_read = max9296a_reg_read,
> +	.reg_write = max9296a_reg_write,
> +	.log_pipe_status = max9626a_log_pipe_status,
> +	.log_phy_status = max9296a_log_phy_status,
> +	.set_enable = max9296a_set_enable,
> +	.init_phy = max9296a_init_phy,
> +	.set_phy_mode = max9296a_set_phy_mode,
> +	.set_phy_active = max9296a_set_phy_active,
> +	.set_pipe_remap = max9296a_set_pipe_remap,
> +	.set_pipe_remaps_enable = max9296a_set_pipe_remaps_enable,
> +	.set_pipe_mode = max9296a_set_pipe_mode,
> +	.set_tpg = max9296a_set_tpg,
> +	.init_link = max9296a_init_link,
> +	.select_links = max9296a_select_links,
> +	.set_link_version = max9296a_set_link_version,
> +};
> +
> +static int max9296a_probe(struct i2c_client *client)
> +{
> +	struct regmap_config i2c_regmap = max9296a_i2c_regmap;
> +	struct device *dev = &client->dev;
> +	struct max9296a_priv *priv;
> +	struct max_des_ops *ops;
> +	int ret;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	ops = devm_kzalloc(dev, sizeof(*ops), GFP_KERNEL);
> +	if (!ops)
> +		return -ENOMEM;
> +
> +	priv->info = device_get_match_data(dev);
> +	if (!priv->info) {
> +		dev_err(dev, "Failed to get match data\n");
> +		return -ENODEV;
> +	}
> +
> +	priv->dev = dev;
> +	priv->client = client;
> +	i2c_set_clientdata(client, priv);
> +
> +	i2c_regmap.max_register = priv->info->max_register;
> +	priv->regmap = devm_regmap_init_i2c(client, &i2c_regmap);
> +	if (IS_ERR(priv->regmap))
> +		return PTR_ERR(priv->regmap);
> +
> +	priv->gpiod_pwdn = devm_gpiod_get_optional(&client->dev, "powerdown",
> +						   GPIOD_OUT_HIGH);
> +	if (IS_ERR(priv->gpiod_pwdn))
> +		return PTR_ERR(priv->gpiod_pwdn);
> +
> +	if (priv->gpiod_pwdn) {
> +		/* PWDN must be held for 1us for reset */
> +		udelay(1);
> +
> +		gpiod_set_value_cansleep(priv->gpiod_pwdn, 0);
> +		/* Maximum power-up time (tLOCK) 4ms */
> +		usleep_range(4000, 5000);
> +	}
> +
> +	*ops = max9296a_ops;
> +
> +	ops->versions = priv->info->versions;
> +	ops->modes = priv->info->modes;
> +	ops->needs_single_link_version = priv->info->needs_single_link_version;
> +	ops->needs_unique_stream_id = priv->info->needs_unique_stream_id;
> +	ops->fix_tx_ids = priv->info->fix_tx_ids;
> +	ops->num_phys = priv->info->num_phys;
> +	ops->num_pipes = priv->info->num_pipes;
> +	ops->num_links = priv->info->num_links;
> +	ops->phys_configs = priv->info->phys_configs;
> +	ops->set_pipe_enable = priv->info->set_pipe_enable;
> +	ops->set_pipe_stream_id = priv->info->set_pipe_stream_id;
> +	ops->set_pipe_tunnel_phy = priv->info->set_pipe_tunnel_phy;
> +	ops->set_pipe_tunnel_enable = priv->info->set_pipe_tunnel_enable;
> +	ops->use_atr = priv->info->use_atr;
> +	ops->tpg_mode = priv->info->tpg_mode;
> +	priv->des.ops = ops;
> +
> +	ret = max9296a_reset(priv);
> +	if (ret)
> +		return ret;
> +
> +	return max_des_probe(client, &priv->des);
> +}
> +
> +static void max9296a_remove(struct i2c_client *client)
> +{
> +	struct max9296a_priv *priv = i2c_get_clientdata(client);
> +
> +	max_des_remove(&priv->des);
> +
> +	gpiod_set_value_cansleep(priv->gpiod_pwdn, 1);
> +}
> +
> +static const struct max_phys_config max9296a_phys_configs[] = {
> +	{ { 4, 4 } },
> +};
> +
> +static const struct max_phys_config max96714_phys_configs[] = {
> +	{ { 4 } },
> +};
> +
> +static const struct max9296a_chip_info max9296a_info = {
> +	.max_register = 0x1f00,
> +	.versions = BIT(MAX_GMSL_2_3GBPS) |
> +		    BIT(MAX_GMSL_2_6GBPS),
> +	.modes = BIT(MAX_GMSL_PIXEL_MODE),
> +	.set_pipe_stream_id = max9296a_set_pipe_stream_id,
> +	.set_pipe_enable = max9296a_set_pipe_enable,
> +	.needs_single_link_version = true,
> +	.needs_unique_stream_id = true,
> +	.use_atr = true,
> +	.phys_configs = {
> +		.num_configs = ARRAY_SIZE(max9296a_phys_configs),
> +		.configs = max9296a_phys_configs,
> +	},
> +	.phy0_lanes_0_1_on_second_phy = true,
> +	.fix_tx_ids = true,
> +	.num_pipes = 4,
> +	.pipe_hw_ids = { 0, 1, 2, 3 },
> +	.num_phys = 2,
> +	.phy_hw_ids = { 1, 2 },
> +	.num_links = 2,
> +};
> +
> +static const struct max9296a_chip_info max96714_info = {
> +	.max_register = 0x5011,
> +	.versions = BIT(MAX_GMSL_2_3GBPS) |
> +		    BIT(MAX_GMSL_2_6GBPS),
> +	.modes = BIT(MAX_GMSL_PIXEL_MODE) | BIT(MAX_GMSL_TUNNEL_MODE),
> +	.set_pipe_stream_id = max96714_set_pipe_stream_id,
> +	.set_pipe_enable = max96714_set_pipe_enable,
> +	.set_pipe_tunnel_enable = max96714_set_pipe_tunnel_enable,
> +	.phys_configs = {
> +		.num_configs = ARRAY_SIZE(max96714_phys_configs),
> +		.configs = max96714_phys_configs,
> +	},
> +	.tpg_mode = MAX_GMSL_PIXEL_MODE,
> +	.polarity_on_physical_lanes = true,
> +	.supports_phy_log = true,
> +	.adjust_rlms = true,
> +	.num_pipes = 1,
> +	.pipe_hw_ids = { 1 },
> +	.num_phys = 1,
> +	.phy_hw_ids = { 1 },
> +	.num_links = 1,
> +};
> +
> +static const struct max9296a_chip_info max96714f_info = {
> +	.max_register = 0x5011,
> +	.versions = BIT(MAX_GMSL_2_3GBPS),
> +	.modes = BIT(MAX_GMSL_PIXEL_MODE) | BIT(MAX_GMSL_TUNNEL_MODE),
> +	.set_pipe_stream_id = max96714_set_pipe_stream_id,
> +	.set_pipe_enable = max96714_set_pipe_enable,
> +	.set_pipe_tunnel_enable = max96714_set_pipe_tunnel_enable,
> +	.phys_configs = {
> +		.num_configs = ARRAY_SIZE(max96714_phys_configs),
> +		.configs = max96714_phys_configs,
> +	},
> +	.tpg_mode = MAX_GMSL_PIXEL_MODE,
> +	.polarity_on_physical_lanes = true,
> +	.supports_phy_log = true,
> +	.adjust_rlms = true,
> +	.num_pipes = 1,
> +	.pipe_hw_ids = { 1 },
> +	.num_phys = 1,
> +	.phy_hw_ids = { 1 },
> +	.num_links = 1,
> +};
> +
> +static const struct max9296a_chip_info max96716a_info = {
> +	.max_register = 0x52d6,
> +	.versions = BIT(MAX_GMSL_2_3GBPS) |
> +		    BIT(MAX_GMSL_2_6GBPS),
> +	.modes = BIT(MAX_GMSL_PIXEL_MODE) | BIT(MAX_GMSL_TUNNEL_MODE),
> +	.set_pipe_stream_id = max96714_set_pipe_stream_id,
> +	.set_pipe_link = max96716a_set_pipe_link,
> +	.set_pipe_enable = max96714_set_pipe_enable,
> +	.set_pipe_tunnel_phy = max96716a_set_pipe_tunnel_phy,
> +	.set_pipe_tunnel_enable = max96714_set_pipe_tunnel_enable,
> +	.use_atr = true,
> +	.phys_configs = {
> +		.num_configs = ARRAY_SIZE(max9296a_phys_configs),
> +		.configs = max9296a_phys_configs,
> +	},
> +	.tpg_mode = MAX_GMSL_PIXEL_MODE,
> +	.has_per_link_reset = true,
> +	.phy0_lanes_0_1_on_second_phy = true,
> +	.supports_cphy = true,
> +	.supports_phy_log = true,
> +	.num_pipes = 2,
> +	.pipe_hw_ids = { 1, 2 },
> +	.num_phys = 2,
> +	.phy_hw_ids = { 1, 2 },
> +	.num_links = 2,
> +};
> +
> +static const struct max9296a_chip_info max96792a_info = {
> +	.max_register = 0x52d6,
> +	.versions = BIT(MAX_GMSL_2_3GBPS) |
> +		    BIT(MAX_GMSL_2_6GBPS) |
> +		    BIT(MAX_GMSL_3),
> +	.modes = BIT(MAX_GMSL_PIXEL_MODE) | BIT(MAX_GMSL_TUNNEL_MODE),
> +	.set_pipe_stream_id = max96714_set_pipe_stream_id,
> +	.set_pipe_enable = max96714_set_pipe_enable,
> +	.set_pipe_tunnel_phy = max96716a_set_pipe_tunnel_phy,
> +	.set_pipe_tunnel_enable = max96714_set_pipe_tunnel_enable,
> +	.use_atr = true,
> +	.phys_configs = {
> +		.num_configs = ARRAY_SIZE(max9296a_phys_configs),
> +		.configs = max9296a_phys_configs,
> +	},
> +	.tpg_mode = MAX_GMSL_PIXEL_MODE,
> +	.has_per_link_reset = true,
> +	.phy0_lanes_0_1_on_second_phy = true,
> +	.supports_cphy = true,
> +	.supports_phy_log = true,
> +	.num_pipes = 2,
> +	.pipe_hw_ids = { 1, 2 },
> +	.num_phys = 2,
> +	.phy_hw_ids = { 1, 2 },
> +	.num_links = 2,
> +};
> +
> +static const struct of_device_id max9296a_of_table[] = {
> +	{ .compatible = "maxim,max9296a", .data = &max9296a_info },
> +	{ .compatible = "maxim,max96714", .data = &max96714_info },
> +	{ .compatible = "maxim,max96714f", .data = &max96714f_info },
> +	{ .compatible = "maxim,max96714r", .data = &max96714f_info },
> +	{ .compatible = "maxim,max96716a", .data = &max96716a_info },
> +	{ .compatible = "maxim,max96792a", .data = &max96792a_info },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, max9296a_of_table);
> +
> +static struct i2c_driver max9296a_i2c_driver = {
> +	.driver	= {
> +		.name = "max9296a",
> +		.of_match_table	= of_match_ptr(max9296a_of_table),
> +	},
> +	.probe = max9296a_probe,
> +	.remove = max9296a_remove,
> +};
> +
> +module_i2c_driver(max9296a_i2c_driver);
> +
> +MODULE_DESCRIPTION("Maxim MAX9296A Quad GMSL2 Deserializer Driver");
> +MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@...log.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/media/i2c/maxim-serdes/max96717.c b/drivers/media/i2c/maxim-serdes/max96717.c
> new file mode 100644
> index 000000000000..40c026fa2f05
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max96717.c
> @@ -0,0 +1,1647 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Maxim MAX96717 GMSL2 Serializer Driver
> + *
> + * Copyright (C) 2025 Analog Devices Inc.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/gpio/driver.h>
> +#include <linux/pinctrl/pinctrl.h>
> +#include <linux/pinctrl/pinmux.h>
> +#include <linux/pinctrl/pinconf.h>
> +#include <linux/pinctrl/pinconf-generic.h>
> +#include <linux/regmap.h>
> +
> +#include "max_ser.h"
> +
> +#define MAX96717_REG0				0x0
> +
> +#define MAX96717_REG2				0x2
> +#define MAX96717_REG2_VID_TX_EN_P(p)		BIT(4 + (p))
> +
> +#define MAX96717_REG3				0x3
> +#define MAX96717_REG3_RCLKSEL			GENMASK(1, 0)
> +#define MAX96717_REG3_RCLKSEL_REFERENCE_PLL	0b11
> +
> +#define MAX96717_REG6				0x6
> +#define MAX96717_REG6_RCLKEN			BIT(5)
> +
> +#define MAX96717_I2C_2(x)			(0x42 + (x) * 0x2)
> +#define MAX96717_I2C_2_SRC			GENMASK(7, 1)
> +
> +#define MAX96717_I2C_3(x)			(0x43 + (x) * 0x2)
> +#define MAX96717_I2C_3_DST			GENMASK(7, 1)
> +
> +#define MAX96717_TX3(p)				(0x53 + (p) * 0x4)
> +#define MAX96717_TX3_TX_STR_SEL			GENMASK(1, 0)
> +
> +#define MAX96717_VIDEO_TX0(p)			(0x100 + (p) * 0x8)
> +#define MAX96717_VIDEO_TX0_AUTO_BPP		BIT(3)
> +
> +#define MAX96717_VIDEO_TX1(p)			(0x101 + (p) * 0x8)
> +#define MAX96717_VIDEO_TX1_BPP			GENMASK(5, 0)
> +
> +#define MAX96717_VIDEO_TX2(p)			(0x102 + (p) * 0x8)
> +#define MAX96717_VIDEO_TX2_PCLKDET		BIT(7)
> +#define MAX96717_VIDEO_TX2_DRIFT_DET_EN		BIT(1)
> +
> +#define MAX96717_VTX0(p)			(0x1c8 + (p) * 0x43)
> +#define MAX96717_VTX0_VTG_MODE			GENMASK(1, 0)
> +#define MAX96717_VTX0_VTG_MODE_FREE_RUNNING	0b11
> +#define MAX96717_VTX0_DE_INV			BIT(2)
> +#define MAX96717_VTX0_HS_INV			BIT(3)
> +#define MAX96717_VTX0_VS_INV			BIT(4)
> +#define MAX96717_VTX0_GEN_DE			BIT(5)
> +#define MAX96717_VTX0_GEN_HS			BIT(6)
> +#define MAX96717_VTX0_GEN_VS			BIT(7)
> +
> +#define MAX96717_VTX1(p)			(0x1c9 + (p) * 0x43)
> +#define MAX96717_VTX1_PATGEN_CLK_SRC		GENMASK(3, 1)
> +#define MAX96717_VTX1_PATGEN_CLK_SRC_25MHZ	0b100
> +#define MAX96717_VTX1_PATGEN_CLK_SRC_75MHZ	0b101
> +#define MAX96717_VTX1_PATGEN_CLK_SRC_150MHZ	0b110
> +#define MAX96717_VTX1_PATGEN_CLK_SRC_375MHZ	0b111
> +
> +#define MAX96717_VTX2_VS_DLY_2(p)		(0x1ca + (p) * 0x43)
> +#define MAX96717_VTX5_VS_HIGH_2(p)		(0x1cd + (p) * 0x43)
> +#define MAX96717_VTX8_VS_LOW_2(p)		(0x1d0 + (p) * 0x43)
> +#define MAX96717_VTX11_V2H_2(p)			(0x1d3 + (p) * 0x43)
> +#define MAX96717_VTX14_HS_HIGH_1(p)		(0x1d6 + (p) * 0x43)
> +#define MAX96717_VTX16_HS_LOW_1(p)		(0x1d8 + (p) * 0x43)
> +#define MAX96717_VTX18_HS_CNT_1(p)		(0x1da + (p) * 0x43)
> +#define MAX96717_VTX20_V2D_2(p)			(0x1dc + (p) * 0x43)
> +#define MAX96717_VTX23_DE_HIGH_1(p)		(0x1df + (p) * 0x43)
> +#define MAX96717_VTX25_DE_LOW_1(p)		(0x1e1 + (p) * 0x43)
> +#define MAX96717_VTX27_DE_CNT_1(p)		(0x1e3 + (p) * 0x43)
> +#define MAX96717_VTX29(p)			(0x1e5 + (p) * 0x43)
> +#define MAX96717_VTX29_PATGEN_MODE		GENMASK(1, 0)
> +#define MAX96717_VTX29_PATGEN_MODE_DISABLED	0b00
> +#define MAX96717_VTX29_PATGEN_MODE_GRADIENT	0b10
> +#define MAX96717_VTX30_GRAD_INCR(p)		(0x1e6 + (p) * 0x43)
> +
> +#define MAX96717_GPIO_A(x)			(0x2be + (x) * 0x3)
> +#define MAX96717_GPIO_A_GPIO_OUT_DIS		BIT(0)
> +#define MAX96717_GPIO_A_GPIO_TX_EN		BIT(1)
> +#define MAX96717_GPIO_A_GPIO_RX_EN		BIT(2)
> +#define MAX96717_GPIO_A_GPIO_IN			BIT(3)
> +#define MAX96717_GPIO_A_GPIO_OUT		BIT(4)
> +#define MAX96717_GPIO_A_TX_COMP_EN		BIT(5)
> +#define MAX96717_GPIO_A_RES_CFG			BIT(7)
> +
> +#define MAX96717_GPIO_B(x)			(0x2bf + (x) * 0x3)
> +#define MAX96717_GPIO_B_GPIO_TX_ID		GENMASK(4, 0)
> +#define MAX96717_GPIO_B_OUT_TYPE		BIT(5)
> +#define MAX96717_GPIO_B_PULL_UPDN_SEL		GENMASK(7, 6)
> +#define MAX96717_GPIO_B_PULL_UPDN_SEL_NONE	0b00
> +#define MAX96717_GPIO_B_PULL_UPDN_SEL_PU	0b01
> +#define MAX96717_GPIO_B_PULL_UPDN_SEL_PD	0b10
> +
> +#define MAX96717_GPIO_C(x)			(0x2c0 + (x) * 0x3)
> +#define MAX96717_GPIO_C_GPIO_RX_ID		GENMASK(4, 0)
> +
> +#define MAX96717_CMU2				0x302
> +#define MAX96717_CMU2_PFDDIV_RSHORT		GENMASK(6, 4)
> +#define MAX96717_CMU2_PFDDIV_RSHORT_1_1V	0b001
> +
> +#define MAX96717_FRONTTOP_0			0x308
> +#define MAX96717_FRONTTOP_0_CLK_SEL_P(x)	BIT(x)
> +#define MAX96717_FRONTTOP_0_START_PORT(x)	BIT((x) + 4)
> +
> +#define MAX96717_FRONTTOP_1(p)			(0x309 + (p) * 0x2)
> +#define MAX96717_FRONTTOP_2(p)			(0x30a + (p) * 0x2)
> +
> +#define MAX96717_FRONTTOP_9			0x311
> +#define MAX96717_FRONTTOP_9_START_PORT(p, x)	BIT((p) + (x) * 4)
> +
> +#define MAX96717_FRONTTOP_10			0x312
> +#define MAX96717_FRONTTOP_10_BPP8DBL(p)		BIT(p)
> +
> +#define MAX96717_FRONTTOP_11			0x313
> +#define MAX96717_FRONTTOP_11_BPP10DBL(p)	BIT(p)
> +#define MAX96717_FRONTTOP_11_BPP12DBL(p)	BIT((p) + 4)
> +
> +#define MAX96717_FRONTTOP_12(p, x)		(0x314 + (p) * 0x2 + (x))
> +#define MAX96717_MEM_DT_SEL			GENMASK(5, 0)
> +#define MAX96717_MEM_DT_EN			BIT(6)
> +
> +#define MAX96717_FRONTTOP_20(p)			(0x31c + (p) * 0x1)
> +#define MAX96717_FRONTTOP_20_SOFT_BPP_EN	BIT(5)
> +#define MAX96717_FRONTTOP_20_SOFT_BPP		GENMASK(4, 0)
> +
> +#define MAX96717_MIPI_RX0			0x330
> +#define MAX96717_MIPI_RX0_NONCONTCLK_EN		BIT(6)
> +
> +#define MAX96717_MIPI_RX1			0x331
> +#define MAX96717_MIPI_RX1_CTRL_NUM_LANES	GENMASK(5, 4)
> +
> +#define MAX96717_MIPI_RX2			0x332
> +#define MAX96717_MIPI_RX2_PHY1_LANE_MAP		GENMASK(7, 4)
> +
> +#define MAX96717_MIPI_RX3			0x333
> +#define MAX96717_MIPI_RX3_PHY2_LANE_MAP		GENMASK(3, 0)
> +
> +#define MAX96717_MIPI_RX4			0x334
> +#define MAX96717_MIPI_RX4_PHY1_POL_MAP		GENMASK(5, 4)
> +
> +#define MAX96717_MIPI_RX5			0x335
> +#define MAX96717_MIPI_RX5_PHY2_POL_MAP		GENMASK(1, 0)
> +#define MAX96717_MIPI_RX5_PHY2_POL_MAP_CLK	BIT(2)
> +
> +#define MAX96717_EXTA(x)			(0x3dc + (x))
> +
> +#define MAX96717_EXT11				0x383
> +#define MAX96717_EXT11_TUN_MODE			BIT(7)
> +
> +#define MAX96717_EXT21				0x38d
> +#define MAX96717_EXT22				0x38e
> +#define MAX96717_EXT23				0x38f
> +#define MAX96717_EXT24				0x390
> +
> +#define MAX96717_REF_VTG0			0x3f0
> +#define MAX96717_REF_VTG0_REFGEN_EN		BIT(0)
> +#define MAX96717_REF_VTG0_REFGEN_RST		BIT(1)
> +#define MAX96717_REF_VTG0_REFGEN_PREDEF_FREQ_ALT\
> +						BIT(3)
> +#define MAX96717_REF_VTG0_REFGEN_PREDEF_FREQ	GENMASK(5, 4)
> +#define MAX96717_REF_VTG0_REFGEN_PREDEF_EN	BIT(6)
> +
> +#define MAX96717_REF_VTG1			0x3f1
> +#define MAX96717_REF_VTG1_PCLKEN		BIT(0)
> +#define MAX96717_REF_VTG1_PCLK_GPIO		GENMASK(5, 1)
> +#define MAX96717_REF_VTG1_RCLKEN_Y		BIT(7)
> +
> +#define MAX96717_PIO_SLEW_0			0x56f
> +#define MAX96717_PIO_SLEW_0_PIO00_SLEW		GENMASK(1, 0)
> +#define MAX96717_PIO_SLEW_0_PIO01_SLEW		GENMASK(3, 2)
> +#define MAX96717_PIO_SLEW_0_PIO02_SLEW		GENMASK(5, 4)
> +
> +#define MAX96717_PIO_SLEW_1			0x570
> +#define MAX96717_PIO_SLEW_1_PIO05_SLEW		GENMASK(3, 2)
> +#define MAX96717_PIO_SLEW_1_PIO06_SLEW		GENMASK(5, 4)
> +
> +#define MAX96717_PIO_SLEW_2			0x571
> +#define MAX96717_PIO_SLEW_2_PIO010_SLEW		GENMASK(5, 4)
> +#define MAX96717_PIO_SLEW_2_PIO011_SLEW		GENMASK(7, 6)
> +
> +#define MAX96717_PIO_SLEW_FASTEST		0b00
> +
> +#define MAX96717_BIAS_PULL_STRENGTH_1000000_OHM	1000000U
> +#define MAX96717_BIAS_PULL_STRENGTH_40000_OHM	40000U
> +
> +#define MAX96717_DEFAULT_CLKOUT_RATE		24000000UL
> +
> +#define MAX96717_NAME				"max96717"
> +#define MAX96717_PINCTRL_NAME			MAX96717_NAME "-pinctrl"
> +#define MAX96717_GPIOCHIP_NAME			MAX96717_NAME "-gpiochip"
> +#define MAX96717_GPIO_NUM			11
> +#define MAX96717_PIPES_NUM			4
> +#define MAX96717_PHYS_NUM			2
> +
> +struct max96717_priv {
> +	struct max_ser ser;
> +	struct pinctrl_desc pctldesc;
> +	struct gpio_chip gc;
> +	const struct max96717_chip_info *info;
> +
> +	struct device *dev;
> +	struct i2c_client *client;
> +	struct regmap *regmap;
> +	struct pinctrl_dev *pctldev;
> +
> +	struct clk_hw clk_hw;
> +	u8 pll_predef_index;
> +};
> +
> +struct max96717_chip_info {
> +	bool supports_3_data_lanes;
> +	bool supports_noncontinuous_clock;
> +	bool supports_pkt_cnt;
> +	unsigned int modes;
> +	unsigned int num_pipes;
> +	unsigned int num_dts_per_pipe;
> +	unsigned int pipe_hw_ids[MAX96717_PIPES_NUM];
> +	unsigned int num_phys;
> +	unsigned int phy_hw_ids[MAX96717_PHYS_NUM];
> +};
> +
> +#define ser_to_priv(_ser) \
> +	container_of(_ser, struct max96717_priv, ser)
> +
> +static inline struct max96717_priv *clk_hw_to_priv(struct clk_hw *hw)
> +{
> +	return container_of(hw, struct max96717_priv, clk_hw);
> +}
> +
> +static const struct regmap_config max96717_i2c_regmap = {
> +	.reg_bits = 16,
> +	.val_bits = 8,
> +	.max_register = 0x1f00,
> +};
> +
> +static int max96717_wait_for_device(struct max96717_priv *priv)
> +{
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < 10; i++) {
> +		unsigned int val;
> +
> +		ret = regmap_read(priv->regmap, MAX96717_REG0, &val);
> +		if (!ret && val)
> +			return 0;
> +
> +		msleep(100);
> +
> +		dev_err(priv->dev, "Retry %u waiting for serializer: %d\n", i, ret);
> +	}
> +
> +	return ret;
> +}
> +
> +#define MAX96717_PIN(n) \
> +	PINCTRL_PIN(n, "mfp" __stringify(n))
> +
> +static const struct pinctrl_pin_desc max96717_pins[] = {
> +	MAX96717_PIN(0),
> +	MAX96717_PIN(1),
> +	MAX96717_PIN(2),
> +	MAX96717_PIN(3),
> +	MAX96717_PIN(4),
> +	MAX96717_PIN(5),
> +	MAX96717_PIN(6),
> +	MAX96717_PIN(7),
> +	MAX96717_PIN(8),
> +	MAX96717_PIN(9),
> +	MAX96717_PIN(10),
> +};
> +
> +#define MAX96717_GROUP_PINS(name, ...) \
> +	static const unsigned int name ## _pins[] = { __VA_ARGS__ }
> +
> +MAX96717_GROUP_PINS(mfp0, 0);
> +MAX96717_GROUP_PINS(mfp1, 1);
> +MAX96717_GROUP_PINS(mfp2, 2);
> +MAX96717_GROUP_PINS(mfp3, 3);
> +MAX96717_GROUP_PINS(mfp4, 4);
> +MAX96717_GROUP_PINS(mfp5, 5);
> +MAX96717_GROUP_PINS(mfp6, 6);
> +MAX96717_GROUP_PINS(mfp7, 7);
> +MAX96717_GROUP_PINS(mfp8, 8);
> +MAX96717_GROUP_PINS(mfp9, 9);
> +MAX96717_GROUP_PINS(mfp10, 10);
> +
> +#define MAX96717_GROUP(name) \
> +	PINCTRL_PINGROUP(__stringify(name), name ## _pins, ARRAY_SIZE(name ## _pins))
> +
> +static const struct pingroup max96717_ctrl_groups[] = {
> +	MAX96717_GROUP(mfp0),
> +	MAX96717_GROUP(mfp1),
> +	MAX96717_GROUP(mfp2),
> +	MAX96717_GROUP(mfp3),
> +	MAX96717_GROUP(mfp4),
> +	MAX96717_GROUP(mfp5),
> +	MAX96717_GROUP(mfp6),
> +	MAX96717_GROUP(mfp7),
> +	MAX96717_GROUP(mfp8),
> +	MAX96717_GROUP(mfp9),
> +	MAX96717_GROUP(mfp10),
> +};
> +
> +#define MAX96717_FUNC_GROUPS(name, ...) \
> +	static const char * const name ## _groups[] = { __VA_ARGS__ }
> +
> +MAX96717_FUNC_GROUPS(gpio, "mfp0", "mfp1", "mfp2", "mfp3", "mfp4", "mfp5",
> +		     "mfp6", "mfp7", "mfp8", "mfp9", "mfp10");
> +MAX96717_FUNC_GROUPS(rclkout, "mfp0", "mfp1", "mfp2", "mfp3", "mfp4",
> +		     "mfp7", "mfp8");
> +
> +enum max96717_func {
> +	max96717_func_gpio,
> +	max96717_func_rclkout,
> +};
> +
> +#define MAX96717_FUNC(name)						\
> +	[max96717_func_ ## name] =					\
> +		PINCTRL_PINFUNCTION(__stringify(name), name ## _groups,	\
> +				    ARRAY_SIZE(name ## _groups))
> +
> +static const struct pinfunction max96717_functions[] = {
> +	MAX96717_FUNC(gpio),
> +	MAX96717_FUNC(rclkout),
> +};
> +
> +#define MAX96717_PINCTRL_X(x)			(PIN_CONFIG_END + (x))
> +#define MAX96717_PINCTRL_PULL_STRENGTH_HIGH	MAX96717_PINCTRL_X(1)
> +#define MAX96717_PINCTRL_JITTER_COMPENSATION_EN	MAX96717_PINCTRL_X(2)
> +#define MAX96717_PINCTRL_GMSL_TX_EN		MAX96717_PINCTRL_X(3)
> +#define MAX96717_PINCTRL_GMSL_RX_EN		MAX96717_PINCTRL_X(4)
> +#define MAX96717_PINCTRL_GMSL_TX_ID		MAX96717_PINCTRL_X(5)
> +#define MAX96717_PINCTRL_GMSL_RX_ID		MAX96717_PINCTRL_X(6)
> +#define MAX96717_PINCTRL_RCLKOUT_CLK		MAX96717_PINCTRL_X(7)
> +#define MAX96717_PINCTRL_INPUT_VALUE		MAX96717_PINCTRL_X(8)
> +
> +static const struct pinconf_generic_params max96717_cfg_params[] = {
> +	{ "maxim,jitter-compensation", MAX96717_PINCTRL_JITTER_COMPENSATION_EN, 0 },
> +	{ "maxim,gmsl-tx", MAX96717_PINCTRL_GMSL_TX_EN, 0 },
> +	{ "maxim,gmsl-rx", MAX96717_PINCTRL_GMSL_RX_EN, 0 },
> +	{ "maxim,gmsl-tx-id", MAX96717_PINCTRL_GMSL_TX_ID, 0 },
> +	{ "maxim,gmsl-rx-id", MAX96717_PINCTRL_GMSL_RX_ID, 0 },
> +	{ "maxim,rclkout-clock", MAX96717_PINCTRL_RCLKOUT_CLK, 0 },
> +};
> +
> +static int max96717_ctrl_get_groups_count(struct pinctrl_dev *pctldev)
> +{
> +	return ARRAY_SIZE(max96717_ctrl_groups);
> +}
> +
> +static const char *max96717_ctrl_get_group_name(struct pinctrl_dev *pctldev,
> +						unsigned int selector)
> +{
> +	return max96717_ctrl_groups[selector].name;
> +}
> +
> +static int max96717_ctrl_get_group_pins(struct pinctrl_dev *pctldev,
> +					unsigned int selector,
> +					const unsigned int **pins,
> +					unsigned int *num_pins)
> +{
> +	*pins = (unsigned int *)max96717_ctrl_groups[selector].pins;
> +	*num_pins = max96717_ctrl_groups[selector].npins;
> +
> +	return 0;
> +}
> +
> +static int max96717_get_pin_config_reg(unsigned int offset, u32 param,
> +				       unsigned int *reg, unsigned int *mask,
> +				       unsigned int *val)
> +{
> +	*reg = MAX96717_GPIO_A(offset);
> +
> +	switch (param) {
> +	case PIN_CONFIG_OUTPUT_ENABLE:
> +		*mask = MAX96717_GPIO_A_GPIO_OUT_DIS;
> +		*val = 0b0;
> +		return 0;
> +	case PIN_CONFIG_INPUT_ENABLE:
> +		*mask = MAX96717_GPIO_A_GPIO_OUT_DIS;
> +		*val = 0b1;
> +		return 0;
> +	case MAX96717_PINCTRL_GMSL_TX_EN:
> +		*mask = MAX96717_GPIO_A_GPIO_TX_EN;
> +		*val = 0b1;
> +		return 0;
> +	case MAX96717_PINCTRL_GMSL_RX_EN:
> +		*mask = MAX96717_GPIO_A_GPIO_RX_EN;
> +		*val = 0b1;
> +		return 0;
> +	case MAX96717_PINCTRL_INPUT_VALUE:
> +		*mask = MAX96717_GPIO_A_GPIO_IN;
> +		*val = 0b1;
> +		return 0;
> +	case PIN_CONFIG_OUTPUT:
> +		*mask = MAX96717_GPIO_A_GPIO_OUT;
> +		*val = 0b1;
> +		return 0;
> +	case MAX96717_PINCTRL_JITTER_COMPENSATION_EN:
> +		*mask = MAX96717_GPIO_A_TX_COMP_EN;
> +		*val = 0b1;
> +		return 0;
> +	case MAX96717_PINCTRL_PULL_STRENGTH_HIGH:
> +		*mask = MAX96717_GPIO_A_RES_CFG;
> +		*val = 0b1;
> +		return 0;
> +	}
> +
> +	*reg = MAX96717_GPIO_B(offset);
> +
> +	switch (param) {
> +	case MAX96717_PINCTRL_GMSL_TX_ID:
> +		*mask = MAX96717_GPIO_B_GPIO_TX_ID;
> +		return 0;
> +	case PIN_CONFIG_DRIVE_OPEN_DRAIN:
> +		*mask = MAX96717_GPIO_B_OUT_TYPE;
> +		*val = 0b0;
> +		return 0;
> +	case PIN_CONFIG_DRIVE_PUSH_PULL:
> +		*mask = MAX96717_GPIO_B_OUT_TYPE;
> +		*val = 0b1;
> +		return 0;
> +	case PIN_CONFIG_BIAS_DISABLE:
> +		*mask = MAX96717_GPIO_B_PULL_UPDN_SEL;
> +		*val = MAX96717_GPIO_B_PULL_UPDN_SEL_NONE;
> +		return 0;
> +	case PIN_CONFIG_BIAS_PULL_DOWN:
> +		*mask = MAX96717_GPIO_B_PULL_UPDN_SEL;
> +		*val = MAX96717_GPIO_B_PULL_UPDN_SEL_PD;
> +		return 0;
> +	case PIN_CONFIG_BIAS_PULL_UP:
> +		*mask = MAX96717_GPIO_B_PULL_UPDN_SEL;
> +		*val = MAX96717_GPIO_B_PULL_UPDN_SEL_PU;
> +		return 0;
> +	}
> +
> +	switch (param) {
> +	case PIN_CONFIG_SLEW_RATE:
> +		if (offset < 3) {
> +			*reg = MAX96717_PIO_SLEW_0;
> +			if (offset == 0)
> +				*mask = MAX96717_PIO_SLEW_0_PIO00_SLEW;
> +			else if (offset == 1)
> +				*mask = MAX96717_PIO_SLEW_0_PIO01_SLEW;
> +			else
> +				*mask = MAX96717_PIO_SLEW_0_PIO02_SLEW;
> +		} else if (offset < 5) {
> +			*reg = MAX96717_PIO_SLEW_1;
> +			if (offset == 3)
> +				*mask = MAX96717_PIO_SLEW_1_PIO05_SLEW;
> +			else
> +				*mask = MAX96717_PIO_SLEW_1_PIO06_SLEW;
> +		} else if (offset < 7) {
> +			return -EINVAL;
> +		} else if (offset < 9) {
> +			*reg  = MAX96717_PIO_SLEW_2;
> +			if (offset == 7)
> +				*mask = MAX96717_PIO_SLEW_2_PIO010_SLEW;
> +			else
> +				*mask = MAX96717_PIO_SLEW_2_PIO011_SLEW;
> +		} else {
> +			return -EINVAL;
> +		}
> +		return 0;
> +	case MAX96717_PINCTRL_GMSL_RX_ID:
> +		*reg = MAX96717_GPIO_C(offset);
> +		*mask = MAX96717_GPIO_C_GPIO_RX_ID;
> +		return 0;
> +	case MAX96717_PINCTRL_RCLKOUT_CLK:
> +		if (offset != 2 && offset != 4)
> +			return -EINVAL;
> +
> +		*reg = MAX96717_REG3;
> +		*mask = MAX96717_REG3_RCLKSEL;
> +		return 0;
> +	default:
> +		return -ENOTSUPP;
> +	}
> +}
> +
> +static int max96717_conf_pin_config_get(struct pinctrl_dev *pctldev,
> +					unsigned int offset,
> +					unsigned long *config)
> +{
> +	struct max96717_priv *priv = pinctrl_dev_get_drvdata(pctldev);
> +	u32 param = pinconf_to_config_param(*config);
> +	unsigned int reg, mask, val, en_val;
> +	int ret;
> +
> +	ret = max96717_get_pin_config_reg(offset, param, &reg, &mask, &en_val);
> +	if (ret)
> +		return ret;
> +
> +	switch (param) {
> +	case PIN_CONFIG_DRIVE_OPEN_DRAIN:
> +	case PIN_CONFIG_DRIVE_PUSH_PULL:
> +	case PIN_CONFIG_BIAS_DISABLE:
> +	case PIN_CONFIG_BIAS_PULL_DOWN:
> +	case PIN_CONFIG_BIAS_PULL_UP:
> +	case MAX96717_PINCTRL_JITTER_COMPENSATION_EN:
> +	case MAX96717_PINCTRL_GMSL_TX_EN:
> +	case MAX96717_PINCTRL_GMSL_RX_EN:
> +	case PIN_CONFIG_OUTPUT_ENABLE:
> +	case PIN_CONFIG_INPUT_ENABLE:
> +		ret = regmap_read(priv->regmap, reg, &val);
> +		if (ret)
> +			return ret;
> +
> +		val = field_get(mask, val) == en_val;
> +		if (!val)
> +			return -EINVAL;
> +
> +		break;
> +	case MAX96717_PINCTRL_PULL_STRENGTH_HIGH:
> +	case MAX96717_PINCTRL_INPUT_VALUE:
> +	case PIN_CONFIG_OUTPUT:
> +		ret = regmap_read(priv->regmap, reg, &val);
> +		if (ret)
> +			return ret;
> +
> +		val = field_get(mask, val) == en_val;
> +		break;
> +	case MAX96717_PINCTRL_GMSL_TX_ID:
> +	case MAX96717_PINCTRL_GMSL_RX_ID:
> +	case MAX96717_PINCTRL_RCLKOUT_CLK:
> +	case PIN_CONFIG_SLEW_RATE:
> +		ret = regmap_read(priv->regmap, reg, &val);
> +		if (ret)
> +			return ret;
> +
> +		val = field_get(mask, val);
> +		break;
> +	default:
> +		return -ENOTSUPP;
> +	}
> +
> +	switch (param) {
> +	case PIN_CONFIG_BIAS_PULL_DOWN:
> +	case PIN_CONFIG_BIAS_PULL_UP:
> +		*config = pinconf_to_config_packed(MAX96717_PINCTRL_PULL_STRENGTH_HIGH, 0);
> +
> +		ret = max96717_conf_pin_config_get(pctldev, offset, config);
> +		if (ret)
> +			return ret;
> +
> +		val = pinconf_to_config_argument(*config);
> +		if (val)
> +			val = MAX96717_BIAS_PULL_STRENGTH_1000000_OHM;
> +		else
> +			val = MAX96717_BIAS_PULL_STRENGTH_40000_OHM;
> +
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	*config = pinconf_to_config_packed(param, val);
> +
> +	return 0;
> +}
> +
> +static int max96717_conf_pin_config_set_one(struct max96717_priv *priv,
> +					    unsigned int offset,
> +					    unsigned long config)
> +{
> +	u32 param = pinconf_to_config_param(config);
> +	u32 arg = pinconf_to_config_argument(config);
> +	unsigned int reg, mask, val, en_val;
> +	int ret;
> +
> +	ret = max96717_get_pin_config_reg(offset, param, &reg, &mask, &en_val);
> +	if (ret)
> +		return ret;
> +
> +	switch (param) {
> +	case PIN_CONFIG_DRIVE_OPEN_DRAIN:
> +	case PIN_CONFIG_DRIVE_PUSH_PULL:
> +	case PIN_CONFIG_BIAS_DISABLE:
> +	case PIN_CONFIG_BIAS_PULL_DOWN:
> +	case PIN_CONFIG_BIAS_PULL_UP:
> +		val = field_prep(mask, en_val);
> +
> +		ret = regmap_update_bits(priv->regmap, reg, mask, val);
> +		break;
> +	case MAX96717_PINCTRL_JITTER_COMPENSATION_EN:
> +	case MAX96717_PINCTRL_PULL_STRENGTH_HIGH:
> +	case MAX96717_PINCTRL_GMSL_TX_EN:
> +	case MAX96717_PINCTRL_GMSL_RX_EN:
> +	case PIN_CONFIG_OUTPUT_ENABLE:
> +	case PIN_CONFIG_INPUT_ENABLE:
> +	case PIN_CONFIG_OUTPUT:
> +		val = field_prep(mask, arg ? en_val : ~en_val);
> +
> +		ret = regmap_update_bits(priv->regmap, reg, mask, val);
> +		break;
> +	case MAX96717_PINCTRL_GMSL_TX_ID:
> +	case MAX96717_PINCTRL_GMSL_RX_ID:
> +	case MAX96717_PINCTRL_RCLKOUT_CLK:
> +	case PIN_CONFIG_SLEW_RATE:
> +		val = field_prep(mask, arg);
> +
> +		ret = regmap_update_bits(priv->regmap, reg, mask, val);
> +		break;
> +	default:
> +		return -ENOTSUPP;
> +	}
> +
> +	if (ret)
> +		return ret;
> +
> +	switch (param) {
> +	case PIN_CONFIG_BIAS_PULL_DOWN:
> +	case PIN_CONFIG_BIAS_PULL_UP:
> +		arg = arg >= MAX96717_BIAS_PULL_STRENGTH_1000000_OHM;
> +		config = pinconf_to_config_packed(MAX96717_PINCTRL_PULL_STRENGTH_HIGH, arg);
> +		return max96717_conf_pin_config_set_one(priv, offset, config);
> +	case PIN_CONFIG_OUTPUT:
> +		config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT_ENABLE, 1);
> +		return max96717_conf_pin_config_set_one(priv, offset, config);
> +	case PIN_CONFIG_OUTPUT_ENABLE:
> +		config = pinconf_to_config_packed(MAX96717_PINCTRL_GMSL_RX_EN, 0);
> +		return max96717_conf_pin_config_set_one(priv, offset, config);
> +	default:
> +		break;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max96717_conf_pin_config_set(struct pinctrl_dev *pctldev,
> +					unsigned int offset,
> +					unsigned long *configs,
> +					unsigned int num_configs)
> +{
> +	struct max96717_priv *priv = pinctrl_dev_get_drvdata(pctldev);
> +	int ret;
> +
> +	while (num_configs--) {
> +		unsigned long config = *configs;
> +
> +		ret = max96717_conf_pin_config_set_one(priv, offset, config);
> +		if (ret)
> +			return ret;
> +
> +		configs++;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max96717_mux_get_functions_count(struct pinctrl_dev *pctldev)
> +{
> +	return ARRAY_SIZE(max96717_functions);
> +}
> +
> +static const char *max96717_mux_get_function_name(struct pinctrl_dev *pctldev,
> +						  unsigned int selector)
> +{
> +	return max96717_functions[selector].name;
> +}
> +
> +static int max96717_mux_get_groups(struct pinctrl_dev *pctldev,
> +				   unsigned int selector,
> +				   const char * const **groups,
> +				   unsigned int * const num_groups)
> +{
> +	*groups = max96717_functions[selector].groups;
> +	*num_groups = max96717_functions[selector].ngroups;
> +
> +	return 0;
> +}
> +
> +static int max96717_mux_set_rclkout(struct max96717_priv *priv, unsigned int group)
> +{
> +	int ret;
> +
> +	/* Enable PCLK output. */
> +	ret = regmap_set_bits(priv->regmap, MAX96717_REF_VTG1,
> +			      MAX96717_REF_VTG1_PCLKEN);
> +	if (ret)
> +		return ret;
> +
> +	/* Set PCLK output to the RCLK pin. */
> +	ret = regmap_update_bits(priv->regmap, MAX96717_REF_VTG1,
> +				 MAX96717_REF_VTG1_PCLK_GPIO,
> +				 FIELD_PREP(MAX96717_REF_VTG1_PCLK_GPIO, group));
> +	if (ret)
> +		return ret;
> +
> +	/* Enable RCLK output on PCLK. */
> +	ret = regmap_set_bits(priv->regmap, MAX96717_REF_VTG1,
> +			      MAX96717_REF_VTG1_RCLKEN_Y);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int max96717_mux_set(struct pinctrl_dev *pctldev, unsigned int selector,
> +			    unsigned int group)
> +{
> +	struct max96717_priv *priv = pinctrl_dev_get_drvdata(pctldev);
> +
> +	switch (selector) {
> +	case max96717_func_rclkout:
> +		return max96717_mux_set_rclkout(priv, group);
> +	}
> +
> +	return 0;
> +}
> +
> +static int max96717_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
> +{
> +	unsigned long config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT_ENABLE, 0);
> +	struct max96717_priv *priv = gpiochip_get_data(gc);
> +	int ret;
> +
> +	ret = max96717_conf_pin_config_get(priv->pctldev, offset, &config);
> +	if (ret)
> +		return ret;
> +
> +	return pinconf_to_config_argument(config) ? GPIO_LINE_DIRECTION_OUT
> +						  : GPIO_LINE_DIRECTION_IN;
> +}
> +
> +static int max96717_gpio_direction_input(struct gpio_chip *gc, unsigned int offset)
> +{
> +	unsigned long config = pinconf_to_config_packed(PIN_CONFIG_INPUT_ENABLE, 1);
> +	struct max96717_priv *priv = gpiochip_get_data(gc);
> +
> +	return max96717_conf_pin_config_set_one(priv, offset, config);
> +}
> +
> +static int max96717_gpio_direction_output(struct gpio_chip *gc, unsigned int offset,
> +					  int value)
> +{
> +	unsigned long config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, value);
> +	struct max96717_priv *priv = gpiochip_get_data(gc);
> +
> +	return max96717_conf_pin_config_set_one(priv, offset, config);
> +}
> +
> +static int max96717_gpio_get(struct gpio_chip *gc, unsigned int offset)
> +{
> +	unsigned long config = pinconf_to_config_packed(MAX96717_PINCTRL_INPUT_VALUE, 0);
> +	struct max96717_priv *priv = gpiochip_get_data(gc);
> +	int ret;
> +
> +	ret = max96717_conf_pin_config_get(priv->pctldev, offset, &config);
> +	if (ret)
> +		return ret;
> +
> +	return pinconf_to_config_argument(config);
> +}
> +
> +static int max96717_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
> +{
> +	unsigned long config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, value);
> +	struct max96717_priv *priv = gpiochip_get_data(gc);
> +
> +	return max96717_conf_pin_config_set_one(priv, offset, config);
> +}
> +
> +static unsigned int max96717_pipe_id(struct max96717_priv *priv,
> +				     struct max_ser_pipe *pipe)
> +{
> +	return priv->info->pipe_hw_ids[pipe->index];
> +}
> +
> +static unsigned int max96717_phy_id(struct max96717_priv *priv,
> +				    struct max_ser_phy *phy)
> +{
> +	return priv->info->phy_hw_ids[phy->index];
> +}
> +
> +static int max96717_set_pipe_enable(struct max_ser *ser,
> +				    struct max_ser_pipe *pipe, bool enable)
> +{
> +	struct max96717_priv *priv = ser_to_priv(ser);
> +	unsigned int index = max96717_pipe_id(priv, pipe);
> +	unsigned int mask = MAX96717_REG2_VID_TX_EN_P(index);
> +
> +	return regmap_assign_bits(priv->regmap, MAX96717_REG2, mask, enable);
> +}
> +
> +static int max96717_reg_read(struct max_ser *ser, unsigned int reg,
> +			     unsigned int *val)
> +{
> +	struct max96717_priv *priv = ser_to_priv(ser);
> +
> +	return regmap_read(priv->regmap, reg, val);
> +}
> +
> +static int max96717_reg_write(struct max_ser *ser, unsigned int reg,
> +			      unsigned int val)
> +{
> +	struct max96717_priv *priv = ser_to_priv(ser);
> +
> +	return regmap_write(priv->regmap, reg, val);
> +}
> +
> +static int max96717_set_pipe_dt_en(struct max_ser *ser, struct max_ser_pipe *pipe,
> +				   unsigned int i, bool enable)
> +{
> +	struct max96717_priv *priv = ser_to_priv(ser);
> +	unsigned int index = max96717_pipe_id(priv, pipe);
> +	unsigned int reg;
> +
> +	if (i < 2)
> +		reg = MAX96717_FRONTTOP_12(index, i);
> +	else
> +		/*
> +		 * DT 7 and 8 are only supported on MAX96717, no need for pipe
> +		 * index to be taken into account.
> +		 */
> +		reg = MAX96717_EXTA(i - 2);
> +
> +	return regmap_assign_bits(priv->regmap, reg, MAX96717_MEM_DT_EN, enable);
> +}
> +
> +static int max96717_set_pipe_dt(struct max_ser *ser, struct max_ser_pipe *pipe,
> +				unsigned int i, unsigned int dt)
> +{
> +	struct max96717_priv *priv = ser_to_priv(ser);
> +	unsigned int index = max96717_pipe_id(priv, pipe);
> +	unsigned int reg;
> +
> +	if (i < 2)
> +		reg = MAX96717_FRONTTOP_12(index,  i);
> +	else
> +		reg = MAX96717_EXTA(i - 2);
> +
> +	return regmap_update_bits(priv->regmap, reg, MAX96717_MEM_DT_SEL,
> +				  FIELD_PREP(MAX96717_MEM_DT_SEL, dt));
> +}
> +
> +static int max96717_set_pipe_vcs(struct max_ser *ser,
> +				 struct max_ser_pipe *pipe,
> +				 unsigned int vcs)
> +{
> +	struct max96717_priv *priv = ser_to_priv(ser);
> +	unsigned int index = max96717_pipe_id(priv, pipe);
> +	int ret;
> +
> +	ret = regmap_write(priv->regmap, MAX96717_FRONTTOP_1(index),
> +			   (vcs >> 0) & 0xff);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_write(priv->regmap, MAX96717_FRONTTOP_2(index),
> +			      (vcs >> 8) & 0xff);
> +}
> +
> +static int max96717_log_status(struct max_ser *ser, const char *name)
> +{
> +	struct max96717_priv *priv = ser_to_priv(ser);
> +	unsigned int val;
> +	int ret;
> +
> +	if (!(priv->info->modes & BIT(MAX_GMSL_TUNNEL_MODE)))
> +		return 0;
> +
> +	ret = regmap_read(priv->regmap, MAX96717_EXT23, &val);
> +	if (ret)
> +		return ret;
> +
> +	pr_info("%s: tun_pkt_cnt: %u\n", name, val);
> +
> +	return 0;
> +}
> +
> +static int max96717_log_pipe_status(struct max_ser *ser,
> +				    struct max_ser_pipe *pipe,
> +				    const char *name)
> +{
> +	struct max96717_priv *priv = ser_to_priv(ser);
> +	unsigned int index = max96717_pipe_id(priv, pipe);
> +	unsigned int val;
> +	int ret;
> +
> +	ret = regmap_read(priv->regmap, MAX96717_VIDEO_TX2(index), &val);
> +	if (ret)
> +		return ret;
> +
> +	pr_info("%s: \tpclkdet: %u\n", name, !!(val & MAX96717_VIDEO_TX2_PCLKDET));
> +
> +	return 0;
> +}
> +
> +static int max96717_log_phy_status(struct max_ser *ser,
> +				   struct max_ser_phy *phy,
> +				   const char *name)
> +{
> +	struct max96717_priv *priv = ser_to_priv(ser);
> +	unsigned int val;
> +	int ret;
> +
> +	if (!priv->info->supports_pkt_cnt)
> +		return 0;
> +
> +	ret = regmap_read(priv->regmap, MAX96717_EXT21, &val);
> +	if (ret)
> +		return ret;
> +
> +	pr_info("%s: \tphy_pkt_cnt: %u\n", name, val);
> +
> +	ret = regmap_read(priv->regmap, MAX96717_EXT22, &val);
> +	if (ret)
> +		return ret;
> +
> +	pr_info("%s: \tcsi_pkt_cnt: %u\n", name, val);
> +
> +	ret = regmap_read(priv->regmap, MAX96717_EXT24, &val);
> +	if (ret)
> +		return ret;
> +
> +	pr_info("%s: \tphy_clk_cnt: %u\n", name, val);
> +
> +	return 0;
> +}
> +
> +static int max96717_init_phy(struct max_ser *ser,
> +			     struct max_ser_phy *phy)
> +{
> +	struct max96717_priv *priv = ser_to_priv(ser);
> +	unsigned int num_data_lanes = phy->mipi.num_data_lanes;
> +	unsigned int used_data_lanes = 0;
> +	unsigned int val;
> +	unsigned int i;
> +	int ret;
> +
> +	if (num_data_lanes == 3 && !priv->info->supports_3_data_lanes) {
> +		dev_err(priv->dev, "Unsupported 3 data lane mode\n");
> +		return -EINVAL;
> +	}
> +
> +	if (phy->mipi.flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK &&
> +	    !priv->info->supports_noncontinuous_clock) {
> +		dev_err(priv->dev, "Unsupported non-continuous mode\n");
> +		return -EINVAL;
> +	}
> +
> +	/* Configure a lane count. */
> +	ret = regmap_update_bits(priv->regmap, MAX96717_MIPI_RX1,
> +				 MAX96717_MIPI_RX1_CTRL_NUM_LANES,
> +				 FIELD_PREP(MAX96717_MIPI_RX1_CTRL_NUM_LANES,
> +					    num_data_lanes - 1));
> +	if (ret)
> +		return ret;
> +
> +	/* Configure lane mapping. */
> +	val = 0;
> +	for (i = 0; i < 4; i++) {
> +		unsigned int map;
> +
> +		if (i < num_data_lanes)
> +			map = phy->mipi.data_lanes[i] - 1;
> +		else
> +			map = ffz(used_data_lanes);
> +
> +		val |= map << (i * 2);
> +		used_data_lanes |= BIT(map);
> +	}
> +
> +	ret = regmap_update_bits(priv->regmap, MAX96717_MIPI_RX3,
> +				 MAX96717_MIPI_RX3_PHY2_LANE_MAP,
> +				 FIELD_PREP(MAX96717_MIPI_RX3_PHY2_LANE_MAP, val));
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_update_bits(priv->regmap, MAX96717_MIPI_RX2,
> +				 MAX96717_MIPI_RX2_PHY1_LANE_MAP,
> +				 FIELD_PREP(MAX96717_MIPI_RX2_PHY1_LANE_MAP, val >> 4));
> +	if (ret)
> +		return ret;
> +
> +	/* Configure lane polarity. */
> +	val = 0;
> +	for (i = 0; i < num_data_lanes; i++)
> +		if (phy->mipi.lane_polarities[i + 1])
> +			val |= BIT(i);
> +
> +	ret = regmap_update_bits(priv->regmap, MAX96717_MIPI_RX5,
> +				 MAX96717_MIPI_RX5_PHY2_POL_MAP,
> +				 FIELD_PREP(MAX96717_MIPI_RX5_PHY2_POL_MAP, val));
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_update_bits(priv->regmap, MAX96717_MIPI_RX4,
> +				 MAX96717_MIPI_RX4_PHY1_POL_MAP,
> +				 FIELD_PREP(MAX96717_MIPI_RX4_PHY1_POL_MAP, val >> 2));
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX96717_MIPI_RX5,
> +				 MAX96717_MIPI_RX5_PHY2_POL_MAP_CLK,
> +				 phy->mipi.lane_polarities[0]);
> +	if (ret)
> +		return ret;
> +
> +	if (priv->info->supports_noncontinuous_clock) {
> +		ret = regmap_assign_bits(priv->regmap, MAX96717_MIPI_RX0,
> +					 MAX96717_MIPI_RX0_NONCONTCLK_EN,
> +					 phy->mipi.flags &
> +					 V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max96717_set_phy_active(struct max_ser *ser, struct max_ser_phy *phy,
> +				   bool enable)
> +{
> +	struct max96717_priv *priv = ser_to_priv(ser);
> +	unsigned int index = max96717_phy_id(priv, phy);
> +
> +	return regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_0,
> +				  MAX96717_FRONTTOP_0_START_PORT(index), enable);
> +}
> +
> +static int max96717_set_pipe_stream_id(struct max_ser *ser,
> +				       struct max_ser_pipe *pipe,
> +				       unsigned int stream_id)
> +{
> +	struct max96717_priv *priv = ser_to_priv(ser);
> +	unsigned int index = max96717_pipe_id(priv, pipe);
> +
> +	return regmap_update_bits(priv->regmap, MAX96717_TX3(index),
> +				  MAX96717_TX3_TX_STR_SEL,
> +				  FIELD_PREP(MAX96717_TX3_TX_STR_SEL, stream_id));
> +}
> +
> +static int max96717_set_pipe_phy(struct max_ser *ser, struct max_ser_pipe *pipe,
> +				 struct max_ser_phy *phy)
> +{
> +	struct max96717_priv *priv = ser_to_priv(ser);
> +	unsigned int index = max96717_pipe_id(priv, pipe);
> +	unsigned int phy_id = max96717_phy_id(priv, phy);
> +	int ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_0,
> +				 MAX96717_FRONTTOP_0_CLK_SEL_P(index),
> +				 phy_id == 1);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_9,
> +				 MAX96717_FRONTTOP_9_START_PORT(index, 0),
> +				 phy_id == 0);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_9,
> +				 MAX96717_FRONTTOP_9_START_PORT(index, 1),
> +				 phy_id == 1);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int max96717_set_pipe_mode(struct max_ser *ser,
> +				  struct max_ser_pipe *pipe,
> +				  struct max_ser_pipe_mode *mode)
> +{
> +	struct max96717_priv *priv = ser_to_priv(ser);
> +	unsigned int index = max96717_pipe_id(priv, pipe);
> +	int ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX96717_VIDEO_TX0(index),
> +				 MAX96717_VIDEO_TX0_AUTO_BPP, !mode->bpp);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_update_bits(priv->regmap, MAX96717_VIDEO_TX1(index),
> +				 MAX96717_VIDEO_TX1_BPP,
> +				 FIELD_PREP(MAX96717_VIDEO_TX1_BPP, mode->bpp));
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX96717_VIDEO_TX2(index),
> +				 MAX96717_VIDEO_TX2_DRIFT_DET_EN, !mode->bpp);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_10,
> +				 MAX96717_FRONTTOP_10_BPP8DBL(index),
> +				 mode->dbl8);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_11,
> +				 MAX96717_FRONTTOP_11_BPP10DBL(index),
> +				 mode->dbl10);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_11,
> +				 MAX96717_FRONTTOP_11_BPP12DBL(index),
> +				 mode->dbl12);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_update_bits(priv->regmap, MAX96717_FRONTTOP_20(index),
> +				 MAX96717_FRONTTOP_20_SOFT_BPP |
> +				 MAX96717_FRONTTOP_20_SOFT_BPP_EN,
> +				 FIELD_PREP(MAX96717_FRONTTOP_20_SOFT_BPP,
> +					    mode->soft_bpp) |
> +				 FIELD_PREP(MAX96717_FRONTTOP_20_SOFT_BPP_EN,
> +					    !!mode->soft_bpp));
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int max96717_set_i2c_xlate(struct max_ser *ser, unsigned int i,
> +				  struct max_i2c_xlate *xlate)
> +{
> +	struct max96717_priv *priv = ser_to_priv(ser);
> +	int ret;
> +
> +	ret = regmap_update_bits(priv->regmap, MAX96717_I2C_2(i),
> +				 MAX96717_I2C_2_SRC,
> +				 FIELD_PREP(MAX96717_I2C_2_SRC, xlate->src));
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_update_bits(priv->regmap, MAX96717_I2C_3(i),
> +				 MAX96717_I2C_3_DST,
> +				 FIELD_PREP(MAX96717_I2C_3_DST, xlate->dst));
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int max96717_set_tunnel_enable(struct max_ser *ser, bool enable)
> +{
> +	struct max96717_priv *priv = ser_to_priv(ser);
> +
> +	return regmap_assign_bits(priv->regmap, MAX96717_EXT11,
> +				  MAX96717_EXT11_TUN_MODE, enable);
> +}
> +
> +static int max96717_set_tpg_timings(struct max96717_priv *priv,
> +				    const struct max_tpg_timings *tm,
> +				    unsigned int index)
> +{
> +	const struct reg_sequence regs[] = {
> +		REG_SEQUENCE_3(MAX96717_VTX2_VS_DLY_2(index), tm->vs_dly),
> +		REG_SEQUENCE_3(MAX96717_VTX5_VS_HIGH_2(index), tm->vs_high),
> +		REG_SEQUENCE_3(MAX96717_VTX8_VS_LOW_2(index), tm->vs_low),
> +		REG_SEQUENCE_3(MAX96717_VTX11_V2H_2(index), tm->v2h),
> +		REG_SEQUENCE_2(MAX96717_VTX14_HS_HIGH_1(index), tm->hs_high),
> +		REG_SEQUENCE_2(MAX96717_VTX16_HS_LOW_1(index), tm->hs_low),
> +		REG_SEQUENCE_2(MAX96717_VTX18_HS_CNT_1(index), tm->hs_cnt),
> +		REG_SEQUENCE_3(MAX96717_VTX20_V2D_2(index), tm->v2d),
> +		REG_SEQUENCE_2(MAX96717_VTX23_DE_HIGH_1(index), tm->de_high),
> +		REG_SEQUENCE_2(MAX96717_VTX25_DE_LOW_1(index), tm->de_low),
> +		REG_SEQUENCE_2(MAX96717_VTX27_DE_CNT_1(index), tm->de_cnt),
> +	};
> +	int ret;
> +
> +	ret = regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs));
> +	if (ret)
> +		return ret;
> +
> +	return regmap_write(priv->regmap, MAX96717_VTX0(index),
> +			    FIELD_PREP(MAX96717_VTX0_VTG_MODE,
> +				       MAX96717_VTX0_VTG_MODE_FREE_RUNNING) |
> +			    FIELD_PREP(MAX96717_VTX0_DE_INV, tm->de_inv) |
> +			    FIELD_PREP(MAX96717_VTX0_HS_INV, tm->hs_inv) |
> +			    FIELD_PREP(MAX96717_VTX0_VS_INV, tm->vs_inv) |
> +			    FIELD_PREP(MAX96717_VTX0_GEN_DE, tm->gen_de) |
> +			    FIELD_PREP(MAX96717_VTX0_GEN_HS, tm->gen_hs) |
> +			    FIELD_PREP(MAX96717_VTX0_GEN_VS, tm->gen_vs));
> +}
> +
> +static int max96717_set_tpg_clk(struct max96717_priv *priv,
> +				const struct videomode *vm, unsigned int index)
> +{
> +	u8 pclk_src;
> +
> +	if (!vm)
> +		return 0;
> +
> +	switch (vm->pixelclock) {
> +	case 25000000:
> +		pclk_src = MAX96717_VTX1_PATGEN_CLK_SRC_25MHZ;
> +		break;
> +	case 75000000:
> +		pclk_src = MAX96717_VTX1_PATGEN_CLK_SRC_75MHZ;
> +		break;
> +	case 150000000:
> +		pclk_src = MAX96717_VTX1_PATGEN_CLK_SRC_150MHZ;
> +		break;
> +	case 375000000:
> +		pclk_src = MAX96717_VTX1_PATGEN_CLK_SRC_375MHZ;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return regmap_update_bits(priv->regmap, MAX96717_VTX1(index),
> +				  MAX96717_VTX1_PATGEN_CLK_SRC,
> +				  FIELD_PREP(MAX96717_VTX1_PATGEN_CLK_SRC,
> +					     pclk_src));
> +}
> +
> +static int max96717_set_tpg(struct max_ser *ser, const struct max_tpg_entry *entry)
> +{
> +	struct max96717_priv *priv = ser_to_priv(ser);
> +	/*
> +	 * MAX9295A supports multiple pipes, each with a pattern generator,
> +	 * use only the first pipe for simplicity.
> +	 */
> +	unsigned int index = max96717_pipe_id(priv, &ser->pipes[0]);
> +	struct max_tpg_timings timings = { 0 };
> +	const struct videomode *vm = NULL;
> +	int ret;
> +
> +	if (entry) {
> +		vm = max_find_tpg_videomode(entry);
> +		if (!vm)
> +			return -EINVAL;
> +
> +		max_get_tpg_timings(vm, &timings);
> +	}
> +
> +	ret = max96717_set_tpg_timings(priv, &timings, index);
> +	if (ret)
> +		return ret;
> +
> +	ret = max96717_set_tpg_clk(priv, vm, index);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_update_bits(priv->regmap, MAX96717_VTX29(index),
> +				  MAX96717_VTX29_PATGEN_MODE,
> +				  FIELD_PREP(MAX96717_VTX29_PATGEN_MODE,
> +					     entry ? MAX96717_VTX29_PATGEN_MODE_GRADIENT
> +						   : MAX96717_VTX29_PATGEN_MODE_DISABLED));
> +}
> +
> +static const struct max_phys_config max96717_phys_configs[] = {
> +	{ { 4 } },
> +};
> +
> +static int max96717_init(struct max_ser *ser)
> +{
> +	struct max96717_priv *priv = ser_to_priv(ser);
> +	int ret;
> +
> +	/*
> +	 * Set CMU2 PFDDIV to 1.1V for correct functionality of the device,
> +	 * as mentioned in the datasheet, under section MANDATORY REGISTER PROGRAMMING.
> +	 */
> +	ret = regmap_update_bits(priv->regmap, MAX96717_CMU2,
> +				 MAX96717_CMU2_PFDDIV_RSHORT,
> +				 FIELD_PREP(MAX96717_CMU2_PFDDIV_RSHORT,
> +					    MAX96717_CMU2_PFDDIV_RSHORT_1_1V));
> +	if (ret)
> +		return ret;
> +
> +	if (ser->ops->set_tunnel_enable) {
> +		ret = ser->ops->set_tunnel_enable(ser, false);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct pinctrl_ops max96717_ctrl_ops = {
> +	.get_groups_count = max96717_ctrl_get_groups_count,
> +	.get_group_name = max96717_ctrl_get_group_name,
> +	.get_group_pins = max96717_ctrl_get_group_pins,
> +	.dt_node_to_map = pinconf_generic_dt_node_to_map_pin,
> +	.dt_free_map = pinconf_generic_dt_free_map,
> +};
> +
> +static const struct pinconf_ops max96717_conf_ops = {
> +	.pin_config_get = max96717_conf_pin_config_get,
> +	.pin_config_set = max96717_conf_pin_config_set,
> +	.is_generic = true,
> +};
> +
> +static const struct pinmux_ops max96717_mux_ops = {
> +	.get_functions_count = max96717_mux_get_functions_count,
> +	.get_function_name = max96717_mux_get_function_name,
> +	.get_function_groups = max96717_mux_get_groups,
> +	.set_mux = max96717_mux_set,
> +};
> +
> +static const struct max_tpg_entry max96717_tpg_entries[] = {
> +	MAX_TPG_ENTRY_640X480P60_RGB888,
> +	MAX_TPG_ENTRY_1920X1080P30_RGB888,
> +	MAX_TPG_ENTRY_1920X1080P60_RGB888,
> +};
> +
> +static const struct max_ser_ops max96717_ops = {
> +	.num_i2c_xlates = 2,
> +	.phys_configs = {
> +		.num_configs = ARRAY_SIZE(max96717_phys_configs),
> +		.configs = max96717_phys_configs,
> +	},
> +	.tpg_entries = {
> +		.num_entries = ARRAY_SIZE(max96717_tpg_entries),
> +		.entries = max96717_tpg_entries,
> +	},
> +	.tpg_mode = MAX_GMSL_PIXEL_MODE,
> +	.reg_read = max96717_reg_read,
> +	.reg_write = max96717_reg_write,
> +	.log_status = max96717_log_status,
> +	.log_pipe_status = max96717_log_pipe_status,
> +	.log_phy_status = max96717_log_phy_status,
> +	.init = max96717_init,
> +	.set_i2c_xlate = max96717_set_i2c_xlate,
> +	.set_tpg = max96717_set_tpg,
> +	.init_phy = max96717_init_phy,
> +	.set_phy_active = max96717_set_phy_active,
> +	.set_pipe_enable = max96717_set_pipe_enable,
> +	.set_pipe_dt = max96717_set_pipe_dt,
> +	.set_pipe_dt_en = max96717_set_pipe_dt_en,
> +	.set_pipe_vcs = max96717_set_pipe_vcs,
> +	.set_pipe_mode = max96717_set_pipe_mode,
> +	.set_pipe_stream_id = max96717_set_pipe_stream_id,
> +	.set_pipe_phy = max96717_set_pipe_phy,
> +};
> +
> +struct max96717_pll_predef_freq {
> +	unsigned long freq;
> +	bool is_alt;
> +	u8 val;
> +};
> +
> +static const struct max96717_pll_predef_freq max96717_predef_freqs[] = {
> +	{ 13500000, true,  0 }, { 19200000, false, 0 },
> +	{ 24000000, true,  1 }, { 27000000, false, 1 },
> +	{ 37125000, false, 2 }, { 74250000, false, 3 },
> +};
> +
> +static unsigned long
> +max96717_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
> +{
> +	struct max96717_priv *priv = clk_hw_to_priv(hw);
> +
> +	return max96717_predef_freqs[priv->pll_predef_index].freq;
> +}
> +
> +static unsigned int max96717_clk_find_best_index(struct max96717_priv *priv,
> +						 unsigned long rate)
> +{
> +	unsigned int i, idx = 0;
> +	unsigned long diff_new, diff_old = U32_MAX;
> +
> +	for (i = 0; i < ARRAY_SIZE(max96717_predef_freqs); i++) {
> +		diff_new = abs(rate - max96717_predef_freqs[i].freq);
> +		if (diff_new < diff_old) {
> +			diff_old = diff_new;
> +			idx = i;
> +		}
> +	}
> +
> +	return idx;
> +}
> +
> +static long max96717_clk_round_rate(struct clk_hw *hw, unsigned long rate,
> +				    unsigned long *parent_rate)
> +{
> +	struct max96717_priv *priv = clk_hw_to_priv(hw);
> +	struct device *dev = &priv->client->dev;
> +	unsigned int idx;
> +
> +	idx = max96717_clk_find_best_index(priv, rate);
> +
> +	if (rate != max96717_predef_freqs[idx].freq) {
> +		dev_warn(dev, "Request CLK freq:%lu, found CLK freq:%lu\n",
> +			 rate, max96717_predef_freqs[idx].freq);
> +	}
> +
> +	return max96717_predef_freqs[idx].freq;
> +}
> +
> +static int max96717_clk_set_rate(struct clk_hw *hw, unsigned long rate,
> +				 unsigned long parent_rate)
> +{
> +	struct max96717_priv *priv = clk_hw_to_priv(hw);
> +	unsigned int val, idx;
> +	int ret = 0;
> +
> +	idx = max96717_clk_find_best_index(priv, rate);
> +
> +	val = FIELD_PREP(MAX96717_REF_VTG0_REFGEN_PREDEF_FREQ,
> +			 max96717_predef_freqs[idx].val);
> +
> +	if (max96717_predef_freqs[idx].is_alt)
> +		val |= MAX96717_REF_VTG0_REFGEN_PREDEF_FREQ_ALT;
> +
> +	val |= MAX96717_REF_VTG0_REFGEN_RST | MAX96717_REF_VTG0_REFGEN_EN;
> +
> +	ret = regmap_write(priv->regmap, MAX96717_REF_VTG0, val);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_clear_bits(priv->regmap, MAX96717_REF_VTG0,
> +				MAX96717_REF_VTG0_REFGEN_RST);
> +	if (ret)
> +		return ret;
> +
> +	priv->pll_predef_index = idx;
> +
> +	return 0;
> +}
> +
> +static int max96717_clk_prepare(struct clk_hw *hw)
> +{
> +	struct max96717_priv *priv = clk_hw_to_priv(hw);
> +
> +	return regmap_set_bits(priv->regmap, MAX96717_REG6, MAX96717_REG6_RCLKEN);
> +}
> +
> +static void max96717_clk_unprepare(struct clk_hw *hw)
> +{
> +	struct max96717_priv *priv = clk_hw_to_priv(hw);
> +
> +	regmap_clear_bits(priv->regmap, MAX96717_REG6, MAX96717_REG6_RCLKEN);
> +}
> +
> +static const struct clk_ops max96717_clk_ops = {
> +	.prepare     = max96717_clk_prepare,
> +	.unprepare   = max96717_clk_unprepare,
> +	.set_rate    = max96717_clk_set_rate,
> +	.recalc_rate = max96717_clk_recalc_rate,
> +	.round_rate  = max96717_clk_round_rate,
> +};
> +
> +static int max96717_register_clkout(struct max96717_priv *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +	struct clk_init_data init = { .ops = &max96717_clk_ops };
> +	unsigned long config;
> +	int ret;
> +
> +	config = pinconf_to_config_packed(MAX96717_PINCTRL_RCLKOUT_CLK,
> +					  MAX96717_REG3_RCLKSEL_REFERENCE_PLL);
> +	ret = max96717_conf_pin_config_set_one(priv, 4, config);
> +	if (ret)
> +		return ret;
> +
> +	config = pinconf_to_config_packed(PIN_CONFIG_SLEW_RATE,
> +					  MAX96717_PIO_SLEW_FASTEST);
> +	ret = max96717_conf_pin_config_set_one(priv, 4, config);
> +	if (ret)
> +		return ret;
> +
> +	init.name = kasprintf(GFP_KERNEL, "max96717.%s.clk_out", dev_name(dev));
> +	if (!init.name)
> +		return -ENOMEM;
> +
> +	priv->clk_hw.init = &init;
> +
> +	ret = max96717_clk_set_rate(&priv->clk_hw,
> +				    MAX96717_DEFAULT_CLKOUT_RATE, 0);
> +	if (ret)
> +		goto free_init_name;
> +
> +	ret = devm_clk_hw_register(dev, &priv->clk_hw);
> +	kfree(init.name);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Cannot register clock HW\n");
> +
> +	ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
> +					  &priv->clk_hw);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "Cannot add OF clock provider\n");
> +
> +	return 0;
> +
> +free_init_name:
> +	kfree(init.name);
> +	return ret;
> +}
> +
> +static int max96717_gpiochip_probe(struct max96717_priv *priv)
> +{
> +	struct device *dev = priv->dev;
> +	int ret;
> +
> +	priv->pctldesc = (struct pinctrl_desc) {
> +		.owner = THIS_MODULE,
> +		.name = MAX96717_PINCTRL_NAME,
> +		.pins = max96717_pins,
> +		.npins = ARRAY_SIZE(max96717_pins),
> +		.pctlops = &max96717_ctrl_ops,
> +		.confops = &max96717_conf_ops,
> +		.pmxops = &max96717_mux_ops,
> +		.custom_params = max96717_cfg_params,
> +		.num_custom_params = ARRAY_SIZE(max96717_cfg_params),
> +	};
> +
> +	ret = devm_pinctrl_register_and_init(dev, &priv->pctldesc, priv, &priv->pctldev);
> +	if (ret)
> +		return ret;
> +
> +	ret = pinctrl_enable(priv->pctldev);
> +	if (ret)
> +		return ret;
> +
> +	priv->gc = (struct gpio_chip) {
> +		.owner = THIS_MODULE,
> +		.label = MAX96717_GPIOCHIP_NAME,
> +		.base = -1,
> +		.ngpio = MAX96717_GPIO_NUM,
> +		.parent = dev,
> +		.can_sleep = true,
> +		.request = gpiochip_generic_request,
> +		.free = gpiochip_generic_free,
> +		.set_config = gpiochip_generic_config,
> +		.get_direction = max96717_gpio_get_direction,
> +		.direction_input = max96717_gpio_direction_input,
> +		.direction_output = max96717_gpio_direction_output,
> +		.get = max96717_gpio_get,
> +		.set_rv = max96717_gpio_set,
> +	};
> +
> +	return devm_gpiochip_add_data(dev, &priv->gc, priv);
> +}
> +
> +static int max96717_probe(struct i2c_client *client)
> +{
> +	struct device *dev = &client->dev;
> +	struct max96717_priv *priv;
> +	struct max_ser_ops *ops;
> +	int ret;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	ops = devm_kzalloc(dev, sizeof(*ops), GFP_KERNEL);
> +	if (!ops)
> +		return -ENOMEM;
> +
> +	priv->info = device_get_match_data(dev);
> +	if (!priv->info) {
> +		dev_err(dev, "Failed to get match data\n");
> +		return -ENODEV;
> +	}
> +
> +	priv->dev = dev;
> +	priv->client = client;
> +	i2c_set_clientdata(client, priv);
> +
> +	priv->regmap = devm_regmap_init_i2c(client, &max96717_i2c_regmap);
> +	if (IS_ERR(priv->regmap))
> +		return PTR_ERR(priv->regmap);
> +
> +	*ops = max96717_ops;
> +
> +	if (priv->info->modes & BIT(MAX_GMSL_TUNNEL_MODE))
> +		ops->set_tunnel_enable = max96717_set_tunnel_enable;
> +
> +	ops->modes = priv->info->modes;
> +	ops->num_pipes = priv->info->num_pipes;
> +	ops->num_dts_per_pipe = priv->info->num_dts_per_pipe;
> +	ops->num_phys = priv->info->num_phys;
> +	priv->ser.ops = ops;
> +
> +	ret = max96717_wait_for_device(priv);
> +	if (ret)
> +		return ret;
> +
> +	ret = max96717_gpiochip_probe(priv);
> +	if (ret)
> +		return ret;
> +
> +	ret = max96717_register_clkout(priv);
> +	if (ret)
> +		return ret;
> +
> +	return max_ser_probe(client, &priv->ser);
> +}
> +
> +static void max96717_remove(struct i2c_client *client)
> +{
> +	struct max96717_priv *priv = i2c_get_clientdata(client);
> +
> +	max_ser_remove(&priv->ser);
> +}
> +
> +static const struct max96717_chip_info max9295a_info = {
> +	.modes = BIT(MAX_GMSL_PIXEL_MODE),
> +	.num_pipes = 4,
> +	.num_dts_per_pipe = 2,
> +	.pipe_hw_ids = { 0, 1, 2, 3 },
> +	.num_phys = 1,
> +	.phy_hw_ids = { 1 },
> +};
> +
> +static const struct max96717_chip_info max96717_info = {
> +	.modes = BIT(MAX_GMSL_PIXEL_MODE) | BIT(MAX_GMSL_TUNNEL_MODE),
> +	.supports_3_data_lanes = true,
> +	.supports_pkt_cnt = true,
> +	.supports_noncontinuous_clock = true,
> +	.num_pipes = 1,
> +	.num_dts_per_pipe = 4,
> +	.pipe_hw_ids = { 2 },
> +	.num_phys = 1,
> +	.phy_hw_ids = { 1 },
> +};
> +
> +static const struct of_device_id max96717_of_ids[] = {
> +	{ .compatible = "maxim,max9295a", .data = &max9295a_info },
> +	{ .compatible = "maxim,max96717", .data = &max96717_info },
> +	{ .compatible = "maxim,max96717f", .data = &max96717_info },
> +	{ .compatible = "maxim,max96793", .data = &max96717_info },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, max96717_of_ids);
> +
> +static struct i2c_driver max96717_i2c_driver = {
> +	.driver	= {
> +		.name = MAX96717_NAME,
> +		.of_match_table = max96717_of_ids,
> +	},
> +	.probe = max96717_probe,
> +	.remove = max96717_remove,
> +};
> +
> +module_i2c_driver(max96717_i2c_driver);
> +
> +MODULE_DESCRIPTION("MAX96717 GMSL2 Serializer Driver");
> +MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@...log.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/media/i2c/maxim-serdes/max96724.c b/drivers/media/i2c/maxim-serdes/max96724.c
> new file mode 100644
> index 000000000000..6737bde21c64
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max96724.c
> @@ -0,0 +1,1155 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Maxim MAX96724 Quad GMSL2 Deserializer Driver
> + *
> + * Copyright (C) 2025 Analog Devices Inc.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/of_graph.h>
> +#include <linux/regmap.h>
> +
> +#include "max_des.h"
> +
> +#define MAX96724_REG0				0x0
> +
> +#define MAX96724_REG6				0x6
> +#define MAX96724_REG6_LINK_EN			GENMASK(3, 0)
> +
> +#define MAX96724_DEBUG_EXTRA			0x9
> +#define MAX96724_DEBUG_EXTRA_PCLK_SRC		GENMASK(1, 0)
> +#define MAX96724_DEBUG_EXTRA_PCLK_SRC_25MHZ	0b00
> +#define MAX96724_DEBUG_EXTRA_PCLK_SRC_75MHZ	0b01
> +#define MAX96724_DEBUG_EXTRA_PCLK_SRC_USE_PIPE	0b10
> +
> +#define MAX96724_REG26(x)			(0x10 + (x) / 2)
> +#define MAX96724_REG26_RX_RATE_PHY(x)		(GENMASK(1, 0) << (4 * ((x) % 2)))
> +#define MAX96724_REG26_RX_RATE_3GBPS		0b01
> +#define MAX96724_REG26_RX_RATE_6GBPS		0b10
> +
> +#define MAX96724_PWR1				0x13
> +#define MAX96724_PWR1_RESET_ALL			BIT(6)
> +
> +#define MAX96724_CTRL1				0x18
> +#define MAX96724_CTRL1_RESET_ONESHOT		GENMASK(3, 0)
> +
> +#define MAX96724_VIDEO_PIPE_SEL(p)		(0xf0 + (p) / 2)
> +#define MAX96724_VIDEO_PIPE_SEL_STREAM(p)	(GENMASK(1, 0) << (4 * ((p) % 2)))
> +#define MAX96724_VIDEO_PIPE_SEL_LINK(p)		(GENMASK(3, 2) << (4 * ((p) % 2)))
> +
> +#define MAX96724_VIDEO_PIPE_EN			0xf4
> +#define MAX96724_VIDEO_PIPE_EN_MASK(p)		BIT(p)
> +#define MAX96724_VIDEO_PIPE_EN_STREAM_SEL_ALL	BIT(4)
> +
> +#define MAX96724_VPRBS(p)			(0x1dc + (p) * 0x20)
> +#define MAX96724_VPRBS_VIDEO_LOCK		BIT(0)
> +#define MAX96724_VPRBS_PATGEN_CLK_SRC		BIT(7)
> +#define MAX96724_VPRBS_PATGEN_CLK_SRC_150MHZ	0b0
> +#define MAX96724_VPRBS_PATGEN_CLK_SRC_375MHZ	0b1
> +
> +#define MAX96724_BACKTOP12			0x40b
> +#define MAX96724_BACKTOP12_CSI_OUT_EN		BIT(1)
> +
> +#define MAX96724_BACKTOP21(p)			(0x414 + (p) / 4 * 0x20)
> +#define MAX96724_BACKTOP21_BPP8DBL(p)		BIT(4 + (p) % 4)
> +
> +#define MAX96724_BACKTOP22(x)			(0x415 + (x) * 0x3)
> +#define MAX96724_BACKTOP22_PHY_CSI_TX_DPLL	GENMASK(4, 0)
> +#define MAX96724_BACKTOP22_PHY_CSI_TX_DPLL_EN	BIT(5)
> +
> +#define MAX96724_BACKTOP24(p)			(0x417 + (p) / 4 * 0x20)
> +#define MAX96724_BACKTOP24_BPP8DBL_MODE(p)	BIT(4 + (p) % 4)
> +
> +#define MAX96724_BACKTOP30(p)			(0x41d + (p) / 4 * 0x20)
> +#define MAX96724_BACKTOP30_BPP10DBL3		BIT(4)
> +#define MAX96724_BACKTOP30_BPP10DBL3_MODE	BIT(5)
> +
> +#define MAX96724_BACKTOP31(p)			(0x41e + (p) / 4 * 0x20)
> +#define MAX96724_BACKTOP31_BPP10DBL2		BIT(6)
> +#define MAX96724_BACKTOP31_BPP10DBL2_MODE	BIT(7)
> +
> +#define MAX96724_BACKTOP32(p)			(0x41f + (p) / 4 * 0x20)
> +#define MAX96724_BACKTOP32_BPP12(p)		BIT(p)
> +#define MAX96724_BACKTOP32_BPP10DBL0		BIT(4)
> +#define MAX96724_BACKTOP32_BPP10DBL0_MODE	BIT(5)
> +#define MAX96724_BACKTOP32_BPP10DBL1		BIT(6)
> +#define MAX96724_BACKTOP32_BPP10DBL1_MODE	BIT(7)
> +
> +#define MAX96724_MIPI_PHY0			0x8a0
> +#define MAX96724_MIPI_PHY0_PHY_CONFIG		GENMASK(4, 0)
> +#define MAX96724_MIPI_PHY0_PHY_4X2		BIT(0)
> +#define MAX96724_MIPI_PHY0_PHY_2X4		BIT(2)
> +#define MAX96724_MIPI_PHY0_PHY_1X4A_2X2		BIT(3)
> +#define MAX96724_MIPI_PHY0_PHY_1X4B_2X2		BIT(4)
> +#define MAX96724_MIPI_PHY0_FORCE_CSI_OUT_EN	BIT(7)
> +
> +#define MAX96724_MIPI_PHY2			0x8a2
> +#define MAX96724_MIPI_PHY2_PHY_STDB_N_4(x)	(GENMASK(5, 4) << ((x) / 2 * 2))
> +#define MAX96724_MIPI_PHY2_PHY_STDB_N_2(x)	(BIT(4 + (x)))
> +
> +#define MAX96724_MIPI_PHY3(x)			(0x8a3 + (x) / 2)
> +#define MAX96724_MIPI_PHY3_PHY_LANE_MAP_4	GENMASK(7, 0)
> +#define MAX96724_MIPI_PHY3_PHY_LANE_MAP_2(x)	(GENMASK(3, 0) << (4 * ((x) % 2)))
> +
> +#define MAX96724_MIPI_PHY5(x)			(0x8a5 + (x) / 2)
> +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_4_0_1	GENMASK(1, 0)
> +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_4_2_3	GENMASK(4, 3)
> +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_4_CLK	BIT(5)
> +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_2(x)	(GENMASK(1, 0) << (3 * ((x) % 2)))
> +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_2_CLK(x)	BIT(2 + 3 * ((x) % 2))
> +
> +#define MAX96724_MIPI_PHY13			0x8ad
> +#define MAX96724_MIPI_PHY13_T_T3_PREBEGIN	GENMASK(5, 0)
> +#define MAX96724_MIPI_PHY13_T_T3_PREBEGIN_64X7	FIELD_PREP(MAX96724_MIPI_PHY13_T_T3_PREBEGIN, 63)
> +
> +#define MAX96724_MIPI_PHY14			0x8ae
> +#define MAX96724_MIPI_PHY14_T_T3_PREP		GENMASK(1, 0)
> +#define MAX96724_MIPI_PHY14_T_T3_PREP_55NS	FIELD_PREP(MAX96724_MIPI_PHY14_T_T3_PREP, 0b01)
> +#define MAX96724_MIPI_PHY14_T_T3_POST		GENMASK(6, 2)
> +#define MAX96724_MIPI_PHY14_T_T3_POST_32X7	FIELD_PREP(MAX96724_MIPI_PHY14_T_T3_POST, 31)
> +
> +#define MAX96724_MIPI_CTRL_SEL			0x8ca
> +#define MAX96724_MIPI_CTRL_SEL_MASK(p)		(GENMASK(1, 0) << ((p) * 2))
> +
> +#define MAX96724_MIPI_PHY25(x)			(0x8d0 + (x) / 2)
> +#define MAX96724_MIPI_PHY25_CSI2_TX_PKT_CNT(x)	(GENMASK(3, 0) << (4 * ((x) % 2)))
> +
> +#define MAX96724_MIPI_PHY27(x)			(0x8d2 + (x) / 2)
> +#define MAX96724_MIPI_PHY27_PHY_PKT_CNT(x)	(GENMASK(3, 0) << (4 * ((x) % 2)))
> +
> +#define MAX96724_MIPI_TX3(x)			(0x903 + (x) * 0x40)
> +#define MAX96724_MIPI_TX3_DESKEW_INIT_8X32K	FIELD_PREP(GENMASK(2, 0), 0b001)
> +#define MAX96724_MIPI_TX3_DESKEW_INIT_AUTO	BIT(7)
> +
> +#define MAX96724_MIPI_TX4(x)			(0x904 + (x) * 0x40)
> +#define MAX96724_MIPI_TX4_DESKEW_PER_2K		FIELD_PREP(GENMASK(2, 0), 0b001)
> +#define MAX96724_MIPI_TX4_DESKEW_PER_AUTO	BIT(7)
> +
> +#define MAX96724_MIPI_TX10(x)			(0x90a + (x) * 0x40)
> +#define MAX96724_MIPI_TX10_CSI2_CPHY_EN		BIT(5)
> +#define MAX96724_MIPI_TX10_CSI2_LANE_CNT	GENMASK(7, 6)
> +
> +#define MAX96724_MIPI_TX11(p)			(0x90b + (p) * 0x40)
> +#define MAX96724_MIPI_TX12(p)			(0x90c + (p) * 0x40)
> +
> +#define MAX96724_MIPI_TX13(p, x)		(0x90d + (p) * 0x40 + (x) * 0x2)
> +#define MAX96724_MIPI_TX13_MAP_SRC_DT		GENMASK(5, 0)
> +#define MAX96724_MIPI_TX13_MAP_SRC_VC		GENMASK(7, 6)
> +
> +#define MAX96724_MIPI_TX14(p, x)		(0x90e + (p) * 0x40 + (x) * 0x2)
> +#define MAX96724_MIPI_TX14_MAP_DST_DT		GENMASK(5, 0)
> +#define MAX96724_MIPI_TX14_MAP_DST_VC		GENMASK(7, 6)
> +
> +#define MAX96724_MIPI_TX45(p, x)		(0x92d + (p) * 0x40 + (x) / 4)
> +#define MAX96724_MIPI_TX45_MAP_DPHY_DEST(x)	(GENMASK(1, 0) << (2 * ((x) % 4)))
> +
> +#define MAX96724_MIPI_TX51(x)			(0x933 + (x) * 0x40)
> +#define MAX96724_MIPI_TX51_ALT_MEM_MAP_12	BIT(0)
> +#define MAX96724_MIPI_TX51_ALT_MEM_MAP_8	BIT(1)
> +#define MAX96724_MIPI_TX51_ALT_MEM_MAP_10	BIT(2)
> +#define MAX96724_MIPI_TX51_ALT2_MEM_MAP_8	BIT(4)
> +
> +#define MAX96724_MIPI_TX54(x)			(0x936 + (x) * 0x40)
> +#define MAX96724_MIPI_TX54_TUN_EN		BIT(0)
> +
> +#define MAX96724_MIPI_TX57(x)			(0x939 + (x) * 0x40)
> +#define MAX96724_MIPI_TX57_TUN_DEST		GENMASK(5, 4)
> +#define MAX96724_MIPI_TX57_DIS_AUTO_TUN_DET	BIT(6)
> +#define MAX96724_DET(p)				BIT(p)
> +
> +#define MAX96724_PATGEN_0			0x1050
> +#define MAX96724_PATGEN_0_VTG_MODE		GENMASK(1, 0)
> +#define MAX96724_PATGEN_0_VTG_MODE_FREE_RUNNING	0b11
> +#define MAX96724_PATGEN_0_DE_INV		BIT(2)
> +#define MAX96724_PATGEN_0_HS_INV		BIT(3)
> +#define MAX96724_PATGEN_0_VS_INV		BIT(4)
> +#define MAX96724_PATGEN_0_GEN_DE		BIT(5)
> +#define MAX96724_PATGEN_0_GEN_HS		BIT(6)
> +#define MAX96724_PATGEN_0_GEN_VS		BIT(7)
> +
> +#define MAX96724_PATGEN_1			0x1051
> +#define MAX96724_PATGEN_1_PATGEN_MODE		GENMASK(5, 4)
> +#define MAX96724_PATGEN_1_PATGEN_MODE_DISABLED	0b00
> +#define MAX96724_PATGEN_1_PATGEN_MODE_GRADIENT	0b10
> +
> +#define MAX96724_VS_DLY_2			0x1052
> +#define MAX96724_VS_HIGH_2			0x1055
> +#define MAX96724_VS_LOW_2			0x1058
> +#define MAX96724_V2H_2				0x105b
> +#define MAX96724_HS_HIGH_1			0x105e
> +#define MAX96724_HS_LOW_1			0x1060
> +#define MAX96724_HS_CNT_1			0x1062
> +#define MAX96724_V2D_2				0x1064
> +#define MAX96724_DE_HIGH_1			0x1067
> +#define MAX96724_DE_LOW_1			0x1069
> +#define MAX96724_DE_CNT_1			0x106b
> +#define MAX96724_GRAD_INCR			0x106d
> +
> +#define MAX96724_DE_DET				0x11f0
> +#define MAX96724_HS_DET				0x11f1
> +#define MAX96724_VS_DET				0x11f2
> +#define MAX96724_HS_POL				0x11f3
> +#define MAX96724_VS_POL				0x11f4
> +#define MAX96724_DET(p)				BIT(p)
> +
> +#define MAX96724_DPLL_0(x)			(0x1c00 + (x) * 0x100)
> +#define MAX96724_DPLL_0_CONFIG_SOFT_RST_N	BIT(0)
> +
> +#define MAX96724_PHY1_ALT_CLOCK			5
> +
> +static const struct regmap_config max96724_i2c_regmap = {
> +	.reg_bits = 16,
> +	.val_bits = 8,
> +	.max_register = 0x1f00,
> +};
> +
> +struct max96724_priv {
> +	struct max_des des;
> +	const struct max96724_chip_info *info;
> +
> +	struct device *dev;
> +	struct i2c_client *client;
> +	struct regmap *regmap;
> +
> +	struct gpio_desc *gpiod_enable;
> +};
> +
> +struct max96724_chip_info {
> +	unsigned int versions;
> +	unsigned int modes;
> +	bool supports_pipe_stream_autoselect;
> +	unsigned int num_pipes;
> +
> +	int (*set_pipe_phy)(struct max_des *des, struct max_des_pipe *pipe,
> +			    struct max_des_phy *phy);
> +	int (*set_pipe_tunnel_phy)(struct max_des *des, struct max_des_pipe *pipe,
> +				   struct max_des_phy *phy);
> +	int (*set_pipe_tunnel_enable)(struct max_des *des, struct max_des_pipe *pipe,
> +				      bool enable);
> +};
> +
> +#define des_to_priv(_des) \
> +	container_of(_des, struct max96724_priv, des)
> +
> +static int max96724_wait_for_device(struct max96724_priv *priv)
> +{
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < 10; i++) {
> +		unsigned int val;
> +
> +		ret = regmap_read(priv->regmap, MAX96724_REG0, &val);
> +		if (!ret && val)
> +			return 0;
> +
> +		msleep(100);
> +
> +		dev_err(priv->dev, "Retry %u waiting for deserializer: %d\n", i, ret);
> +	}
> +
> +	return ret;
> +}
> +
> +static int max96724_reset(struct max96724_priv *priv)
> +{
> +	int ret;
> +
> +	ret = max96724_wait_for_device(priv);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_update_bits(priv->regmap, MAX96724_PWR1,
> +				 MAX96724_PWR1_RESET_ALL,
> +				 FIELD_PREP(MAX96724_PWR1_RESET_ALL, 1));
> +	if (ret)
> +		return ret;
> +
> +	fsleep(10000);
> +
> +	ret = max96724_wait_for_device(priv);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int max96724_reg_read(struct max_des *des, unsigned int reg,
> +			     unsigned int *val)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +
> +	return regmap_read(priv->regmap, reg, val);
> +}
> +
> +static int max96724_reg_write(struct max_des *des, unsigned int reg,
> +			      unsigned int val)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +
> +	return regmap_write(priv->regmap, reg, val);
> +}
> +
> +static unsigned int max96724_phy_id(struct max_des *des, struct max_des_phy *phy)
> +{
> +	unsigned int num_hw_data_lanes = max_des_phy_hw_data_lanes(des, phy);
> +
> +	/* PHY 1 is the master PHY when combining PHY 0 and PHY 1. */
> +	if (phy->index == 0 && num_hw_data_lanes == 4)
> +		return 1;
> +
> +	if (phy->index == 1 && !des->phys[1].enabled)
> +		return 0;
> +
> +	return phy->index;
> +}
> +
> +static int max96724_log_pipe_status(struct max_des *des,
> +				    struct max_des_pipe *pipe, const char *name)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +	unsigned int index = pipe->index;
> +	unsigned int val, mask;
> +	int ret;
> +
> +	ret = regmap_read(priv->regmap, MAX96724_VPRBS(index), &val);
> +	if (ret)
> +		return ret;
> +
> +	pr_info("%s: \tvideo_lock: %u\n", name,
> +		!!(val & MAX96724_VPRBS_VIDEO_LOCK));
> +
> +	mask = MAX96724_DET(index);
> +
> +	ret = regmap_read(priv->regmap, MAX96724_DE_DET, &val);
> +	if (ret)
> +		return ret;
> +
> +	pr_info("%s: \tde_det: %u\n", name, !!(val & mask));
> +
> +	ret = regmap_read(priv->regmap, MAX96724_HS_DET, &val);
> +	if (ret)
> +		return ret;
> +
> +	pr_info("%s: \ths_det: %u\n", name, !!(val & mask));
> +
> +	ret = regmap_read(priv->regmap, MAX96724_VS_DET, &val);
> +	if (ret)
> +		return ret;
> +
> +	pr_info("%s: \tvs_det: %u\n", name, !!(val & mask));
> +
> +	ret = regmap_read(priv->regmap, MAX96724_HS_POL, &val);
> +	if (ret)
> +		return ret;
> +
> +	pr_info("%s: \ths_pol: %u\n", name, !!(val & mask));
> +
> +	ret = regmap_read(priv->regmap, MAX96724_VS_POL, &val);
> +	if (ret)
> +		return ret;
> +
> +	pr_info("%s: \tvs_pol: %u\n", name, !!(val & mask));
> +
> +	return 0;
> +}
> +
> +static int max96724_log_phy_status(struct max_des *des,
> +				   struct max_des_phy *phy, const char *name)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +	unsigned int index = max96724_phy_id(des, phy);
> +	unsigned int val;
> +	int ret;
> +
> +	ret = regmap_read(priv->regmap, MAX96724_MIPI_PHY25(index), &val);
> +	if (ret)
> +		return ret;
> +
> +	pr_info("%s: \tcsi2_pkt_cnt: %lu\n", name,
> +		field_get(MAX96724_MIPI_PHY25_CSI2_TX_PKT_CNT(index), val));
> +
> +	ret = regmap_read(priv->regmap, MAX96724_MIPI_PHY27(index), &val);
> +	if (ret)
> +		return ret;
> +
> +	pr_info("%s: \tphy_pkt_cnt: %lu\n", name,
> +		field_get(MAX96724_MIPI_PHY27_PHY_PKT_CNT(index), val));
> +
> +	return 0;
> +}
> +
> +static int max96724_set_enable(struct max_des *des, bool enable)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +
> +	return regmap_assign_bits(priv->regmap, MAX96724_BACKTOP12,
> +				  MAX96724_BACKTOP12_CSI_OUT_EN, enable);
> +}
> +
> +static const unsigned int max96724_phys_configs_reg_val[] = {
> +	MAX96724_MIPI_PHY0_PHY_1X4A_2X2,
> +	MAX96724_MIPI_PHY0_PHY_2X4,
> +
> +	MAX96724_MIPI_PHY0_PHY_4X2,
> +	MAX96724_MIPI_PHY0_PHY_1X4A_2X2,
> +	MAX96724_MIPI_PHY0_PHY_1X4B_2X2,
> +	MAX96724_MIPI_PHY0_PHY_2X4,
> +};
> +
> +static const struct max_phys_config max96724_phys_configs[] = {
> +	/*
> +	 * PHY 1 can be in 4-lane mode (combining lanes of PHY 0 and PHY 1)
> +	 * but only use the data lanes of PHY0, while continuing to use the
> +	 * clock lane of PHY 1.
> +	 * Specifying clock-lanes as 5 turns on alternate clocking mode.
> +	 */
> +	{ { 2, 0, 2, 2 }, { MAX96724_PHY1_ALT_CLOCK, 0, 0, 0 } },
> +	{ { 2, 0, 4, 0 }, { MAX96724_PHY1_ALT_CLOCK, 0, 0, 0 } },
> +
> +	/*
> +	 * When combining PHY 0 and PHY 1 to make them function in 4-lane mode,
> +	 * PHY 1 is the master PHY, but we use PHY 0 here to maintain
> +	 * compatibility.
> +	 */
> +	{ { 2, 2, 2, 2 } },
> +	{ { 4, 0, 2, 2 } },
> +	{ { 2, 2, 4, 0 } },
> +	{ { 4, 0, 4, 0 } },
> +};
> +
> +static int max96724_init(struct max_des *des)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +	unsigned int i;
> +	int ret;
> +
> +	if (priv->info->set_pipe_tunnel_enable) {
> +		for (i = 0; i < des->ops->num_pipes; i++) {
> +			ret = regmap_set_bits(priv->regmap, MAX96724_MIPI_TX57(i),
> +					      MAX96724_MIPI_TX57_DIS_AUTO_TUN_DET);
> +			if (ret)
> +				return ret;
> +		}
> +	}
> +
> +	if (priv->info->supports_pipe_stream_autoselect) {
> +		/* Enable stream autoselect. */
> +		ret = regmap_set_bits(priv->regmap, MAX96724_VIDEO_PIPE_EN,
> +				      MAX96724_VIDEO_PIPE_EN_STREAM_SEL_ALL);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	/* Set PHY mode. */
> +	ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_PHY0,
> +				 MAX96724_MIPI_PHY0_PHY_CONFIG,
> +				 max96724_phys_configs_reg_val[des->phys_config]);
> +	if (ret)
> +		return ret;
> +
> +	/* Set TPG gradient increase. */
> +	ret = regmap_write(priv->regmap, MAX96724_GRAD_INCR, 0x4);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int max96724_init_phy(struct max_des *des, struct max_des_phy *phy)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +	bool is_cphy = phy->bus_type == V4L2_MBUS_CSI2_CPHY;
> +	unsigned int num_data_lanes = phy->mipi.num_data_lanes;
> +	unsigned int dpll_freq = phy->link_frequency * 2;
> +	unsigned int num_hw_data_lanes;
> +	unsigned int index;
> +	unsigned int used_data_lanes = 0;
> +	unsigned int val, mask;
> +	unsigned int i;
> +	int ret;
> +
> +	index = max96724_phy_id(des, phy);
> +	num_hw_data_lanes = max_des_phy_hw_data_lanes(des, phy);
> +
> +	ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_TX10(index),
> +				 MAX96724_MIPI_TX10_CSI2_LANE_CNT,
> +				 FIELD_PREP(MAX96724_MIPI_TX10_CSI2_LANE_CNT,
> +					    num_data_lanes - 1));
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX10(index),
> +				 MAX96724_MIPI_TX10_CSI2_CPHY_EN, is_cphy);
> +	if (ret)
> +		return ret;
> +
> +	/* Configure lane mapping. */
> +	val = 0;
> +	for (i = 0; i < num_hw_data_lanes ; i++) {
> +		unsigned int map;
> +
> +		if (i < num_data_lanes)
> +			map = phy->mipi.data_lanes[i] - 1;
> +		else
> +			map = ffz(used_data_lanes);
> +
> +		val |= map << (i * 2);
> +		used_data_lanes |= BIT(map);
> +	}
> +
> +	if (num_hw_data_lanes == 4)
> +		mask = MAX96724_MIPI_PHY3_PHY_LANE_MAP_4;
> +	else
> +		mask = MAX96724_MIPI_PHY3_PHY_LANE_MAP_2(index);
> +
> +	ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_PHY3(index),
> +				 mask, field_prep(mask, val));
> +	if (ret)
> +		return ret;
> +
> +	/* Configure lane polarity. */
> +	val = 0;
> +	for (i = 0; i < num_data_lanes; i++)
> +		if (phy->mipi.lane_polarities[i + 1])
> +			val |= BIT(i);
> +
> +	if (num_hw_data_lanes == 4) {
> +		ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_PHY5(index),
> +					 MAX96724_MIPI_PHY5_PHY_POL_MAP_4_0_1 |
> +					 MAX96724_MIPI_PHY5_PHY_POL_MAP_4_2_3,
> +					 FIELD_PREP(MAX96724_MIPI_PHY5_PHY_POL_MAP_4_0_1,
> +						    val) |
> +					 FIELD_PREP(MAX96724_MIPI_PHY5_PHY_POL_MAP_4_2_3,
> +						    val >> 2));
> +		if (ret)
> +			return ret;
> +
> +		ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_PHY5(index),
> +					 MAX96724_MIPI_PHY5_PHY_POL_MAP_4_CLK,
> +					 phy->mipi.lane_polarities[0]);
> +		if (ret)
> +			return ret;
> +	} else {
> +		ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_PHY5(index),
> +					 MAX96724_MIPI_PHY5_PHY_POL_MAP_2(index),
> +					 field_prep(MAX96724_MIPI_PHY5_PHY_POL_MAP_2(index), val));
> +		if (ret)
> +			return ret;
> +
> +		ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_PHY5(index),
> +					 MAX96724_MIPI_PHY5_PHY_POL_MAP_2_CLK(index),
> +					 phy->mipi.lane_polarities[0]);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (!is_cphy && dpll_freq > 1500000000ull) {
> +		/* Enable initial deskew with 2 x 32k UI. */
> +		ret = regmap_write(priv->regmap, MAX96724_MIPI_TX3(index),
> +				   MAX96724_MIPI_TX3_DESKEW_INIT_AUTO |
> +				   MAX96724_MIPI_TX3_DESKEW_INIT_8X32K);
> +		if (ret)
> +			return ret;
> +
> +		/* Enable periodic deskew with 2 x 1k UI.. */
> +		ret = regmap_write(priv->regmap, MAX96724_MIPI_TX4(index),
> +				   MAX96724_MIPI_TX4_DESKEW_PER_AUTO |
> +				   MAX96724_MIPI_TX4_DESKEW_PER_2K);
> +		if (ret)
> +			return ret;
> +	} else {
> +		/* Disable initial deskew. */
> +		ret = regmap_write(priv->regmap, MAX96724_MIPI_TX3(index), 0x0);
> +		if (ret)
> +			return ret;
> +
> +		/* Disable periodic deskew. */
> +		ret = regmap_write(priv->regmap, MAX96724_MIPI_TX4(index), 0x0);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (is_cphy) {
> +		/* Configure C-PHY timings. */
> +		ret = regmap_write(priv->regmap, MAX96724_MIPI_PHY13,
> +				   MAX96724_MIPI_PHY13_T_T3_PREBEGIN_64X7);
> +		if (ret)
> +			return ret;
> +
> +		ret = regmap_write(priv->regmap, MAX96724_MIPI_PHY14,
> +				   MAX96724_MIPI_PHY14_T_T3_PREP_55NS |
> +				   MAX96724_MIPI_PHY14_T_T3_POST_32X7);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	/* Put DPLL block into reset. */
> +	ret = regmap_clear_bits(priv->regmap, MAX96724_DPLL_0(index),
> +				MAX96724_DPLL_0_CONFIG_SOFT_RST_N);
> +	if (ret)
> +		return ret;
> +
> +	/* Set DPLL frequency. */
> +	ret = regmap_update_bits(priv->regmap, MAX96724_BACKTOP22(index),
> +				 MAX96724_BACKTOP22_PHY_CSI_TX_DPLL,
> +				 FIELD_PREP(MAX96724_BACKTOP22_PHY_CSI_TX_DPLL,
> +					    div_u64(dpll_freq, 100000000)));
> +	if (ret)
> +		return ret;
> +
> +	/* Enable DPLL frequency. */
> +	ret = regmap_set_bits(priv->regmap, MAX96724_BACKTOP22(index),
> +			      MAX96724_BACKTOP22_PHY_CSI_TX_DPLL_EN);
> +	if (ret)
> +		return ret;
> +
> +	/* Pull DPLL block out of reset. */
> +	ret = regmap_set_bits(priv->regmap, MAX96724_DPLL_0(index),
> +			      MAX96724_DPLL_0_CONFIG_SOFT_RST_N);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int max96724_set_phy_mode(struct max_des *des, struct max_des_phy *phy,
> +				 struct max_des_phy_mode *mode)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +	unsigned int index = max96724_phy_id(des, phy);
> +	int ret;
> +
> +	/* Set alternate memory map modes. */
> +	ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX51(index),
> +				 MAX96724_MIPI_TX51_ALT_MEM_MAP_12,
> +				 mode->alt_mem_map12);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX51(index),
> +				 MAX96724_MIPI_TX51_ALT_MEM_MAP_8,
> +				 mode->alt_mem_map8);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX51(index),
> +				 MAX96724_MIPI_TX51_ALT_MEM_MAP_10,
> +				 mode->alt_mem_map10);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX51(index),
> +				 MAX96724_MIPI_TX51_ALT2_MEM_MAP_8,
> +				 mode->alt2_mem_map8);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int max96724_set_phy_active(struct max_des *des, struct max_des_phy *phy,
> +				   bool enable)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +	unsigned int index = max96724_phy_id(des, phy);
> +	unsigned int num_hw_data_lanes;
> +	unsigned int mask;
> +
> +	num_hw_data_lanes = max_des_phy_hw_data_lanes(des, phy);
> +
> +	if (num_hw_data_lanes == 4)
> +		/* PHY 1 -> bits [1:0] */
> +		/* PHY 2 -> bits [3:2] */
> +		mask = MAX96724_MIPI_PHY2_PHY_STDB_N_4(index);
> +	else
> +		mask = MAX96724_MIPI_PHY2_PHY_STDB_N_2(index);
> +
> +	return regmap_assign_bits(priv->regmap, MAX96724_MIPI_PHY2, mask, enable);
> +}
> +
> +static int max96724_set_pipe_remap(struct max_des *des,
> +				   struct max_des_pipe *pipe,
> +				   unsigned int i,
> +				   struct max_des_remap *remap)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +	struct max_des_phy *phy = &des->phys[remap->phy];
> +	unsigned int phy_id = max96724_phy_id(des, phy);
> +	unsigned int index = pipe->index;
> +	int ret;
> +
> +	/* Set source Data Type and Virtual Channel. */
> +	/* TODO: implement extended Virtual Channel. */
> +	ret = regmap_write(priv->regmap, MAX96724_MIPI_TX13(index, i),
> +			   FIELD_PREP(MAX96724_MIPI_TX13_MAP_SRC_DT,
> +				      remap->from_dt) |
> +			   FIELD_PREP(MAX96724_MIPI_TX13_MAP_SRC_VC,
> +				      remap->from_vc));
> +	if (ret)
> +		return ret;
> +
> +	/* Set destination Data Type and Virtual Channel. */
> +	/* TODO: implement extended Virtual Channel. */
> +	ret = regmap_write(priv->regmap, MAX96724_MIPI_TX14(index, i),
> +			   FIELD_PREP(MAX96724_MIPI_TX14_MAP_DST_DT,
> +				      remap->to_dt) |
> +			   FIELD_PREP(MAX96724_MIPI_TX14_MAP_DST_VC,
> +				      remap->to_vc));
> +	if (ret)
> +		return ret;
> +
> +	/* Set destination PHY. */
> +	return regmap_update_bits(priv->regmap, MAX96724_MIPI_TX45(index, i),
> +				  MAX96724_MIPI_TX45_MAP_DPHY_DEST(i),
> +				  field_prep(MAX96724_MIPI_TX45_MAP_DPHY_DEST(i),
> +					     phy_id));
> +}
> +
> +static int max96724_set_pipe_remaps_enable(struct max_des *des,
> +					   struct max_des_pipe *pipe,
> +					   unsigned int mask)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +	unsigned int index = pipe->index;
> +	int ret;
> +
> +	ret = regmap_write(priv->regmap, MAX96724_MIPI_TX11(index), mask);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_write(priv->regmap, MAX96724_MIPI_TX12(index), mask >> 8);
> +}
> +
> +static int max96724_set_pipe_tunnel_phy(struct max_des *des,
> +					struct max_des_pipe *pipe,
> +					struct max_des_phy *phy)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +	unsigned int phy_index = max96724_phy_id(des, phy);
> +
> +	return regmap_update_bits(priv->regmap, MAX96724_MIPI_TX57(pipe->index),
> +				  MAX96724_MIPI_TX57_TUN_DEST,
> +				  FIELD_PREP(MAX96724_MIPI_TX57_TUN_DEST,
> +					     phy_index));
> +}
> +
> +static int max96724_set_pipe_phy(struct max_des *des, struct max_des_pipe *pipe,
> +				 struct max_des_phy *phy)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +	unsigned int phy_index = max96724_phy_id(des, phy);
> +
> +	return regmap_update_bits(priv->regmap, MAX96724_MIPI_CTRL_SEL,
> +				  MAX96724_MIPI_CTRL_SEL_MASK(pipe->index),
> +				  field_prep(MAX96724_MIPI_CTRL_SEL_MASK(pipe->index),
> +					     phy_index));
> +}
> +
> +static int max96724_set_pipe_enable(struct max_des *des, struct max_des_pipe *pipe,
> +				    bool enable)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +	unsigned int index = pipe->index;
> +
> +	return regmap_assign_bits(priv->regmap, MAX96724_VIDEO_PIPE_EN,
> +				  MAX96724_VIDEO_PIPE_EN_MASK(index), enable);
> +}
> +
> +static int max96724_set_pipe_stream_id(struct max_des *des, struct max_des_pipe *pipe,
> +				       unsigned int stream_id)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +	unsigned int index = pipe->index;
> +
> +	return regmap_update_bits(priv->regmap, MAX96724_VIDEO_PIPE_SEL(index),
> +				  MAX96724_VIDEO_PIPE_SEL_STREAM(index),
> +				  field_prep(MAX96724_VIDEO_PIPE_SEL_STREAM(index),
> +					     stream_id));
> +}
> +
> +static int max96724_set_pipe_link(struct max_des *des, struct max_des_pipe *pipe,
> +				  struct max_des_link *link)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +	unsigned int index = pipe->index;
> +
> +	return regmap_update_bits(priv->regmap, MAX96724_VIDEO_PIPE_SEL(index),
> +				  MAX96724_VIDEO_PIPE_SEL_LINK(index),
> +				  field_prep(MAX96724_VIDEO_PIPE_SEL_LINK(index),
> +					     link->index));
> +}
> +
> +static int max96724_set_pipe_mode(struct max_des *des,
> +				  struct max_des_pipe *pipe,
> +				  struct max_des_pipe_mode *mode)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +	unsigned int index = pipe->index;
> +	unsigned int reg, mask, mode_mask;
> +	int ret;
> +
> +	/* Set 8bit double mode. */
> +	ret = regmap_assign_bits(priv->regmap, MAX96724_BACKTOP21(index),
> +				 MAX96724_BACKTOP21_BPP8DBL(index), mode->dbl8);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, MAX96724_BACKTOP24(index),
> +				 MAX96724_BACKTOP24_BPP8DBL_MODE(index),
> +				 mode->dbl8mode);
> +	if (ret)
> +		return ret;
> +
> +	/* Set 10bit double mode. */
> +	if (index % 4 == 3) {
> +		reg = MAX96724_BACKTOP30(index);
> +		mask = MAX96724_BACKTOP30_BPP10DBL3;
> +		mode_mask = MAX96724_BACKTOP30_BPP10DBL3_MODE;
> +	} else if (index % 4 == 2) {
> +		reg = MAX96724_BACKTOP31(index);
> +		mask = MAX96724_BACKTOP31_BPP10DBL2;
> +		mode_mask = MAX96724_BACKTOP31_BPP10DBL2_MODE;
> +	} else if (index % 4 == 1) {
> +		reg = MAX96724_BACKTOP32(index);
> +		mask = MAX96724_BACKTOP32_BPP10DBL1;
> +		mode_mask = MAX96724_BACKTOP32_BPP10DBL1_MODE;
> +	} else {
> +		reg = MAX96724_BACKTOP32(index);
> +		mask = MAX96724_BACKTOP32_BPP10DBL0;
> +		mode_mask = MAX96724_BACKTOP32_BPP10DBL0_MODE;
> +	}
> +
> +	ret = regmap_assign_bits(priv->regmap, reg, mask, mode->dbl10);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_assign_bits(priv->regmap, reg, mode_mask, mode->dbl10mode);
> +	if (ret)
> +		return ret;
> +
> +	/* Set 12bit double mode. */
> +	return regmap_assign_bits(priv->regmap, MAX96724_BACKTOP32(index),
> +				  MAX96724_BACKTOP32_BPP12(index), mode->dbl12);
> +}
> +
> +static int max96724_set_pipe_tunnel_enable(struct max_des *des,
> +					   struct max_des_pipe *pipe, bool enable)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +
> +	return regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX54(pipe->index),
> +				  MAX96724_MIPI_TX54_TUN_EN, enable);
> +}
> +
> +static int max96724_select_links(struct max_des *des, unsigned int mask)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +	int ret;
> +
> +	ret = regmap_update_bits(priv->regmap, MAX96724_REG6, MAX96724_REG6_LINK_EN,
> +				 field_prep(MAX96724_REG6_LINK_EN, mask));
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_set_bits(priv->regmap, MAX96724_CTRL1,
> +			      MAX96724_CTRL1_RESET_ONESHOT);
> +	if (ret)
> +		return ret;
> +
> +	msleep(60);
> +
> +	return 0;
> +}
> +
> +static int max96724_set_link_version(struct max_des *des,
> +				     struct max_des_link *link,
> +				     enum max_gmsl_version version)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +	unsigned int index = link->index;
> +	unsigned int val;
> +
> +	if (version == MAX_GMSL_2_6GBPS)
> +		val = MAX96724_REG26_RX_RATE_6GBPS;
> +	else
> +		val = MAX96724_REG26_RX_RATE_3GBPS;
> +
> +	return regmap_update_bits(priv->regmap, MAX96724_REG26(index),
> +				  MAX96724_REG26_RX_RATE_PHY(index),
> +				  field_prep(MAX96724_REG26_RX_RATE_PHY(index), val));
> +}
> +
> +static int max96724_set_tpg_timings(struct max96724_priv *priv,
> +				    const struct max_tpg_timings *tm)
> +{
> +	const struct reg_sequence regs[] = {
> +		REG_SEQUENCE_3(MAX96724_VS_DLY_2, tm->vs_dly),
> +		REG_SEQUENCE_3(MAX96724_VS_HIGH_2, tm->vs_high),
> +		REG_SEQUENCE_3(MAX96724_VS_LOW_2, tm->vs_low),
> +		REG_SEQUENCE_3(MAX96724_V2H_2, tm->v2h),
> +		REG_SEQUENCE_2(MAX96724_HS_HIGH_1, tm->hs_high),
> +		REG_SEQUENCE_2(MAX96724_HS_LOW_1, tm->hs_low),
> +		REG_SEQUENCE_2(MAX96724_HS_CNT_1, tm->hs_cnt),
> +		REG_SEQUENCE_3(MAX96724_V2D_2, tm->v2d),
> +		REG_SEQUENCE_2(MAX96724_DE_HIGH_1, tm->de_high),
> +		REG_SEQUENCE_2(MAX96724_DE_LOW_1, tm->de_low),
> +		REG_SEQUENCE_2(MAX96724_DE_CNT_1, tm->de_cnt),
> +	};
> +	int ret;
> +
> +	ret = regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs));
> +	if (ret)
> +		return ret;
> +
> +	return regmap_write(priv->regmap, MAX96724_PATGEN_0,
> +			    FIELD_PREP(MAX96724_PATGEN_0_VTG_MODE,
> +				       MAX96724_PATGEN_0_VTG_MODE_FREE_RUNNING) |
> +			    FIELD_PREP(MAX96724_PATGEN_0_DE_INV, tm->de_inv) |
> +			    FIELD_PREP(MAX96724_PATGEN_0_HS_INV, tm->hs_inv) |
> +			    FIELD_PREP(MAX96724_PATGEN_0_VS_INV, tm->vs_inv) |
> +			    FIELD_PREP(MAX96724_PATGEN_0_GEN_DE, tm->gen_de) |
> +			    FIELD_PREP(MAX96724_PATGEN_0_GEN_HS, tm->gen_hs) |
> +			    FIELD_PREP(MAX96724_PATGEN_0_GEN_VS, tm->gen_vs));
> +}
> +
> +static int max96724_set_tpg_clk(struct max96724_priv *priv, const struct videomode *vm)
> +{
> +	bool patgen_clk_src = 0;
> +	u8 pclk_src;
> +	int ret;
> +
> +	if (!vm)
> +		return 0;
> +
> +	switch (vm->pixelclock) {
> +	case 25000000:
> +		pclk_src = MAX96724_DEBUG_EXTRA_PCLK_SRC_25MHZ;
> +		break;
> +	case 75000000:
> +		pclk_src = MAX96724_DEBUG_EXTRA_PCLK_SRC_75MHZ;
> +		break;
> +	case 150000000:
> +		pclk_src = MAX96724_DEBUG_EXTRA_PCLK_SRC_USE_PIPE;
> +		patgen_clk_src = MAX96724_VPRBS_PATGEN_CLK_SRC_150MHZ;
> +		break;
> +	case 375000000:
> +		pclk_src = MAX96724_DEBUG_EXTRA_PCLK_SRC_USE_PIPE;
> +		patgen_clk_src = MAX96724_VPRBS_PATGEN_CLK_SRC_375MHZ;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * TPG data is always injected on link 0, which is always routed to
> +	 * pipe 0.
> +	 */
> +	ret = regmap_update_bits(priv->regmap, MAX96724_VPRBS(0),
> +				 MAX96724_VPRBS_PATGEN_CLK_SRC,
> +				 FIELD_PREP(MAX96724_VPRBS_PATGEN_CLK_SRC,
> +					    patgen_clk_src));
> +	if (ret)
> +		return ret;
> +
> +	return regmap_update_bits(priv->regmap, MAX96724_DEBUG_EXTRA,
> +				  MAX96724_DEBUG_EXTRA_PCLK_SRC,
> +				  FIELD_PREP(MAX96724_DEBUG_EXTRA_PCLK_SRC,
> +					     pclk_src));
> +}
> +
> +static int max96724_set_tpg(struct max_des *des, const struct max_tpg_entry *entry)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +	struct max_tpg_timings timings = { 0 };
> +	const struct videomode *vm = NULL;
> +	int ret;
> +
> +	if (entry) {
> +		vm = max_find_tpg_videomode(entry);
> +		if (!vm)
> +			return -EINVAL;
> +
> +		max_get_tpg_timings(vm, &timings);
> +	}
> +
> +	ret = max96724_set_tpg_timings(priv, &timings);
> +	if (ret)
> +		return ret;
> +
> +	ret = max96724_set_tpg_clk(priv, vm);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_update_bits(priv->regmap, MAX96724_PATGEN_1,
> +				 MAX96724_PATGEN_1_PATGEN_MODE,
> +				 FIELD_PREP(MAX96724_PATGEN_1_PATGEN_MODE,
> +					    entry ? MAX96724_PATGEN_1_PATGEN_MODE_GRADIENT
> +						  : MAX96724_PATGEN_1_PATGEN_MODE_DISABLED));
> +	if (ret)
> +		return ret;
> +
> +	return regmap_assign_bits(priv->regmap, MAX96724_MIPI_PHY0,
> +				  MAX96724_MIPI_PHY0_FORCE_CSI_OUT_EN, !!entry);
> +}
> +
> +static const struct max_tpg_entry max96724_tpg_entries[] = {
> +	MAX_TPG_ENTRY_640X480P60_RGB888,
> +	MAX_TPG_ENTRY_1920X1080P30_RGB888,
> +	MAX_TPG_ENTRY_1920X1080P60_RGB888,
> +};
> +
> +static const struct max_des_ops max96724_ops = {
> +	.num_phys = 4,
> +	.num_links = 4,
> +	.num_remaps_per_pipe = 16,
> +	.phys_configs = {
> +		.num_configs = ARRAY_SIZE(max96724_phys_configs),
> +		.configs = max96724_phys_configs,
> +	},
> +	.tpg_entries = {
> +		.num_entries = ARRAY_SIZE(max96724_tpg_entries),
> +		.entries = max96724_tpg_entries,
> +	},
> +	.tpg_mode = MAX_GMSL_PIXEL_MODE,
> +	.use_atr = true,
> +	.reg_read = max96724_reg_read,
> +	.reg_write = max96724_reg_write,
> +	.log_pipe_status = max96724_log_pipe_status,
> +	.log_phy_status = max96724_log_phy_status,
> +	.set_enable = max96724_set_enable,
> +	.init = max96724_init,
> +	.init_phy = max96724_init_phy,
> +	.set_phy_mode = max96724_set_phy_mode,
> +	.set_phy_active = max96724_set_phy_active,
> +	.set_pipe_stream_id = max96724_set_pipe_stream_id,
> +	.set_pipe_link = max96724_set_pipe_link,
> +	.set_pipe_enable = max96724_set_pipe_enable,
> +	.set_pipe_remap = max96724_set_pipe_remap,
> +	.set_pipe_remaps_enable = max96724_set_pipe_remaps_enable,
> +	.set_pipe_mode = max96724_set_pipe_mode,
> +	.set_tpg = max96724_set_tpg,
> +	.select_links = max96724_select_links,
> +	.set_link_version = max96724_set_link_version,
> +};
> +
> +static const struct max96724_chip_info max96724_info = {
> +	.versions = BIT(MAX_GMSL_2_3GBPS) | BIT(MAX_GMSL_2_6GBPS),
> +	.modes = BIT(MAX_GMSL_PIXEL_MODE) | BIT(MAX_GMSL_TUNNEL_MODE),
> +	.set_pipe_tunnel_enable = max96724_set_pipe_tunnel_enable,
> +	.set_pipe_phy = max96724_set_pipe_phy,
> +	.set_pipe_tunnel_phy = max96724_set_pipe_tunnel_phy,
> +	.supports_pipe_stream_autoselect = true,
> +	.num_pipes = 4,
> +};
> +
> +static const struct max96724_chip_info max96724f_info = {
> +	.versions = BIT(MAX_GMSL_2_3GBPS),
> +	.modes = BIT(MAX_GMSL_PIXEL_MODE) | BIT(MAX_GMSL_TUNNEL_MODE),
> +	.set_pipe_tunnel_enable = max96724_set_pipe_tunnel_enable,
> +	.set_pipe_phy = max96724_set_pipe_phy,
> +	.set_pipe_tunnel_phy = max96724_set_pipe_tunnel_phy,
> +	.supports_pipe_stream_autoselect = true,
> +	.num_pipes = 4,
> +};
> +
> +static const struct max96724_chip_info max96712_info = {
> +	.versions = BIT(MAX_GMSL_2_3GBPS) | BIT(MAX_GMSL_2_6GBPS),
> +	.modes = BIT(MAX_GMSL_PIXEL_MODE),
> +	.num_pipes = 8,
> +};
> +
> +static int max96724_probe(struct i2c_client *client)
> +{
> +	struct device *dev = &client->dev;
> +	struct max96724_priv *priv;
> +	struct max_des_ops *ops;
> +	int ret;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	ops = devm_kzalloc(dev, sizeof(*ops), GFP_KERNEL);
> +	if (!ops)
> +		return -ENOMEM;
> +
> +	priv->info = device_get_match_data(dev);
> +	if (!priv->info) {
> +		dev_err(dev, "Failed to get match data\n");
> +		return -ENODEV;
> +	}
> +
> +	priv->dev = dev;
> +	priv->client = client;
> +	i2c_set_clientdata(client, priv);
> +
> +	priv->regmap = devm_regmap_init_i2c(client, &max96724_i2c_regmap);
> +	if (IS_ERR(priv->regmap))
> +		return PTR_ERR(priv->regmap);
> +
> +	priv->gpiod_enable = devm_gpiod_get_optional(&client->dev, "enable",
> +						     GPIOD_OUT_LOW);
> +	if (IS_ERR(priv->gpiod_enable))
> +		return PTR_ERR(priv->gpiod_enable);
> +
> +	if (priv->gpiod_enable) {
> +		/* PWDN must be held for 1us for reset */
> +		udelay(1);
> +
> +		gpiod_set_value_cansleep(priv->gpiod_enable, 1);
> +
> +		/* Maximum power-up time (tLOCK) 4ms */
> +		usleep_range(4000, 5000);
> +	}
> +
> +	*ops = max96724_ops;
> +	ops->versions = priv->info->versions;
> +	ops->modes = priv->info->modes;
> +	ops->num_pipes = priv->info->num_pipes;
> +	ops->set_pipe_tunnel_enable = priv->info->set_pipe_tunnel_enable;
> +	ops->set_pipe_phy = priv->info->set_pipe_phy;
> +	priv->des.ops = ops;
> +
> +	ret = max96724_reset(priv);
> +	if (ret)
> +		return ret;
> +
> +	return max_des_probe(client, &priv->des);
> +}
> +
> +static void max96724_remove(struct i2c_client *client)
> +{
> +	struct max96724_priv *priv = i2c_get_clientdata(client);
> +
> +	max_des_remove(&priv->des);
> +
> +	gpiod_set_value_cansleep(priv->gpiod_enable, 0);
> +}
> +
> +static const struct of_device_id max96724_of_table[] = {
> +	{ .compatible = "maxim,max96712", .data = &max96712_info },
> +	{ .compatible = "maxim,max96724", .data = &max96724_info },
> +	{ .compatible = "maxim,max96724f", .data = &max96724f_info },
> +	{ .compatible = "maxim,max96724r", .data = &max96724f_info },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, max96724_of_table);
> +
> +static struct i2c_driver max96724_i2c_driver = {
> +	.driver	= {
> +		.name = "max96724",
> +		.of_match_table	= of_match_ptr(max96724_of_table),
> +	},
> +	.probe = max96724_probe,
> +	.remove = max96724_remove,
> +};
> +
> +module_i2c_driver(max96724_i2c_driver);
> +
> +MODULE_DESCRIPTION("Maxim MAX96724 Quad GMSL2 Deserializer Driver");
> +MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@...log.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/media/i2c/maxim-serdes/max_des.c b/drivers/media/i2c/maxim-serdes/max_des.c
> new file mode 100644
> index 000000000000..6a45f42fe033
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max_des.c
> @@ -0,0 +1,3108 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Maxim GMSL2 Deserializer Driver

How about naming the file maxim-deserialiser? I'd use e.g. "maxim_des"
prefix for the functions. Just "max" is quite generic.

> + *
> + * Copyright (C) 2025 Analog Devices Inc.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/i2c-atr.h>
> +#include <linux/i2c-mux.h>
> +#include <linux/module.h>
> +#include <linux/regulator/consumer.h>
> +
> +#include <media/mipi-csi2.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-subdev.h>
> +
> +#include "max_des.h"
> +#include "max_ser.h"
> +#include "max_serdes.h"
> +
> +#define MAX_DES_LINK_FREQUENCY_MIN 100000000ull
> +#define MAX_DES_LINK_FREQUENCY_DEFAULT 750000000ull
> +#define MAX_DES_LINK_FREQUENCY_MAX 1250000000ull
> +
> +#define MAX_DES_PHYS_NUM		4
> +#define MAX_DES_PIPES_NUM		8
> +
> +struct max_des_priv {
> +	struct max_des *des;
> +
> +	struct device *dev;
> +	struct i2c_client *client;
> +	struct i2c_atr *atr;
> +	struct i2c_mux_core *mux;
> +
> +	struct media_pad *pads;
> +	struct regulator **pocs;
> +	struct max_source *sources;
> +	u64 *streams_masks;
> +
> +	struct notifier_block i2c_nb;
> +	struct v4l2_subdev sd;
> +	struct v4l2_async_notifier nf;
> +
> +	struct max_des_phy *unused_phy;
> +};
> +
> +struct max_des_remap_context {
> +	enum max_gmsl_mode mode;
> +	/* Mark whether TPG is enabled */
> +	bool tpg;
> +	/* Mark the PHYs to which each pipe is mapped. */
> +	unsigned long pipe_phy_masks[MAX_DES_PIPES_NUM];
> +	/* Mark the pipes in use. */
> +	bool pipe_in_use[MAX_DES_PIPES_NUM];
> +	/* Mark whether pipe has remapped VC ids. */
> +	bool vc_ids_remapped[MAX_DES_PIPES_NUM];
> +	/* Map between pipe VC ids and PHY VC ids. */
> +	unsigned int vc_ids_map[MAX_DES_PIPES_NUM][MAX_DES_PHYS_NUM][MAX_SERDES_VC_ID_NUM];
> +	/* Mark whether a pipe VC id has been mapped to a PHY VC id. */
> +	unsigned long vc_ids_masks[MAX_DES_PIPES_NUM][MAX_DES_PHYS_NUM];
> +	/* Mark whether a PHY VC id has been mapped. */
> +	unsigned long dst_vc_ids_masks[MAX_DES_PHYS_NUM];
> +};
> +
> +struct max_des_mode_context {
> +	bool phys_bpp8_shared_with_16[MAX_DES_PHYS_NUM];
> +	bool pipes_bpp8_shared_with_16[MAX_DES_PIPES_NUM];
> +	u32 phys_double_bpps[MAX_DES_PHYS_NUM];
> +	u32 pipes_double_bpps[MAX_DES_PIPES_NUM];
> +};
> +
> +struct max_des_route_hw {
> +	struct max_source *source;
> +	struct max_des_pipe *pipe;
> +	struct max_des_phy *phy;
> +	struct v4l2_mbus_frame_desc_entry entry;
> +	bool is_tpg;
> +};
> +
> +struct max_des_link_hw {
> +	struct max_source *source;
> +	struct max_des_link *link;
> +	struct max_des_pipe *pipe;
> +};
> +
> +static inline struct max_des_priv *sd_to_priv(struct v4l2_subdev *sd)
> +{
> +	return container_of(sd, struct max_des_priv, sd);
> +}
> +
> +static inline struct max_des_priv *nf_to_priv(struct v4l2_async_notifier *nf)
> +{
> +	return container_of(nf, struct max_des_priv, nf);
> +}
> +
> +static inline bool max_des_pad_is_sink(struct max_des *des, u32 pad)
> +{
> +	return pad < des->ops->num_links;
> +}
> +
> +static inline bool max_des_pad_is_source(struct max_des *des, u32 pad)
> +{
> +	return pad >= des->ops->num_links &&
> +	       pad < des->ops->num_links + des->ops->num_phys;
> +}
> +
> +static inline bool max_des_pad_is_tpg(struct max_des *des, u32 pad)
> +{
> +	return pad == des->ops->num_links + des->ops->num_phys;
> +}
> +
> +static inline unsigned int max_des_link_to_pad(struct max_des *des,
> +					       struct max_des_link *link)
> +{
> +	return link->index;
> +}
> +
> +static inline unsigned int max_des_phy_to_pad(struct max_des *des,
> +					      struct max_des_phy *phy)
> +{
> +	return phy->index + des->ops->num_links;
> +}
> +
> +static inline unsigned int max_des_num_pads(struct max_des *des)
> +{
> +	return des->ops->num_links + des->ops->num_phys +
> +	       (des->ops->set_tpg ? 1 : 0);
> +}
> +
> +static struct max_des_phy *max_des_pad_to_phy(struct max_des *des, u32 pad)
> +{
> +	if (!max_des_pad_is_source(des, pad))
> +		return NULL;
> +
> +	return &des->phys[pad - des->ops->num_links];
> +}
> +
> +static struct max_des_link *max_des_pad_to_link(struct max_des *des, u32 pad)
> +{
> +	if (!max_des_pad_is_sink(des, pad))
> +		return NULL;
> +
> +	return &des->links[pad];
> +}
> +
> +static struct max_des_pipe *
> +max_des_find_link_pipe(struct max_des *des, struct max_des_link *link)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < des->ops->num_pipes; i++) {
> +		struct max_des_pipe *pipe = &des->pipes[i];
> +
> +		if (pipe->link_id == link->index)
> +			return pipe;
> +	}
> +
> +	return NULL;
> +}
> +
> +static struct max_source *
> +max_des_get_link_source(struct max_des_priv *priv, struct max_des_link *link)
> +{
> +	return &priv->sources[link->index];
> +}
> +
> +static const struct max_tpg_entry *
> +max_des_find_tpg_entry(struct max_des *des, u32 target_index,
> +		       u32 width, u32 height, u32 code,
> +		       u32 numerator, u32 denominator)
> +{
> +	const struct max_tpg_entry *entry;
> +	unsigned int index = 0;
> +	unsigned int i;
> +
> +	for (i = 0; i < des->ops->tpg_entries.num_entries; i++) {
> +		entry = &des->ops->tpg_entries.entries[i];
> +
> +		if ((width != 0 && width != entry->width) ||
> +		    (height != 0 && height != entry->height) ||
> +		    (code != 0 && code != entry->code) ||
> +		    (numerator != 0 && numerator != entry->interval.numerator) ||
> +		    (denominator != 0 && denominator != entry->interval.denominator))
> +			continue;
> +
> +		if (index == target_index)
> +			break;
> +
> +		index++;
> +	}
> +
> +	if (i == des->ops->tpg_entries.num_entries)
> +		return NULL;
> +
> +	return &des->ops->tpg_entries.entries[i];
> +}
> +
> +static const struct max_tpg_entry *
> +max_des_find_state_tpg_entry(struct max_des *des, struct v4l2_subdev_state *state,
> +			     unsigned int pad)
> +{
> +	struct v4l2_mbus_framefmt *fmt;
> +	struct v4l2_fract *in;
> +
> +	fmt = v4l2_subdev_state_get_format(state, pad, MAX_SERDES_TPG_STREAM);
> +	if (!fmt)
> +		return NULL;
> +
> +	in = v4l2_subdev_state_get_interval(state, pad, MAX_SERDES_TPG_STREAM);
> +	if (!in)
> +		return NULL;
> +
> +	return max_des_find_tpg_entry(des, 0, fmt->width, fmt->height, fmt->code,
> +				      in->numerator, in->denominator);
> +}
> +
> +static int max_des_get_tpg_fd_entry_state(struct max_des *des,
> +					  struct v4l2_subdev_state *state,
> +					  struct v4l2_mbus_frame_desc_entry *fd_entry,
> +					  unsigned int pad)
> +{
> +	const struct max_tpg_entry *entry;
> +
> +	entry = max_des_find_state_tpg_entry(des, state, pad);
> +	if (!entry)
> +		return -EINVAL;
> +
> +	fd_entry->stream = MAX_SERDES_TPG_STREAM;
> +	fd_entry->flags = V4L2_MBUS_FRAME_DESC_FL_LEN_MAX;
> +	fd_entry->length = entry->width * entry->height * entry->bpp / 8;
> +	fd_entry->pixelcode = entry->code;
> +	fd_entry->bus.csi2.vc = 0;
> +	fd_entry->bus.csi2.dt = entry->dt;
> +
> +	return 0;
> +}
> +
> +static int max_des_tpg_route_to_hw(struct max_des_priv *priv,
> +				   struct v4l2_subdev_state *state,
> +				   struct v4l2_subdev_route *route,
> +				   struct max_des_route_hw *hw)
> +{
> +	struct max_des *des = priv->des;
> +
> +	/* TPG injects its data into all pipes, but use pipe 0 for simplicity. */
> +	hw->pipe = &des->pipes[0];
> +
> +	hw->phy = max_des_pad_to_phy(des, route->source_pad);
> +	if (!hw->phy)
> +		return -ENOENT;
> +
> +	return max_des_get_tpg_fd_entry_state(des, state, &hw->entry,
> +					      route->sink_pad);
> +}
> +
> +static int max_des_route_to_hw(struct max_des_priv *priv,
> +			       struct v4l2_subdev_state *state,
> +			       struct v4l2_subdev_route *route,
> +			       struct max_des_route_hw *hw)
> +{
> +	struct max_des *des = priv->des;
> +	struct v4l2_mbus_frame_desc fd;
> +	struct max_des_link *link;
> +	unsigned int i;
> +	int ret;
> +
> +	memset(hw, 0, sizeof(*hw));
> +
> +	hw->is_tpg = max_des_pad_is_tpg(des, route->sink_pad);
> +	if (hw->is_tpg)
> +		return max_des_tpg_route_to_hw(priv, state, route, hw);
> +
> +	link = max_des_pad_to_link(des, route->sink_pad);
> +	if (!link)
> +		return -ENOENT;
> +
> +	hw->phy = max_des_pad_to_phy(des, route->source_pad);
> +	if (!hw->phy)
> +		return -ENOENT;
> +
> +	hw->pipe = max_des_find_link_pipe(des, link);
> +	if (!hw->pipe)
> +		return -ENOENT;
> +
> +	hw->source = max_des_get_link_source(priv, link);
> +	if (!hw->source->sd)
> +		return 0;
> +
> +	ret = v4l2_subdev_call(hw->source->sd, pad, get_frame_desc,
> +			       hw->source->pad, &fd);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < fd.num_entries; i++)
> +		if (fd.entry[i].stream == route->sink_stream)
> +			break;
> +
> +	if (i == fd.num_entries)
> +		return -ENOENT;
> +
> +	hw->entry = fd.entry[i];
> +
> +	return 0;
> +}
> +
> +static int max_des_link_to_hw(struct max_des_priv *priv,
> +			      struct max_des_link *link,
> +			      struct max_des_link_hw *hw)
> +{
> +	struct max_des *des = priv->des;
> +
> +	memset(hw, 0, sizeof(*hw));
> +
> +	hw->link = link;
> +
> +	hw->pipe = max_des_find_link_pipe(des, hw->link);
> +	if (!hw->pipe)
> +		return -ENOENT;
> +
> +	hw->source = max_des_get_link_source(priv, hw->link);
> +
> +	return 0;
> +}
> +
> +static int max_des_link_index_to_hw(struct max_des_priv *priv, unsigned int i,
> +				    struct max_des_link_hw *hw)
> +{
> +	return max_des_link_to_hw(priv, &priv->des->links[i], hw);
> +}
> +
> +static int max_des_set_pipe_remaps(struct max_des_priv *priv,
> +				   struct max_des_pipe *pipe,
> +				   struct max_des_remap *remaps,
> +				   unsigned int num_remaps)
> +{
> +	struct max_des *des = priv->des;
> +	unsigned int mask = 0;
> +	unsigned int i;
> +	int ret;
> +
> +	if (!des->ops->set_pipe_remap)
> +		return 0;
> +
> +	for (i = 0; i < num_remaps; i++) {
> +		ret = des->ops->set_pipe_remap(des, pipe, i, &remaps[i]);
> +		if (ret)
> +			return ret;
> +
> +		mask |= BIT(i);
> +	}
> +
> +	return des->ops->set_pipe_remaps_enable(des, pipe, mask);
> +}
> +
> +static int max_des_set_pipe_vc_remaps(struct max_des_priv *priv,
> +				      struct max_des_pipe *pipe,
> +				      struct max_vc_remap *vc_remaps,
> +				      unsigned int num_vc_remaps)
> +{
> +	struct max_des *des = priv->des;
> +	unsigned int mask = 0;
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < num_vc_remaps; i++) {
> +		ret = des->ops->set_pipe_vc_remap(des, pipe, i, &vc_remaps[i]);
> +		if (ret)
> +			return ret;
> +
> +		mask |= BIT(i);
> +	}
> +
> +	return des->ops->set_pipe_vc_remaps_enable(des, pipe, mask);
> +}
> +
> +static int max_des_set_phy_active(struct max_des *des, struct max_des_phy *phy,
> +				  bool active)
> +{
> +	int ret;
> +
> +	ret = des->ops->set_phy_active(des, phy, active);
> +	if (ret)
> +		return ret;
> +
> +	phy->active = active;
> +
> +	return 0;
> +}
> +
> +static int max_des_map_src_dst_vc_id(struct max_des_remap_context *context,
> +				     unsigned int pipe_id, unsigned int phy_id,
> +				     unsigned int src_vc_id, bool keep_vc)
> +{
> +	unsigned int vc_id;
> +
> +	if (src_vc_id >= MAX_SERDES_VC_ID_NUM)
> +		return -E2BIG;
> +
> +	if (context->vc_ids_masks[pipe_id][phy_id] & BIT(src_vc_id))
> +		return 0;
> +
> +	if (keep_vc && !(context->dst_vc_ids_masks[phy_id] & BIT(src_vc_id)))
> +		vc_id = src_vc_id;
> +	else
> +		vc_id = ffz(context->dst_vc_ids_masks[phy_id]);
> +
> +	if (vc_id != src_vc_id)
> +		context->vc_ids_remapped[pipe_id] = true;
> +
> +	if (vc_id >= MAX_SERDES_VC_ID_NUM)
> +		return -E2BIG;
> +
> +	context->pipe_phy_masks[pipe_id] |= BIT(phy_id);
> +	context->dst_vc_ids_masks[phy_id] |= BIT(vc_id);
> +
> +	context->vc_ids_map[pipe_id][phy_id][src_vc_id] = vc_id;
> +	context->vc_ids_masks[pipe_id][phy_id] |= BIT(src_vc_id);
> +
> +	return 0;
> +}
> +
> +static int max_des_get_src_dst_vc_id(struct max_des_remap_context *context,
> +				     unsigned int pipe_id, unsigned int phy_id,
> +				     unsigned int src_vc_id, unsigned int *dst_vc_id)
> +{
> +	if (!(context->vc_ids_masks[pipe_id][phy_id] & BIT(src_vc_id)))
> +		return -ENOENT;
> +
> +	*dst_vc_id = context->vc_ids_map[pipe_id][phy_id][src_vc_id];
> +
> +	return 0;
> +}
> +
> +static int max_des_populate_remap_usage(struct max_des_priv *priv,
> +					struct max_des_remap_context *context,
> +					struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_subdev_route *route;
> +	int ret;
> +
> +	for_each_active_route(&state->routing, route) {
> +		struct max_des_route_hw hw;
> +
> +		ret = max_des_route_to_hw(priv, state, route, &hw);
> +		if (ret)
> +			return ret;
> +
> +		if (hw.is_tpg)
> +			context->tpg = true;
> +
> +		context->pipe_in_use[hw.pipe->index] = true;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max_des_get_supported_modes(struct max_des_priv *priv,
> +				       struct max_des_remap_context *context,
> +				       unsigned int *modes)
> +{
> +	struct max_des *des = priv->des;
> +	unsigned int i;
> +	int ret;
> +
> +	*modes = des->ops->modes;
> +
> +	if (context->tpg)
> +		*modes = BIT(des->ops->tpg_mode);
> +
> +	for (i = 0; i < des->ops->num_links; i++) {
> +		struct max_des_link_hw hw;
> +
> +		ret = max_des_link_index_to_hw(priv, i, &hw);
> +		if (ret)
> +			return ret;
> +
> +		if (!hw.link->enabled)
> +			continue;
> +
> +		if (!hw.source->sd)
> +			continue;
> +
> +		if (!context->pipe_in_use[hw.pipe->index])
> +			continue;
> +
> +		*modes &= max_ser_get_supported_modes(hw.source->sd);
> +	}
> +
> +	/*
> +	 * Serializers need to all be in the same mode because of hardware
> +	 * issues when running them in mixed modes.
> +	 */
> +	if (!*modes)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +static int max_des_populate_remap_context_mode(struct max_des_priv *priv,
> +					       struct max_des_remap_context *context,
> +					       unsigned int modes)
> +{
> +	struct max_des *des = priv->des;
> +	unsigned int i;
> +	int ret;
> +
> +	context->mode = MAX_GMSL_PIXEL_MODE;
> +
> +	/*
> +	 * If pixel mode is the only supported mode, do not try to see if
> +	 * tunnel mode can be used.
> +	 */
> +	if (modes == BIT(MAX_GMSL_PIXEL_MODE))
> +		return 0;
> +
> +	for (i = 0; i < des->ops->num_links; i++) {
> +		struct max_des_link_hw hw;
> +
> +		ret = max_des_link_index_to_hw(priv, i, &hw);
> +		if (ret)
> +			return ret;
> +
> +		if (!hw.link->enabled)
> +			continue;
> +
> +		if (!hw.source->sd)
> +			continue;
> +
> +		if (!context->pipe_in_use[hw.pipe->index])
> +			continue;
> +
> +		if (hweight_long(context->pipe_phy_masks[hw.pipe->index]) == 1 &&
> +		    (!context->vc_ids_remapped[hw.pipe->index] ||
> +		     max_ser_supports_vc_remap(hw.source->sd) ||
> +		     des->ops->set_pipe_vc_remap))
> +			continue;
> +
> +		return 0;
> +	}
> +
> +	context->mode = MAX_GMSL_TUNNEL_MODE;
> +
> +	return 0;
> +}
> +
> +static int max_des_should_keep_vc(struct max_des_priv *priv,
> +				  struct max_des_route_hw *hw,
> +				  unsigned int modes)
> +{
> +	struct max_des *des = priv->des;
> +
> +	/* Pixel mode deserializers always have the ability to remap VCs. */
> +	if (modes == BIT(MAX_GMSL_PIXEL_MODE))
> +		return false;
> +
> +	if (des->ops->set_pipe_vc_remap)
> +		return false;
> +
> +	if (!hw->is_tpg && hw->source && hw->source->sd &&
> +	    max_ser_supports_vc_remap(hw->source->sd))
> +		return false;
> +
> +	return true;
> +}
> +
> +static int max_des_populate_remap_context(struct max_des_priv *priv,
> +					  struct max_des_remap_context *context,
> +					  struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_subdev_route *route;
> +	unsigned int modes;
> +	int ret;
> +
> +	ret = max_des_populate_remap_usage(priv, context, state);
> +	if (ret)
> +		return ret;
> +
> +	ret = max_des_get_supported_modes(priv, context, &modes);
> +	if (ret)
> +		return ret;
> +
> +	for_each_active_route(&state->routing, route) {
> +		struct max_des_route_hw hw;
> +		bool keep_vc;
> +
> +		ret = max_des_route_to_hw(priv, state, route, &hw);
> +		if (ret)
> +			return ret;
> +
> +		keep_vc = max_des_should_keep_vc(priv, &hw, modes);
> +
> +		ret = max_des_map_src_dst_vc_id(context, hw.pipe->index, hw.phy->index,
> +						hw.entry.bus.csi2.vc, keep_vc);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return max_des_populate_remap_context_mode(priv, context, modes);
> +}
> +
> +static int max_des_populate_mode_context(struct max_des_priv *priv,
> +					 struct max_des_mode_context *context,
> +					 struct v4l2_subdev_state *state,
> +					 enum max_gmsl_mode mode)
> +{
> +	bool bpp8_not_shared_with_16_phys[MAX_DES_PHYS_NUM] = { 0 };
> +	u32 undoubled_bpps_phys[MAX_DES_PHYS_NUM] = { 0 };
> +	u32 bpps_pipes[MAX_DES_PIPES_NUM] = { 0 };
> +	struct max_des *des = priv->des;
> +	struct v4l2_subdev_route *route;
> +	unsigned int doubled_bpp;
> +	unsigned int i;
> +	u32 sink_bpps;
> +	int ret;
> +
> +	if (mode != MAX_GMSL_PIXEL_MODE)
> +		return 0;
> +
> +	/*
> +	 * Go over all streams and gather the bpps for all pipes.
> +	 *
> +	 * Then, go over all the streams again and check if the
> +	 * current stream is doubled.
> +	 *
> +	 * If the current stream is doubled, add it to a doubled mask for both
> +	 * the pipe and the PHY.
> +	 *
> +	 * If the current stream is not doubled, add it to a local undoubled
> +	 * mask for the PHY.
> +	 *
> +	 * Also, track whether an 8bpp stream is shared with any bpp > 8 on both
> +	 * the PHYs and the pipes, since that needs to be special cased.
> +	 *
> +	 * After going over all the streams, remove the undoubled streams from
> +	 * the doubled ones. Doubled and undoubled streams cannot be streamed
> +	 * over the same PHY.
> +	 *
> +	 * Then, do a second pass to remove the undoubled streams from the pipes.
> +	 *
> +	 * This operation cannot be done in a single pass because any pipe might
> +	 * generate an undoubled stream for a specific bpp, causing already
> +	 * processed pipes to need to have their doubled bpps updated.
> +	 */
> +
> +	for_each_active_route(&state->routing, route) {
> +		struct max_des_route_hw hw;
> +		unsigned int bpp;
> +
> +		ret = max_des_route_to_hw(priv, state, route, &hw);
> +		if (ret)
> +			return ret;
> +
> +		ret = max_get_fd_bpp(&hw.entry, &bpp);
> +		if (ret)
> +			return ret;
> +
> +		bpps_pipes[hw.pipe->index] |= BIT(bpp);
> +	}
> +
> +	for_each_active_route(&state->routing, route) {
> +		unsigned int bpp, min_bpp, max_bpp;
> +		unsigned int pipe_id, phy_id;
> +		struct max_des_route_hw hw;
> +
> +		ret = max_des_route_to_hw(priv, state, route, &hw);
> +		if (ret)
> +			return ret;
> +
> +		ret = max_get_fd_bpp(&hw.entry, &bpp);
> +		if (ret)
> +			return ret;
> +
> +		sink_bpps = bpps_pipes[hw.pipe->index];
> +
> +		ret = max_process_bpps(priv->dev, sink_bpps, ~0U, &doubled_bpp);
> +		if (ret)
> +			return ret;
> +
> +		min_bpp = __ffs(sink_bpps);
> +		max_bpp = __fls(sink_bpps);
> +		pipe_id = hw.pipe->index;
> +		phy_id = hw.phy->index;
> +
> +		if (bpp == doubled_bpp) {
> +			context->phys_double_bpps[phy_id] |= BIT(bpp);
> +			context->pipes_double_bpps[pipe_id] |= BIT(bpp);
> +		} else {
> +			undoubled_bpps_phys[phy_id] |= BIT(bpp);
> +		}
> +
> +		if (min_bpp == 8 && max_bpp > 8) {
> +			context->phys_bpp8_shared_with_16[phy_id] = true;
> +			context->pipes_bpp8_shared_with_16[pipe_id] = true;
> +		} else if (min_bpp == 8 && max_bpp == 8) {
> +			bpp8_not_shared_with_16_phys[phy_id] = true;
> +		}
> +	}
> +
> +	for (i = 0; i < des->ops->num_phys; i++) {
> +		if (context->phys_bpp8_shared_with_16[i] && bpp8_not_shared_with_16_phys[i]) {
> +			dev_err(priv->dev,
> +				"Cannot stream 8bpp coming from pipes padded to 16bpp "
> +				"and pipes not padded to 16bpp on the same PHY\n");
> +			return -EINVAL;
> +		}
> +	}
> +
> +	for (i = 0; i < des->ops->num_phys; i++)
> +		context->phys_double_bpps[i] &= ~undoubled_bpps_phys[i];
> +
> +	for_each_active_route(&state->routing, route) {
> +		struct max_des_route_hw hw;
> +
> +		ret = max_des_route_to_hw(priv, state, route, &hw);
> +		if (ret)
> +			return ret;
> +
> +		context->pipes_double_bpps[hw.pipe->index] &=
> +			context->phys_double_bpps[hw.phy->index];
> +	}
> +
> +	return 0;
> +}
> +
> +static int max_des_add_vc_remap(struct max_des *des, struct max_vc_remap *vc_remaps,
> +				unsigned int *num_vc_remaps, unsigned int src_vc_id,
> +				unsigned int dst_vc_id)
> +{
> +	struct max_vc_remap *vc_remap;
> +	unsigned int i;
> +
> +	for (i = 0; i < *num_vc_remaps; i++) {
> +		vc_remap = &vc_remaps[i];
> +
> +		if (vc_remap->src == src_vc_id && vc_remap->dst == dst_vc_id)
> +			return 0;
> +	}
> +
> +	if (*num_vc_remaps == MAX_SERDES_VC_ID_NUM)
> +		return -E2BIG;
> +
> +	vc_remaps[*num_vc_remaps].src = src_vc_id;
> +	vc_remaps[*num_vc_remaps].dst = dst_vc_id;
> +
> +	(*num_vc_remaps)++;
> +
> +	return 0;
> +}
> +
> +static int max_des_get_pipe_vc_remaps(struct max_des_priv *priv,
> +				      struct max_des_remap_context *context,
> +				      struct max_des_pipe *pipe,
> +				      struct max_vc_remap *vc_remaps,
> +				      unsigned int *num_vc_remaps,
> +				      struct v4l2_subdev_state *state,
> +				      u64 *streams_masks, bool with_tpg)
> +{
> +	struct max_des *des = priv->des;
> +	struct v4l2_subdev_route *route;
> +	int ret;
> +
> +	*num_vc_remaps = 0;
> +
> +	if (context->mode != MAX_GMSL_TUNNEL_MODE)
> +		return 0;
> +
> +	for_each_active_route(&state->routing, route) {
> +		unsigned int src_vc_id, dst_vc_id;
> +		struct max_des_route_hw hw;
> +
> +		if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
> +			continue;
> +
> +		ret = max_des_route_to_hw(priv, state, route, &hw);
> +		if (ret)
> +			return ret;
> +
> +		if (!with_tpg && hw.is_tpg)
> +			continue;
> +
> +		if (hw.pipe != pipe)
> +			continue;
> +
> +		src_vc_id = hw.entry.bus.csi2.vc;
> +
> +		ret = max_des_get_src_dst_vc_id(context, pipe->index, hw.phy->index,
> +						src_vc_id, &dst_vc_id);
> +		if (ret)
> +			return ret;
> +
> +		ret = max_des_add_vc_remap(des, vc_remaps, num_vc_remaps,
> +					   src_vc_id, dst_vc_id);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void max_des_get_pipe_mode(struct max_des_mode_context *context,
> +				  struct max_des_pipe *pipe,
> +				  struct max_des_pipe_mode *mode)
> +{
> +	u32 double_bpps = context->pipes_double_bpps[pipe->index];
> +
> +	if ((double_bpps & BIT(8)) &&
> +	    !context->pipes_bpp8_shared_with_16[pipe->index]) {
> +		mode->dbl8 = true;
> +		mode->dbl8mode = true;
> +	}
> +}
> +
> +static void max_des_get_phy_mode(struct max_des_mode_context *context,
> +				 struct max_des_phy *phy,
> +				 struct max_des_phy_mode *mode)
> +{
> +	bool bpp8_pipe_shared_with_16 = context->phys_bpp8_shared_with_16[phy->index];
> +	u32 double_bpps = context->phys_double_bpps[phy->index];
> +
> +	if (BIT(8) & double_bpps) {
> +		if (bpp8_pipe_shared_with_16)
> +			mode->alt2_mem_map8 = true;
> +		else
> +			mode->alt_mem_map8 = true;
> +	}
> +
> +	if (BIT(10) & double_bpps)
> +		mode->alt_mem_map10 = true;
> +
> +	if (BIT(12) & double_bpps)
> +		mode->alt_mem_map12 = true;
> +}
> +
> +static int max_des_set_modes(struct max_des_priv *priv,
> +			     struct max_des_mode_context *context)
> +{
> +	struct max_des *des = priv->des;
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < des->ops->num_phys; i++) {
> +		struct max_des_phy *phy = &des->phys[i];
> +		struct max_des_phy_mode mode = { 0 };
> +
> +		max_des_get_phy_mode(context, phy, &mode);
> +
> +		if (phy->mode.alt_mem_map8 == mode.alt_mem_map8 &&
> +		    phy->mode.alt_mem_map10 == mode.alt_mem_map10 &&
> +		    phy->mode.alt_mem_map12 == mode.alt_mem_map12 &&
> +		    phy->mode.alt2_mem_map8 == mode.alt2_mem_map8)
> +			continue;
> +
> +		if (des->ops->set_phy_mode) {
> +			ret = des->ops->set_phy_mode(des, phy, &mode);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		phy->mode = mode;
> +	}
> +
> +	for (i = 0; i < des->ops->num_pipes; i++) {
> +		struct max_des_pipe *pipe = &des->pipes[i];
> +		struct max_des_pipe_mode mode = { 0 };
> +
> +		max_des_get_pipe_mode(context, pipe, &mode);
> +
> +		if (pipe->mode.dbl8 == mode.dbl8 &&
> +		    pipe->mode.dbl10 == mode.dbl10 &&
> +		    pipe->mode.dbl12 == mode.dbl12 &&
> +		    pipe->mode.dbl8mode == mode.dbl8mode &&
> +		    pipe->mode.dbl10mode == mode.dbl10mode)
> +			continue;
> +
> +		if (des->ops->set_pipe_mode) {
> +			ret = des->ops->set_pipe_mode(des, pipe, &mode);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		pipe->mode = mode;
> +	}
> +
> +	for (i = 0; i < des->ops->num_links; i++) {
> +		struct max_des_link_hw hw;
> +		u32 pipe_double_bpps = 0;
> +
> +		ret = max_des_link_index_to_hw(priv, i, &hw);
> +		if (ret)
> +			return ret;
> +
> +		if (!hw.link->enabled)
> +			continue;
> +
> +		if (!hw.source->sd)
> +			continue;
> +
> +		pipe_double_bpps = context->pipes_double_bpps[hw.pipe->index];
> +
> +		ret = max_ser_set_double_bpps(hw.source->sd, pipe_double_bpps);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max_des_set_tunnel(struct max_des_priv *priv,
> +			      struct max_des_remap_context *context)
> +{
> +	struct max_des *des = priv->des;
> +	unsigned int i;
> +	int ret;
> +
> +	if (des->ops->set_pipe_tunnel_enable) {
> +		for (i = 0; i < des->ops->num_pipes; i++) {
> +			struct max_des_pipe *pipe = &des->pipes[i];
> +			bool tunnel_mode = context->mode == MAX_GMSL_TUNNEL_MODE;
> +
> +			ret = des->ops->set_pipe_tunnel_enable(des, pipe, tunnel_mode);
> +			if (ret)
> +				return ret;
> +		}
> +	}
> +
> +	for (i = 0; i < des->ops->num_links; i++) {
> +		struct max_des_link_hw hw;
> +
> +		ret = max_des_link_index_to_hw(priv, i, &hw);
> +		if (ret)
> +			return ret;
> +
> +		if (!hw.link->enabled)
> +			continue;
> +
> +		if (!hw.source->sd)
> +			continue;
> +
> +		if (!context->pipe_in_use[hw.pipe->index])
> +			continue;
> +
> +		ret = max_ser_set_mode(hw.source->sd, context->mode);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	des->mode = context->mode;
> +
> +	return 0;
> +}
> +
> +static int max_des_set_vc_remaps(struct max_des_priv *priv,
> +				 struct max_des_remap_context *context,
> +				 struct v4l2_subdev_state *state,
> +				 u64 *streams_masks)
> +{
> +	struct max_des *des = priv->des;
> +	unsigned int i;
> +	int ret;
> +
> +	if (des->ops->set_pipe_vc_remap)
> +		return 0;
> +
> +	for (i = 0; i < des->ops->num_links; i++) {
> +		struct max_vc_remap vc_remaps[MAX_SERDES_VC_ID_NUM];
> +		struct max_des_link_hw hw;
> +		unsigned int num_vc_remaps;
> +
> +		ret = max_des_link_index_to_hw(priv, i, &hw);
> +		if (ret)
> +			return ret;
> +
> +		if (!hw.link->enabled)
> +			continue;
> +
> +		if (!hw.source->sd)
> +			continue;
> +
> +		if (!max_ser_supports_vc_remap(hw.source->sd))
> +			continue;
> +
> +		ret = max_des_get_pipe_vc_remaps(priv, context, hw.pipe,
> +						 vc_remaps, &num_vc_remaps,
> +						 state, streams_masks, false);
> +		if (ret)
> +			return ret;
> +
> +		ret = max_ser_set_vc_remaps(hw.source->sd, vc_remaps, num_vc_remaps);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max_des_set_pipes_stream_id(struct max_des_priv *priv)
> +{
> +	struct max_des *des = priv->des;
> +	unsigned int i;
> +	int ret;
> +
> +	if (!des->ops->needs_unique_stream_id)
> +		return 0;
> +
> +	for (i = 0; i < des->ops->num_links; i++) {
> +		struct max_des_link_hw hw;
> +		unsigned int stream_id;
> +
> +		ret = max_des_link_index_to_hw(priv, i, &hw);
> +		if (ret)
> +			return ret;
> +
> +		if (!hw.link->enabled)
> +			continue;
> +
> +		if (!hw.source->sd)
> +			continue;
> +
> +		stream_id = hw.pipe->stream_id;
> +
> +		ret = max_ser_set_stream_id(hw.source->sd, stream_id);
> +		if (ret)
> +			return ret;
> +
> +		ret = max_ser_get_stream_id(hw.source->sd, &stream_id);
> +		if (ret)
> +			return ret;
> +
> +		if (des->ops->set_pipe_stream_id) {
> +			ret = des->ops->set_pipe_stream_id(des, hw.pipe, stream_id);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		hw.pipe->stream_id = stream_id;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max_des_set_pipes_phy(struct max_des_priv *priv,
> +				 struct max_des_remap_context *context)
> +{
> +	struct max_des *des = priv->des;
> +	unsigned int i;
> +	int ret;
> +
> +	if (!des->ops->set_pipe_phy && !des->ops->set_pipe_tunnel_phy)
> +		return 0;
> +
> +	for (i = 0; i < des->ops->num_pipes; i++) {
> +		struct max_des_pipe *pipe = &des->pipes[i];
> +		struct max_des_phy *phy;
> +		unsigned int phy_id;
> +
> +		phy_id = find_first_bit(&context->pipe_phy_masks[pipe->index],
> +					des->ops->num_phys);
> +
> +		if (priv->unused_phy &&
> +		    (context->mode != MAX_GMSL_TUNNEL_MODE ||
> +		     phy_id == des->ops->num_phys))
> +			phy_id = priv->unused_phy->index;
> +
> +		if (phy_id != des->ops->num_phys) {
> +			phy = &des->phys[phy_id];
> +
> +			if (des->ops->set_pipe_phy)
> +				ret = des->ops->set_pipe_phy(des, pipe, phy);
> +			else if (des->ops->set_pipe_tunnel_phy)
> +				ret = des->ops->set_pipe_tunnel_phy(des, pipe, phy);
> +			else
> +				ret = -EINVAL;
> +
> +			if (ret)
> +				return ret;
> +		}
> +
> +		pipe->phy_id = phy_id;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max_des_add_remap(struct max_des *des, struct max_des_remap *remaps,
> +			     unsigned int *num_remaps, unsigned int phy_id,
> +			     unsigned int src_vc_id, unsigned int dst_vc_id,
> +			     unsigned int dt)
> +{
> +	struct max_des_remap *remap;
> +	unsigned int i;
> +
> +	for (i = 0; i < *num_remaps; i++) {
> +		remap = &remaps[i];
> +
> +		if (remap->from_dt == dt && remap->to_dt == dt &&
> +		    remap->from_vc == src_vc_id && remap->to_vc == dst_vc_id &&
> +		    remap->phy == phy_id)
> +			return 0;
> +	}
> +
> +	if (*num_remaps == des->ops->num_remaps_per_pipe)
> +		return -E2BIG;
> +
> +	remap = &remaps[*num_remaps];
> +	remap->from_dt = dt;
> +	remap->from_vc = src_vc_id;
> +	remap->to_dt = dt;
> +	remap->to_vc = dst_vc_id;
> +	remap->phy = phy_id;
> +
> +	(*num_remaps)++;
> +
> +	return 0;
> +}
> +
> +static int max_des_add_remaps(struct max_des *des, struct max_des_remap *remaps,
> +			      unsigned int *num_remaps, unsigned int phy_id,
> +			      unsigned int src_vc_id, unsigned int dst_vc_id,
> +			      unsigned int dt)
> +{
> +	int ret;
> +
> +	ret = max_des_add_remap(des, remaps, num_remaps, phy_id,
> +				src_vc_id, dst_vc_id, dt);
> +	if (ret)
> +		return ret;
> +
> +	ret = max_des_add_remap(des, remaps, num_remaps, phy_id,
> +				src_vc_id, dst_vc_id, MIPI_CSI2_DT_FS);
> +	if (ret)
> +		return ret;
> +
> +	ret = max_des_add_remap(des, remaps, num_remaps, phy_id,
> +				src_vc_id, dst_vc_id, MIPI_CSI2_DT_FE);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int max_des_get_pipe_remaps(struct max_des_priv *priv,
> +				   struct max_des_remap_context *context,
> +				   struct max_des_pipe *pipe,
> +				   struct max_des_remap *remaps,
> +				   unsigned int *num_remaps,
> +				   struct v4l2_subdev_state *state,
> +				   u64 *streams_masks)
> +{
> +	struct v4l2_mbus_frame_desc_entry tpg_entry;
> +	struct max_des *des = priv->des;
> +	struct v4l2_subdev_route *route;
> +	bool is_tpg_pipe = true;
> +	int ret;
> +
> +	*num_remaps = 0;
> +
> +	if (context->mode != MAX_GMSL_PIXEL_MODE)
> +		return 0;
> +
> +	for_each_active_route(&state->routing, route) {
> +		struct max_des_route_hw hw;
> +		unsigned int src_vc_id, dst_vc_id;
> +
> +		if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
> +			continue;
> +
> +		ret = max_des_route_to_hw(priv, state, route, &hw);
> +		if (ret)
> +			return ret;
> +
> +		if (hw.is_tpg && hw.pipe != pipe) {
> +			is_tpg_pipe = false;
> +			tpg_entry = hw.entry;
> +		}
> +
> +		if (hw.pipe != pipe)
> +			continue;
> +
> +		src_vc_id = hw.entry.bus.csi2.vc;
> +
> +		ret = max_des_get_src_dst_vc_id(context, pipe->index, hw.phy->index,
> +						src_vc_id, &dst_vc_id);
> +		if (ret)
> +			return ret;
> +
> +		ret = max_des_add_remaps(des, remaps, num_remaps, hw.phy->index,
> +					 src_vc_id, dst_vc_id,
> +					 hw.entry.bus.csi2.dt);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	/*
> +	 * TPG mode is only handled on pipe 0, but the TPG pollutes other pipes
> +	 * with the same data.
> +	 * For devices that do not support setting the default PHY of a pipe,
> +	 * we want to filter out this data so it does not end up on the wrong
> +	 * PHY.
> +	 * Devices that support setting the default PHY of a pipe already use it
> +	 * to route unused pipes to an unused PHY.
> +	 */
> +	if (context->tpg && !is_tpg_pipe && !des->ops->set_pipe_phy &&
> +	    priv->unused_phy) {
> +		ret = max_des_add_remaps(des, remaps, num_remaps,
> +					 priv->unused_phy->index,
> +					 tpg_entry.bus.csi2.vc,
> +					 tpg_entry.bus.csi2.vc,
> +					 tpg_entry.bus.csi2.dt);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max_des_update_pipe_vc_remaps(struct max_des_priv *priv,
> +					 struct max_des_remap_context *context,
> +					 struct max_des_pipe *pipe,
> +					 struct v4l2_subdev_state *state,
> +					 u64 *streams_masks)
> +{
> +	struct max_des *des = priv->des;
> +	struct max_vc_remap *vc_remaps;
> +	unsigned int num_vc_remaps;
> +	int ret;
> +
> +	if (!des->ops->set_pipe_vc_remap)
> +		return 0;
> +
> +	vc_remaps = devm_kcalloc(priv->dev, MAX_SERDES_VC_ID_NUM,
> +				 sizeof(*vc_remaps), GFP_KERNEL);
> +	if (!vc_remaps)
> +		return -ENOMEM;
> +
> +	ret = max_des_get_pipe_vc_remaps(priv, context, pipe, vc_remaps, &num_vc_remaps,
> +					 state, streams_masks, true);
> +	if (ret)
> +		goto err_free_new_vc_remaps;
> +
> +	ret = max_des_set_pipe_vc_remaps(priv, pipe, vc_remaps, num_vc_remaps);
> +	if (ret)
> +		goto err_free_new_vc_remaps;
> +
> +	if (pipe->num_vc_remaps)
> +		devm_kfree(priv->dev, pipe->vc_remaps);
> +
> +	pipe->vc_remaps = vc_remaps;
> +	pipe->num_vc_remaps = num_vc_remaps;
> +
> +	return 0;
> +
> +err_free_new_vc_remaps:
> +	devm_kfree(priv->dev, vc_remaps);
> +
> +	return ret;
> +}
> +
> +static int max_des_update_pipe_remaps(struct max_des_priv *priv,
> +				      struct max_des_remap_context *context,
> +				      struct max_des_pipe *pipe,
> +				      struct v4l2_subdev_state *state,
> +				      u64 *streams_masks)
> +{
> +	struct max_des *des = priv->des;
> +	struct max_des_remap *remaps;
> +	unsigned int num_remaps;
> +	int ret;
> +
> +	if (!des->ops->set_pipe_remap)
> +		return 0;
> +
> +	remaps = devm_kcalloc(priv->dev, des->ops->num_remaps_per_pipe,
> +			      sizeof(*remaps), GFP_KERNEL);
> +	if (!remaps)
> +		return -ENOMEM;
> +
> +	ret = max_des_get_pipe_remaps(priv, context, pipe, remaps, &num_remaps,
> +				      state, streams_masks);
> +	if (ret)
> +		goto err_free_new_remaps;
> +
> +	ret = max_des_set_pipe_remaps(priv, pipe, remaps, num_remaps);
> +	if (ret)
> +		goto err_free_new_remaps;
> +
> +	if (pipe->remaps)
> +		devm_kfree(priv->dev, pipe->remaps);
> +
> +	pipe->remaps = remaps;
> +	pipe->num_remaps = num_remaps;
> +
> +	return 0;
> +
> +err_free_new_remaps:
> +	devm_kfree(priv->dev, remaps);
> +
> +	return ret;
> +}
> +
> +static int max_des_update_pipe_enable(struct max_des_priv *priv,
> +				      struct max_des_pipe *pipe,
> +				      struct v4l2_subdev_state *state,
> +				      u64 *streams_masks)
> +{
> +	struct max_des *des = priv->des;
> +	struct v4l2_subdev_route *route;
> +	bool enable = false;
> +	int ret;
> +
> +	for_each_active_route(&state->routing, route) {
> +		struct max_des_route_hw hw;
> +
> +		if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
> +			continue;
> +
> +		ret = max_des_route_to_hw(priv, state, route, &hw);
> +		if (ret)
> +			return ret;
> +
> +		if (hw.pipe != pipe)
> +			continue;
> +
> +		enable = true;
> +		break;
> +	}
> +
> +	if (enable == pipe->enabled)
> +		return 0;
> +
> +	ret = des->ops->set_pipe_enable(des, pipe, enable);
> +	if (ret)
> +		return ret;
> +
> +	pipe->enabled = enable;
> +
> +	return 0;
> +}
> +
> +static int max_des_update_pipe(struct max_des_priv *priv,
> +			       struct max_des_remap_context *context,
> +			       struct max_des_pipe *pipe,
> +			       struct v4l2_subdev_state *state,
> +			       u64 *streams_masks)
> +{
> +	int ret;
> +
> +	ret = max_des_update_pipe_remaps(priv, context, pipe,
> +					 state, streams_masks);
> +	if (ret)
> +		return ret;
> +
> +	ret = max_des_update_pipe_vc_remaps(priv, context, pipe, state,
> +					    streams_masks);
> +	if (ret)
> +		goto err_revert_update_pipe_remaps;
> +
> +	ret = max_des_update_pipe_enable(priv, pipe, state, streams_masks);
> +	if (ret)
> +		goto err_revert_update_pipe_vc_remaps;
> +
> +	return 0;
> +
> +err_revert_update_pipe_vc_remaps:
> +	max_des_update_pipe_vc_remaps(priv, context, pipe, state,
> +				      priv->streams_masks);
> +
> +err_revert_update_pipe_remaps:
> +	max_des_update_pipe_remaps(priv, context, pipe, state,
> +				   priv->streams_masks);
> +
> +	return ret;
> +}
> +
> +static int max_des_init_link_ser_xlate(struct max_des_priv *priv,
> +				       struct max_des_link *link,
> +				       struct i2c_adapter *adapter,
> +				       u8 power_up_addr, u8 new_addr)
> +{
> +	struct max_des *des = priv->des;
> +	u8 addrs[] = { power_up_addr, new_addr };
> +	u8 current_addr;
> +	int ret;
> +
> +	ret = des->ops->select_links(des, BIT(link->index));
> +	if (ret)
> +		return ret;
> +
> +	ret = max_ser_wait_for_multiple(adapter, addrs, ARRAY_SIZE(addrs),
> +					&current_addr);
> +	if (ret) {
> +		dev_err(priv->dev,
> +			"Failed to wait for serializer at 0x%02x or 0x%02x: %d\n",
> +			power_up_addr, new_addr, ret);
> +		return ret;
> +	}
> +
> +	ret = max_ser_reset(adapter, current_addr);
> +	if (ret) {
> +		dev_err(priv->dev, "Failed to reset serializer: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = max_ser_wait(adapter, power_up_addr);
> +	if (ret) {
> +		dev_err(priv->dev,
> +			"Failed to wait for serializer at 0x%02x: %d\n",
> +			power_up_addr, ret);
> +		return ret;
> +	}
> +
> +	ret = max_ser_change_address(adapter, power_up_addr, new_addr);
> +	if (ret) {
> +		dev_err(priv->dev,
> +			"Failed to change serializer from 0x%02x to 0x%02x: %d\n",
> +			power_up_addr, new_addr, ret);
> +		return ret;
> +	}
> +
> +	ret = max_ser_wait(adapter, new_addr);
> +	if (ret) {
> +		dev_err(priv->dev,
> +			"Failed to wait for serializer at 0x%02x: %d\n",
> +			new_addr, ret);
> +		return ret;
> +	}
> +
> +	if (des->ops->fix_tx_ids) {
> +		ret = max_ser_fix_tx_ids(adapter, new_addr);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return ret;
> +}
> +
> +static int max_des_init(struct max_des_priv *priv)
> +{
> +	struct max_des *des = priv->des;
> +	unsigned int i;
> +	int ret;
> +
> +	if (des->ops->init) {
> +		ret = des->ops->init(des);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (des->ops->set_enable) {
> +		ret = des->ops->set_enable(des, false);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	for (i = 0; i < des->ops->num_phys; i++) {
> +		struct max_des_phy *phy = &des->phys[i];
> +
> +		if (phy->enabled) {
> +			ret = des->ops->init_phy(des, phy);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		ret = des->ops->set_phy_active(des, phy, false);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	for (i = 0; i < des->ops->num_pipes; i++) {
> +		struct max_des_pipe *pipe = &des->pipes[i];
> +		struct max_des_link *link = &des->links[pipe->link_id];
> +
> +		ret = des->ops->set_pipe_enable(des, pipe, false);
> +		if (ret)
> +			return ret;
> +
> +		if (des->ops->set_pipe_tunnel_enable) {
> +			ret = des->ops->set_pipe_tunnel_enable(des, pipe, false);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		if (des->ops->set_pipe_stream_id) {
> +			ret = des->ops->set_pipe_stream_id(des, pipe, pipe->stream_id);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		if (des->ops->set_pipe_link) {
> +			ret = des->ops->set_pipe_link(des, pipe, link);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		ret = max_des_set_pipe_remaps(priv, pipe, pipe->remaps,
> +					      pipe->num_remaps);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (!des->ops->init_link)
> +		return 0;
> +
> +	for (i = 0; i < des->ops->num_links; i++) {
> +		struct max_des_link *link = &des->links[i];
> +
> +		if (!link->enabled)
> +			continue;
> +
> +		ret = des->ops->init_link(des, link);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void max_des_ser_find_version_range(struct max_des *des, int *min, int *max)
> +{
> +	unsigned int i;
> +
> +	*min = MAX_GMSL_MIN;
> +	*max = MAX_GMSL_MAX;
> +
> +	if (!des->ops->needs_single_link_version)
> +		return;
> +
> +	for (i = 0; i < des->ops->num_links; i++) {
> +		struct max_des_link *link = &des->links[i];
> +
> +		if (!link->enabled)
> +			continue;
> +
> +		if (!link->ser_xlate.en)
> +			continue;
> +
> +		*min = *max = link->version;
> +
> +		return;
> +	}
> +}
> +
> +static int max_des_ser_attach_addr(struct max_des_priv *priv, u32 chan_id,
> +				   u16 addr, u16 alias)
> +{
> +	struct max_des *des = priv->des;
> +	struct max_des_link *link = &des->links[chan_id];
> +	int i, min, max;
> +	int ret;
> +
> +	max_des_ser_find_version_range(des, &min, &max);
> +
> +	if (link->ser_xlate.en) {
> +		dev_err(priv->dev, "Serializer for link %u already bound\n",
> +			link->index);
> +		return -EINVAL;
> +	}
> +
> +	for (i = max; i >= min; i--) {
> +		if (!(des->ops->versions & BIT(i)))
> +			continue;
> +
> +		if (des->ops->set_link_version) {
> +			ret = des->ops->set_link_version(des, link, i);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		ret = max_des_init_link_ser_xlate(priv, link, priv->client->adapter,
> +						  addr, alias);
> +		if (!ret)
> +			break;
> +	}
> +
> +	if (ret) {
> +		dev_err(priv->dev, "Cannot find serializer for link %u\n",
> +			link->index);
> +		return -ENOENT;
> +	}
> +
> +	link->version = i;
> +	link->ser_xlate.src = alias;
> +	link->ser_xlate.dst = addr;
> +	link->ser_xlate.en = true;
> +
> +	return 0;
> +}
> +
> +static int max_des_ser_atr_attach_addr(struct i2c_atr *atr, u32 chan_id,
> +				       u16 addr, u16 alias)
> +{
> +	struct max_des_priv *priv = i2c_atr_get_driver_data(atr);
> +
> +	return max_des_ser_attach_addr(priv, chan_id, addr, alias);
> +}
> +
> +static void max_des_ser_atr_detach_addr(struct i2c_atr *atr, u32 chan_id, u16 addr)
> +{
> +	/* Don't do anything. */
> +}
> +
> +static const struct i2c_atr_ops max_des_i2c_atr_ops = {
> +	.attach_addr = max_des_ser_atr_attach_addr,
> +	.detach_addr = max_des_ser_atr_detach_addr,
> +};
> +
> +static void max_des_i2c_atr_deinit(struct max_des_priv *priv)
> +{
> +	struct max_des *des = priv->des;
> +	unsigned int i;
> +
> +	for (i = 0; i < des->ops->num_links; i++) {
> +		struct max_des_link *link = &des->links[i];
> +
> +		/* Deleting adapters that haven't been added does no harm. */
> +		i2c_atr_del_adapter(priv->atr, link->index);
> +	}
> +
> +	i2c_atr_delete(priv->atr);
> +}
> +
> +static int max_des_i2c_atr_init(struct max_des_priv *priv)
> +{
> +	struct max_des *des = priv->des;
> +	unsigned int mask = 0;
> +	unsigned int i;
> +	int ret;
> +
> +	if (!i2c_check_functionality(priv->client->adapter,
> +				     I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
> +		return -ENODEV;
> +
> +	priv->atr = i2c_atr_new(priv->client->adapter, priv->dev,
> +				&max_des_i2c_atr_ops, des->ops->num_links,
> +				I2C_ATR_F_STATIC | I2C_ATR_F_PASSTHROUGH);
> +	if (IS_ERR(priv->atr))
> +		return PTR_ERR(priv->atr);
> +
> +	i2c_atr_set_driver_data(priv->atr, priv);
> +
> +	for (i = 0; i < des->ops->num_links; i++) {
> +		struct max_des_link *link = &des->links[i];
> +		struct i2c_atr_adap_desc desc = {
> +			.chan_id = i,
> +		};
> +
> +		if (!link->enabled)
> +			continue;
> +
> +		ret = i2c_atr_add_adapter(priv->atr, &desc);
> +		if (ret)
> +			goto err_add_adapters;
> +	}
> +
> +	for (i = 0; i < des->ops->num_links; i++) {
> +		struct max_des_link *link = &des->links[i];
> +
> +		if (!link->enabled)
> +			continue;
> +
> +		mask |= BIT(link->index);
> +	}
> +
> +	return des->ops->select_links(des, mask);
> +
> +err_add_adapters:
> +	max_des_i2c_atr_deinit(priv);
> +
> +	return ret;
> +}
> +
> +static void max_des_i2c_mux_deinit(struct max_des_priv *priv)
> +{
> +	i2c_mux_del_adapters(priv->mux);
> +	bus_unregister_notifier(&i2c_bus_type, &priv->i2c_nb);
> +}
> +
> +static int max_des_i2c_mux_bus_notifier_call(struct notifier_block *nb,
> +					     unsigned long event, void *device)
> +{
> +	struct max_des_priv *priv = container_of(nb, struct max_des_priv, i2c_nb);
> +	struct device *dev = device;
> +	struct i2c_client *client;
> +	u32 chan_id;
> +
> +	/*
> +	 * Ideally, we would want to negotiate the GMSL version on
> +	 * BUS_NOTIFY_ADD_DEVICE, but the adapters list is only populated with
> +	 * the new adapter after BUS_NOTIFY_ADD_DEVICE is issued.
> +	 */
> +	if (event != BUS_NOTIFY_BIND_DRIVER)
> +		return NOTIFY_DONE;
> +
> +	client = i2c_verify_client(dev);
> +	if (!client)
> +		return NOTIFY_DONE;
> +
> +	for (chan_id = 0; chan_id < priv->mux->max_adapters; ++chan_id) {
> +		if (client->adapter == priv->mux->adapter[chan_id])
> +			break;
> +	}
> +
> +	if (chan_id == priv->mux->max_adapters)
> +		return NOTIFY_DONE;
> +
> +	max_des_ser_attach_addr(priv, chan_id, client->addr, client->addr);
> +
> +	return NOTIFY_DONE;
> +}
> +
> +static int max_des_i2c_mux_select(struct i2c_mux_core *muxc, u32 chan)
> +{
> +	struct max_des_priv *priv = i2c_mux_priv(muxc);
> +	struct max_des *des = priv->des;
> +
> +	if (!des->ops->select_links)
> +		return 0;
> +
> +	return des->ops->select_links(des, BIT(chan));
> +}
> +
> +static int max_des_i2c_mux_init(struct max_des_priv *priv)
> +{
> +	struct max_des *des = priv->des;
> +	u32 flags = I2C_MUX_LOCKED;
> +	unsigned int i;
> +	int ret;
> +
> +	if (des->ops->num_links == 1)
> +		flags |= I2C_MUX_GATE;
> +
> +	priv->mux = i2c_mux_alloc(priv->client->adapter, priv->dev,
> +				  des->ops->num_links, 0, flags,
> +				  max_des_i2c_mux_select, NULL);
> +	if (!priv->mux)
> +		return -ENOMEM;
> +
> +	priv->mux->priv = priv;
> +
> +	priv->i2c_nb.notifier_call = max_des_i2c_mux_bus_notifier_call;
> +	ret = bus_register_notifier(&i2c_bus_type, &priv->i2c_nb);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < des->ops->num_links; i++) {
> +		struct max_des_link *link = &des->links[i];
> +
> +		if (!link->enabled)
> +			continue;
> +
> +		ret = i2c_mux_add_adapter(priv->mux, 0, i);
> +		if (ret)
> +			goto err_add_adapters;
> +	}
> +
> +	return 0;
> +
> +err_add_adapters:
> +	max_des_i2c_mux_deinit(priv);
> +
> +	return ret;
> +}
> +
> +static void max_des_i2c_adapter_deinit(struct max_des_priv *priv)
> +{
> +	struct max_des *des = priv->des;
> +
> +	if (des->ops->use_atr)
> +		return max_des_i2c_atr_deinit(priv);
> +	else
> +		return max_des_i2c_mux_deinit(priv);
> +}
> +
> +static int max_des_i2c_adapter_init(struct max_des_priv *priv)
> +{
> +	struct max_des *des = priv->des;
> +
> +	if (des->ops->use_atr)
> +		return max_des_i2c_atr_init(priv);
> +	else
> +		return max_des_i2c_mux_init(priv);
> +
> +	return 0;
> +}
> +
> +static int max_des_set_tpg_fmt(struct v4l2_subdev *sd,
> +			       struct v4l2_subdev_state *state,
> +			       struct v4l2_subdev_format *format)
> +{
> +	struct v4l2_mbus_framefmt *fmt = &format->format;
> +	struct max_des_priv *priv = v4l2_get_subdevdata(sd);
> +	struct max_des *des = priv->des;
> +	const struct max_tpg_entry *entry;
> +	struct v4l2_fract *in;
> +
> +	if (format->stream != MAX_SERDES_TPG_STREAM)
> +		return -EINVAL;
> +
> +	entry = max_des_find_tpg_entry(des, 0, fmt->width, fmt->height,
> +				       fmt->code, 0, 0);
> +	if (!entry)
> +		return -EINVAL;
> +
> +	in = v4l2_subdev_state_get_interval(state, format->pad, format->stream);
> +	if (!in)
> +		return -EINVAL;
> +
> +	in->numerator = entry->interval.numerator;
> +	in->denominator = entry->interval.denominator;
> +
> +	return 0;
> +}
> +
> +static int max_des_set_fmt(struct v4l2_subdev *sd,
> +			   struct v4l2_subdev_state *state,
> +			   struct v4l2_subdev_format *format)
> +{
> +	struct max_des_priv *priv = v4l2_get_subdevdata(sd);
> +	struct max_des *des = priv->des;
> +	struct v4l2_mbus_framefmt *fmt;
> +	int ret;
> +
> +	if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && des->active)
> +		return -EBUSY;
> +
> +	/* No transcoding, source and sink formats must match. */
> +	if (max_des_pad_is_source(des, format->pad))
> +		return v4l2_subdev_get_fmt(sd, state, format);
> +
> +	if (max_des_pad_is_tpg(des, format->pad)) {
> +		ret = max_des_set_tpg_fmt(sd, state, format);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
> +	if (!fmt)
> +		return -EINVAL;
> +
> +	*fmt = format->format;
> +
> +	fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
> +							   format->stream);
> +	if (!fmt)
> +		return -EINVAL;
> +
> +	*fmt = format->format;
> +
> +	return 0;
> +}
> +
> +static int max_des_enum_frame_interval(struct v4l2_subdev *sd,
> +				       struct v4l2_subdev_state *state,
> +				       struct v4l2_subdev_frame_interval_enum *fie)
> +{
> +	struct max_des_priv *priv = v4l2_get_subdevdata(sd);
> +	struct max_des *des = priv->des;
> +	const struct max_tpg_entry *entry;
> +
> +	if (!max_des_pad_is_tpg(des, fie->pad) ||
> +	    fie->stream != MAX_SERDES_TPG_STREAM)
> +		return -EINVAL;
> +
> +	entry = max_des_find_tpg_entry(des, fie->index, fie->width, fie->height,
> +				       fie->code, fie->interval.denominator,
> +				       fie->interval.numerator);
> +	if (!entry)
> +		return -EINVAL;
> +
> +	fie->interval.numerator = entry->interval.numerator;
> +	fie->interval.denominator = entry->interval.denominator;
> +
> +	return 0;
> +}
> +
> +static int max_des_set_frame_interval(struct v4l2_subdev *sd,
> +				      struct v4l2_subdev_state *state,
> +				      struct v4l2_subdev_frame_interval *fi)
> +{
> +	struct max_des_priv *priv = v4l2_get_subdevdata(sd);
> +	struct max_des *des = priv->des;
> +	const struct max_tpg_entry *entry;
> +	struct v4l2_mbus_framefmt *fmt;
> +	struct v4l2_fract *in;
> +
> +	if (!max_des_pad_is_tpg(des, fi->pad) ||
> +	    fi->stream != MAX_SERDES_TPG_STREAM)
> +		return -EINVAL;
> +
> +	if (fi->which == V4L2_SUBDEV_FORMAT_ACTIVE && des->active)
> +		return -EBUSY;
> +
> +	fmt = v4l2_subdev_state_get_format(state, fi->pad, fi->stream);
> +	if (!fmt)
> +		return -EINVAL;
> +
> +	entry = max_des_find_tpg_entry(des, 0, fmt->width, fmt->height,
> +				       fmt->code, fi->interval.denominator,
> +				       fi->interval.numerator);
> +	if (!entry)
> +		return -EINVAL;
> +
> +	in = v4l2_subdev_state_get_interval(state, fi->pad, fi->stream);
> +	if (!in)
> +		return -EINVAL;
> +
> +	in->numerator = fi->interval.numerator;
> +	in->denominator = fi->interval.denominator;
> +
> +	return 0;
> +}
> +
> +static int max_des_log_status(struct v4l2_subdev *sd)
> +{
> +	struct max_des_priv *priv = v4l2_get_subdevdata(sd);
> +	struct max_des *des = priv->des;
> +	unsigned int i, j;
> +	int ret;
> +
> +	v4l2_info(sd, "active: %u\n", des->active);
> +	v4l2_info(sd, "mode: %s", max_gmsl_mode_str(des->mode));
> +	if (des->ops->set_tpg) {
> +		const struct max_tpg_entry *entry = des->tpg_entry;
> +
> +		if (entry) {
> +			v4l2_info(sd, "tpg: %ux%u@...%u, code: %u, dt: %u, bpp: %u\n",
> +				  entry->width, entry->height,
> +				  entry->interval.numerator,
> +				  entry->interval.denominator,
> +				  entry->code, entry->dt,  entry->bpp);
> +		} else {
> +			v4l2_info(sd, "tpg: disabled\n");
> +		}
> +	}
> +	if (des->ops->log_status) {
> +		ret = des->ops->log_status(des, sd->name);
> +		if (ret)
> +			return ret;
> +	}
> +	v4l2_info(sd, "\n");
> +
> +	for (i = 0; i < des->ops->num_links; i++) {
> +		struct max_des_link *link = &des->links[i];
> +
> +		v4l2_info(sd, "link: %u\n", link->index);
> +		v4l2_info(sd, "\tenabled: %u\n", link->enabled);
> +
> +		if (!link->enabled) {
> +			v4l2_info(sd, "\n");
> +			continue;
> +		}
> +
> +		v4l2_info(sd, "\tversion: %s\n", max_gmsl_version_str(link->version));
> +		v4l2_info(sd, "\tser_xlate: en: %u, src: 0x%02x dst: 0x%02x\n",
> +			  link->ser_xlate.en, link->ser_xlate.src,
> +			  link->ser_xlate.dst);
> +		v4l2_info(sd, "\n");
> +	}
> +
> +	for (i = 0; i < des->ops->num_pipes; i++) {
> +		struct max_des_pipe *pipe = &des->pipes[i];
> +
> +		v4l2_info(sd, "pipe: %u\n", pipe->index);
> +		v4l2_info(sd, "\tenabled: %u\n", pipe->enabled);
> +		if (pipe->phy_id == des->ops->num_phys ||
> +		    (priv->unused_phy && pipe->phy_id == priv->unused_phy->index))
> +			v4l2_info(sd, "\tphy_id: invalid\n");
> +		else
> +			v4l2_info(sd, "\tphy_id: %u\n", pipe->phy_id);
> +		v4l2_info(sd, "\tlink_id: %u\n", pipe->link_id);
> +		if (des->ops->set_pipe_stream_id)
> +			v4l2_info(sd, "\tstream_id: %u\n", pipe->stream_id);
> +		if (des->ops->set_pipe_mode) {
> +			v4l2_info(sd, "\tdbl8: %u\n", pipe->mode.dbl8);
> +			v4l2_info(sd, "\tdbl8mode: %u\n", pipe->mode.dbl8mode);
> +			v4l2_info(sd, "\tdbl10: %u\n", pipe->mode.dbl10);
> +			v4l2_info(sd, "\tdbl10mode: %u\n", pipe->mode.dbl10mode);
> +			v4l2_info(sd, "\tdbl12: %u\n", pipe->mode.dbl12);
> +		}
> +		if (des->ops->set_pipe_remap) {
> +			v4l2_info(sd, "\tremaps: %u\n", pipe->num_remaps);
> +			for (j = 0; j < pipe->num_remaps; j++) {
> +				struct max_des_remap *remap = &pipe->remaps[j];
> +
> +				v4l2_info(sd, "\t\tremap: from: vc: %u, dt: 0x%02x\n",
> +					  remap->from_vc, remap->from_dt);
> +				v4l2_info(sd, "\t\t       to:   vc: %u, dt: 0x%02x, phy: %u\n",
> +					  remap->to_vc, remap->to_dt, remap->phy);
> +			}
> +		}
> +		if (des->ops->set_pipe_vc_remap) {
> +			v4l2_info(sd, "\tvc_remaps: %u\n", pipe->num_vc_remaps);
> +			for (j = 0; j < pipe->num_vc_remaps; j++) {
> +				v4l2_info(sd, "\t\tvc_remap: src: %u, dst: %u\n",
> +					  pipe->vc_remaps[j].src, pipe->vc_remaps[j].dst);
> +			}
> +		}
> +		if (des->ops->log_pipe_status) {
> +			ret = des->ops->log_pipe_status(des, pipe, sd->name);
> +			if (ret)
> +				return ret;
> +		}
> +		v4l2_info(sd, "\n");
> +	}
> +
> +	for (i = 0; i < des->ops->num_phys; i++) {
> +		struct max_des_phy *phy = &des->phys[i];
> +
> +		v4l2_info(sd, "phy: %u\n", phy->index);
> +		v4l2_info(sd, "\tenabled: %u\n", phy->enabled);
> +
> +		if (!phy->enabled) {
> +			v4l2_info(sd, "\n");
> +			continue;
> +		}
> +
> +		v4l2_info(sd, "\tactive: %u\n", phy->active);
> +		v4l2_info(sd, "\tlink_frequency: %llu\n", phy->link_frequency);
> +		v4l2_info(sd, "\tnum_data_lanes: %u\n", phy->mipi.num_data_lanes);
> +		v4l2_info(sd, "\tclock_lane: %u\n", phy->mipi.clock_lane);
> +		if (des->ops->set_phy_mode) {
> +			v4l2_info(sd, "\talt_mem_map8: %u\n", phy->mode.alt_mem_map8);
> +			v4l2_info(sd, "\talt2_mem_map8: %u\n", phy->mode.alt2_mem_map8);
> +			v4l2_info(sd, "\talt_mem_map10: %u\n", phy->mode.alt_mem_map10);
> +			v4l2_info(sd, "\talt_mem_map12: %u\n", phy->mode.alt_mem_map12);
> +		}
> +		if (des->ops->log_phy_status) {
> +			ret = des->ops->log_phy_status(des, phy, sd->name);
> +			if (ret)
> +				return ret;
> +		}
> +		v4l2_info(sd, "\n");
> +	}
> +
> +	return 0;
> +}
> +
> +static int max_des_get_frame_desc_state(struct v4l2_subdev *sd,
> +					struct v4l2_subdev_state *state,
> +					struct v4l2_mbus_frame_desc *fd,
> +					unsigned int pad)
> +{
> +	struct max_des_remap_context context = { 0 };
> +	struct max_des_priv *priv = sd_to_priv(sd);
> +	struct v4l2_subdev_route *route;
> +	int ret;
> +
> +	fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
> +
> +	ret = max_des_populate_remap_context(priv, &context, state);
> +	if (ret)
> +		return ret;
> +
> +	for_each_active_route(&state->routing, route) {
> +		struct max_des_route_hw hw;
> +		unsigned int dst_vc_id;
> +
> +		if (pad != route->source_pad)
> +			continue;
> +
> +		ret = max_des_route_to_hw(priv, state, route, &hw);
> +		if (ret)
> +			return ret;
> +
> +		ret = max_des_get_src_dst_vc_id(&context, hw.pipe->index, hw.phy->index,
> +						hw.entry.bus.csi2.vc, &dst_vc_id);
> +		if (ret)
> +			return ret;
> +
> +		hw.entry.bus.csi2.vc = dst_vc_id;
> +		hw.entry.stream = route->source_stream;
> +
> +		fd->entry[fd->num_entries++] = hw.entry;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max_des_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
> +				  struct v4l2_mbus_frame_desc *fd)
> +{
> +	struct max_des_priv *priv = sd_to_priv(sd);
> +	struct v4l2_subdev_state *state;
> +	int ret;
> +
> +	state = v4l2_subdev_lock_and_get_active_state(&priv->sd);
> +
> +	ret = max_des_get_frame_desc_state(sd, state, fd, pad);
> +
> +	v4l2_subdev_unlock_state(state);
> +
> +	return ret;
> +}
> +
> +static int max_des_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad,
> +				   struct v4l2_mbus_config *cfg)
> +{
> +	struct max_des_priv *priv = sd_to_priv(sd);
> +	struct max_des *des = priv->des;
> +	struct max_des_phy *phy;
> +
> +	phy = max_des_pad_to_phy(des, pad);
> +	if (!phy)
> +		return -EINVAL;
> +
> +	cfg->type = phy->bus_type;
> +	cfg->bus.mipi_csi2 = phy->mipi;
> +	cfg->link_freq = phy->link_frequency;
> +
> +	return 0;
> +}
> +
> +static int max_des_set_tpg_routing(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state,
> +				   struct v4l2_subdev_krouting *routing)
> +{
> +	struct max_des_priv *priv = sd_to_priv(sd);
> +	struct max_des *des = priv->des;
> +	const struct max_tpg_entry *entry;
> +	struct v4l2_mbus_framefmt fmt = { 0 };
> +	int ret;
> +
> +	ret = max_validate_tpg_routing(routing);
> +	if (ret)
> +		return ret;
> +
> +	entry = &des->ops->tpg_entries.entries[0];
> +
> +	fmt.width = entry->width;
> +	fmt.height = entry->height;
> +	fmt.code = entry->code;
> +
> +	return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &fmt);
> +}
> +
> +static int max_des_set_routing(struct v4l2_subdev *sd,
> +			       struct v4l2_subdev_state *state,
> +			       enum v4l2_subdev_format_whence which,
> +			       struct v4l2_subdev_krouting *routing)
> +{
> +	struct max_des_priv *priv = sd_to_priv(sd);
> +	struct max_des *des = priv->des;
> +	struct v4l2_subdev_route *route;
> +	bool is_tpg = false;
> +	int ret;
> +
> +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && des->active)
> +		return -EBUSY;
> +
> +	/*
> +	 * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until
> +	 * frame desc is made dynamically allocated.
> +	 */
> +
> +	if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX)
> +		return -E2BIG;
> +
> +	ret = v4l2_subdev_routing_validate(sd, routing,
> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1 |
> +					   V4L2_SUBDEV_ROUTING_NO_SINK_STREAM_MIX);
> +	if (ret)
> +		return ret;
> +
> +	for_each_active_route(routing, route) {
> +		if (max_des_pad_is_tpg(des, route->sink_pad)) {
> +			is_tpg = true;
> +			break;
> +		}
> +	}
> +
> +	if (is_tpg)
> +		return max_des_set_tpg_routing(sd, state, routing);
> +
> +	return v4l2_subdev_set_routing(sd, state, routing);
> +}
> +
> +static int max_des_update_link(struct max_des_priv *priv,
> +			       struct max_des_remap_context *context,
> +			       struct max_des_link *link,
> +			       struct v4l2_subdev_state *state,
> +			       u64 *streams_masks)
> +{
> +	struct max_des *des = priv->des;
> +	struct max_des_pipe *pipe;
> +	int ret;
> +
> +	pipe = max_des_find_link_pipe(des, link);
> +	if (!pipe)
> +		return -ENOENT;
> +
> +	ret = max_des_update_pipe(priv, context, pipe, state, streams_masks);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int max_des_update_phy(struct max_des_priv *priv,
> +			      u32 pad, u64 *streams_masks)
> +{
> +	bool enable_changed = !streams_masks[pad] != !priv->streams_masks[pad];
> +	bool enable = !!streams_masks[pad];
> +	struct max_des *des = priv->des;
> +	struct max_des_phy *phy;
> +	int ret;
> +
> +	phy = max_des_pad_to_phy(des, pad);
> +	if (!phy)
> +		return -EINVAL;
> +
> +	if (enable_changed) {
> +		ret = max_des_set_phy_active(des, phy, enable);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max_des_update_tpg(struct max_des_priv *priv,
> +			      struct v4l2_subdev_state *state,
> +			      u64 *streams_masks)
> +{
> +	const struct max_tpg_entry *entry = NULL;
> +	struct max_des *des = priv->des;
> +	struct v4l2_subdev_route *route;
> +	int ret;
> +
> +	for_each_active_route(&state->routing, route) {
> +		struct max_des_route_hw hw;
> +
> +		if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
> +			continue;
> +
> +		ret = max_des_route_to_hw(priv, state, route, &hw);
> +		if (ret)
> +			return ret;
> +
> +		if (!hw.is_tpg)
> +			continue;
> +
> +		entry = max_des_find_state_tpg_entry(des, state, route->sink_pad);
> +		break;
> +	}
> +
> +	ret = des->ops->set_tpg(des, entry);
> +	if (ret)
> +		return ret;
> +
> +	des->tpg_entry = entry;
> +
> +	return 0;
> +}
> +
> +static int max_des_update_active(struct max_des_priv *priv, u64 *streams_masks,
> +				 bool expected_active)
> +{
> +	struct max_des *des = priv->des;
> +	bool active = false;
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < des->ops->num_phys; i++) {
> +		struct max_des_phy *phy = &des->phys[i];
> +		u32 pad = max_des_phy_to_pad(des, phy);
> +
> +		if (streams_masks[pad]) {
> +			active = true;
> +			break;
> +		}
> +	}
> +
> +	if (active != expected_active || des->active == active)
> +		return 0;
> +
> +	if (des->ops->set_enable) {
> +		ret = des->ops->set_enable(des, active);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	des->active = active;
> +
> +	return 0;
> +}
> +
> +static int max_des_update_links(struct max_des_priv *priv,
> +				struct max_des_remap_context *context,
> +				struct v4l2_subdev_state *state,
> +				u64 *streams_masks)
> +{
> +	struct max_des *des = priv->des;
> +	unsigned int failed_update_link_id = des->ops->num_links;
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < des->ops->num_links; i++) {
> +		struct max_des_link *link = &des->links[i];
> +
> +		ret = max_des_update_link(priv, context, link, state,
> +					  streams_masks);
> +		if (ret) {
> +			failed_update_link_id = i;
> +			goto err;
> +		}
> +	}
> +
> +	return 0;
> +
> +err:
> +	for (i = 0; i < failed_update_link_id; i++) {
> +		struct max_des_link *link = &des->links[i];
> +
> +		max_des_update_link(priv, context, link, state,
> +				    priv->streams_masks);
> +	}
> +
> +	return ret;
> +}
> +
> +static int max_des_enable_disable_streams(struct max_des_priv *priv,
> +					  struct v4l2_subdev_state *state,
> +					  u32 pad, u64 updated_streams_mask,
> +					  bool enable)
> +{
> +	struct max_des *des = priv->des;
> +
> +	return max_xlate_enable_disable_streams(priv->sources, 0, state,
> +						pad, updated_streams_mask, 0,
> +						des->ops->num_links, enable);
> +}
> +
> +static int max_des_update_streams(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *state,
> +				  u32 pad, u64 updated_streams_mask, bool enable)
> +{
> +	struct max_des_priv *priv = v4l2_get_subdevdata(sd);
> +	struct max_des_remap_context context = { 0 };
> +	struct max_des_mode_context mode_context = { 0 };
> +	struct max_des *des = priv->des;
> +	unsigned int num_pads = max_des_num_pads(des);
> +	u64 *streams_masks;
> +	int ret;
> +
> +	ret = max_des_populate_remap_context(priv, &context, state);
> +	if (ret)
> +		return ret;
> +
> +	ret = max_des_populate_mode_context(priv, &mode_context, state, context.mode);
> +	if (ret)
> +		return ret;
> +
> +	ret = max_get_streams_masks(priv->dev, state, pad, updated_streams_mask,
> +				    num_pads, priv->streams_masks, &streams_masks,
> +				    enable);
> +	if (ret)
> +		return ret;
> +
> +	ret = max_des_set_pipes_phy(priv, &context);
> +	if (ret)
> +		goto err_free_streams_masks;
> +
> +	ret = max_des_set_tunnel(priv, &context);
> +	if (ret)
> +		goto err_free_streams_masks;
> +
> +	ret = max_des_set_modes(priv, &mode_context);
> +	if (ret)
> +		goto err_free_streams_masks;
> +
> +	ret = max_des_set_vc_remaps(priv, &context, state, streams_masks);
> +	if (ret)
> +		return ret;
> +
> +	ret = max_des_set_pipes_stream_id(priv);
> +	if (ret)
> +		goto err_free_streams_masks;
> +
> +	if (!enable) {
> +		ret = max_des_enable_disable_streams(priv, state, pad,
> +						     updated_streams_mask, enable);
> +		if (ret)
> +			goto err_free_streams_masks;
> +	}
> +
> +	ret = max_des_update_active(priv, streams_masks, false);
> +	if (ret)
> +		goto err_revert_streams_disable;
> +
> +	ret = max_des_update_links(priv, &context, state, streams_masks);
> +	if (ret)
> +		goto err_revert_active_disable;
> +
> +	ret = max_des_update_phy(priv, pad, streams_masks);
> +	if (ret)
> +		goto err_revert_links_update;
> +
> +	ret = max_des_update_tpg(priv, state, streams_masks);
> +	if (ret)
> +		goto err_revert_phy_update;
> +
> +	ret = max_des_update_active(priv, streams_masks, true);
> +	if (ret)
> +		goto err_revert_tpg_update;
> +
> +	if (enable) {
> +		ret = max_des_enable_disable_streams(priv, state, pad,
> +						     updated_streams_mask, enable);
> +		if (ret)
> +			goto err_revert_active_enable;
> +	}
> +
> +	devm_kfree(priv->dev, priv->streams_masks);
> +	priv->streams_masks = streams_masks;
> +
> +	return 0;
> +
> +err_revert_active_enable:
> +	max_des_update_active(priv, priv->streams_masks, false);
> +
> +err_revert_tpg_update:
> +	max_des_update_tpg(priv, state, priv->streams_masks);
> +
> +err_revert_phy_update:
> +	max_des_update_phy(priv, pad, priv->streams_masks);
> +
> +err_revert_links_update:
> +	max_des_update_links(priv, &context, state, priv->streams_masks);
> +
> +err_revert_active_disable:
> +	max_des_update_active(priv, priv->streams_masks, true);
> +
> +err_revert_streams_disable:
> +	if (!enable)
> +		max_des_enable_disable_streams(priv, state, pad,
> +					       updated_streams_mask, !enable);
> +
> +err_free_streams_masks:
> +	devm_kfree(priv->dev, streams_masks);
> +
> +	return ret;
> +}
> +
> +static int max_des_enable_streams(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *state,
> +				  u32 pad, u64 streams_mask)
> +{
> +	return max_des_update_streams(sd, state, pad, streams_mask, true);
> +}
> +
> +static int max_des_disable_streams(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state,
> +				   u32 pad, u64 streams_mask)
> +{
> +	return max_des_update_streams(sd, state, pad, streams_mask, false);
> +}
> +
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> +static int max_des_g_register(struct v4l2_subdev *sd,
> +			      struct v4l2_dbg_register *reg)
> +{
> +	struct max_des_priv *priv = v4l2_get_subdevdata(sd);
> +	struct max_des *des = priv->des;
> +	unsigned int val;
> +	int ret;
> +
> +	ret = des->ops->reg_read(des, reg->reg, &val);
> +	if (ret)
> +		return ret;
> +
> +	reg->val = val;
> +	reg->size = 1;
> +
> +	return 0;
> +}
> +
> +static int max_des_s_register(struct v4l2_subdev *sd,
> +			      const struct v4l2_dbg_register *reg)
> +{
> +	struct max_des_priv *priv = v4l2_get_subdevdata(sd);
> +	struct max_des *des = priv->des;
> +
> +	return des->ops->reg_write(des, reg->reg, reg->val);
> +}
> +#endif
> +
> +static const struct v4l2_subdev_core_ops max_des_core_ops = {
> +	.log_status = max_des_log_status,
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> +	.g_register = max_des_g_register,
> +	.s_register = max_des_s_register,
> +#endif
> +};
> +
> +static const struct v4l2_subdev_pad_ops max_des_pad_ops = {
> +	.enable_streams = max_des_enable_streams,
> +	.disable_streams = max_des_disable_streams,
> +
> +	.set_routing = max_des_set_routing,
> +	.get_frame_desc = max_des_get_frame_desc,
> +
> +	.get_mbus_config = max_des_get_mbus_config,
> +
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = max_des_set_fmt,
> +
> +	.enum_frame_interval = max_des_enum_frame_interval,
> +	.get_frame_interval = v4l2_subdev_get_frame_interval,
> +	.set_frame_interval = max_des_set_frame_interval,
> +};
> +
> +static const struct v4l2_subdev_ops max_des_subdev_ops = {
> +	.core = &max_des_core_ops,
> +	.pad = &max_des_pad_ops,
> +};
> +
> +static const struct media_entity_operations max_des_media_ops = {
> +	.link_validate = v4l2_subdev_link_validate,
> +	.get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
> +};
> +
> +static int max_des_notify_bound(struct v4l2_async_notifier *nf,
> +				struct v4l2_subdev *subdev,
> +				struct v4l2_async_connection *base_asc)
> +{
> +	struct max_des_priv *priv = nf_to_priv(nf);
> +	struct max_asc *asc = asc_to_max(base_asc);
> +	struct max_source *source = asc->source;
> +	struct max_des *des = priv->des;
> +	struct max_des_link *link = &des->links[source->index];
> +	u32 pad = max_des_link_to_pad(des, link);
> +	int ret;
> +
> +	ret = media_entity_get_fwnode_pad(&subdev->entity,
> +					  source->ep_fwnode,
> +					  MEDIA_PAD_FL_SOURCE);
> +	if (ret < 0) {
> +		dev_err(priv->dev, "Failed to find pad for %s\n", subdev->name);
> +		return ret;
> +	}
> +
> +	source->sd = subdev;
> +	source->pad = ret;
> +
> +	ret = media_create_pad_link(&source->sd->entity, source->pad,
> +				    &priv->sd.entity, pad,
> +				    MEDIA_LNK_FL_ENABLED |
> +				    MEDIA_LNK_FL_IMMUTABLE);
> +	if (ret) {
> +		dev_err(priv->dev, "Unable to link %s:%u -> %s:%u\n",
> +			source->sd->name, source->pad, priv->sd.name, pad);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void max_des_notify_unbind(struct v4l2_async_notifier *nf,
> +				  struct v4l2_subdev *subdev,
> +				  struct v4l2_async_connection *base_asc)
> +{
> +	struct max_asc *asc = asc_to_max(base_asc);
> +	struct max_source *source = asc->source;
> +
> +	source->sd = NULL;
> +}
> +
> +static const struct v4l2_async_notifier_operations max_des_notify_ops = {
> +	.bound = max_des_notify_bound,
> +	.unbind = max_des_notify_unbind,
> +};
> +
> +static int max_des_v4l2_notifier_register(struct max_des_priv *priv)
> +{
> +	struct max_des *des = priv->des;
> +	unsigned int i;
> +	int ret;
> +
> +	v4l2_async_subdev_nf_init(&priv->nf, &priv->sd);
> +
> +	for (i = 0; i < des->ops->num_links; i++) {
> +		struct max_des_link *link = &des->links[i];
> +		struct max_source *source;
> +		struct max_asc *asc;
> +
> +		if (!link->enabled)
> +			continue;
> +
> +		source = max_des_get_link_source(priv, link);
> +		if (!source->ep_fwnode)
> +			continue;
> +
> +		asc = v4l2_async_nf_add_fwnode(&priv->nf, source->ep_fwnode,
> +					       struct max_asc);
> +		if (IS_ERR(asc)) {
> +			dev_err(priv->dev,
> +				"Failed to add subdev for source %u: %pe", i,
> +				asc);
> +
> +			v4l2_async_nf_cleanup(&priv->nf);
> +
> +			return PTR_ERR(asc);
> +		}
> +
> +		asc->source = source;
> +	}
> +
> +	priv->nf.ops = &max_des_notify_ops;
> +
> +	ret = v4l2_async_nf_register(&priv->nf);
> +	if (ret) {
> +		dev_err(priv->dev, "Failed to register subdev notifier");
> +		v4l2_async_nf_cleanup(&priv->nf);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void max_des_v4l2_notifier_unregister(struct max_des_priv *priv)
> +{
> +	v4l2_async_nf_unregister(&priv->nf);
> +	v4l2_async_nf_cleanup(&priv->nf);
> +}
> +
> +static int max_des_v4l2_register(struct max_des_priv *priv)
> +{
> +	struct v4l2_subdev *sd = &priv->sd;
> +	struct max_des *des = priv->des;
> +	void *data = i2c_get_clientdata(priv->client);
> +	unsigned int num_pads = max_des_num_pads(des);
> +	unsigned int i;
> +	int ret;
> +
> +	v4l2_i2c_subdev_init(sd, priv->client, &max_des_subdev_ops);
> +	i2c_set_clientdata(priv->client, data);
> +	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> +	sd->entity.ops = &max_des_media_ops;
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
> +
> +	for (i = 0; i < num_pads; i++) {
> +		if (max_des_pad_is_sink(des, i))
> +			priv->pads[i].flags = MEDIA_PAD_FL_SINK;
> +		else if (max_des_pad_is_source(des, i))
> +			priv->pads[i].flags = MEDIA_PAD_FL_SOURCE;
> +		else if (max_des_pad_is_tpg(des, i))
> +			priv->pads[i].flags = MEDIA_PAD_FL_SINK |
> +					      MEDIA_PAD_FL_INTERNAL;
> +		else
> +			return -EINVAL;
> +	}
> +
> +	v4l2_set_subdevdata(sd, priv);
> +
> +	ret = media_entity_pads_init(&sd->entity, num_pads, priv->pads);
> +	if (ret)
> +		return ret;
> +
> +	ret = max_des_v4l2_notifier_register(priv);
> +	if (ret)
> +		goto err_media_entity_cleanup;
> +
> +	ret = v4l2_subdev_init_finalize(sd);
> +	if (ret)
> +		goto err_nf_cleanup;
> +
> +	ret = v4l2_async_register_subdev(sd);
> +	if (ret)
> +		goto err_sd_cleanup;
> +
> +	return 0;
> +
> +err_sd_cleanup:
> +	v4l2_subdev_cleanup(sd);
> +err_nf_cleanup:
> +	max_des_v4l2_notifier_unregister(priv);
> +err_media_entity_cleanup:
> +	media_entity_cleanup(&sd->entity);
> +
> +	return ret;
> +}
> +
> +static void max_des_v4l2_unregister(struct max_des_priv *priv)
> +{
> +	struct v4l2_subdev *sd = &priv->sd;
> +
> +	v4l2_async_unregister_subdev(sd);
> +	v4l2_subdev_cleanup(sd);
> +	max_des_v4l2_notifier_unregister(priv);
> +	media_entity_cleanup(&sd->entity);
> +}
> +
> +static int max_des_update_pocs(struct max_des_priv *priv, bool enable)
> +{
> +	struct max_des *des = priv->des;
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < des->ops->num_links; i++) {
> +		struct max_des_link *link = &des->links[i];
> +
> +		if (!link->enabled)
> +			continue;
> +
> +		if (!priv->pocs[i])
> +			continue;
> +
> +		if (enable)
> +			ret = regulator_enable(priv->pocs[i]);
> +		else
> +			ret = regulator_disable(priv->pocs[i]);
> +
> +		if (ret) {
> +			dev_err(priv->dev,
> +				"Failed to set POC supply to %u: %u\n",
> +				enable, ret);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int max_des_parse_sink_dt_endpoint(struct max_des_priv *priv,
> +					  struct max_des_link *link,
> +					  struct max_source *source,
> +					  struct fwnode_handle *fwnode)
> +{
> +	struct max_des *des = priv->des;
> +	u32 pad = max_des_link_to_pad(des, link);
> +	unsigned int index = link->index;
> +	struct fwnode_handle *ep;
> +	char poc_name[10];
> +	int ret;
> +
> +	ep = fwnode_graph_get_endpoint_by_id(fwnode, pad, 0, 0);
> +	if (!ep)
> +		return 0;
> +
> +	source->ep_fwnode = fwnode_graph_get_remote_endpoint(ep);
> +	fwnode_handle_put(ep);
> +	if (!source->ep_fwnode) {
> +		dev_err(priv->dev,
> +			"Failed to get remote endpoint on port %u\n", pad);
> +		return -ENODEV;
> +	}
> +
> +	snprintf(poc_name, sizeof(poc_name), "port%u-poc", index);
> +	priv->pocs[index] = devm_regulator_get_optional(priv->dev, poc_name);
> +	if (IS_ERR(priv->pocs[index])) {
> +		ret = PTR_ERR(priv->pocs[index]);
> +		if (ret != -ENODEV) {
> +			dev_err(priv->dev,
> +				"Failed to get POC supply on port %u: %d\n",
> +				index, ret);
> +			goto err_put_source_ep_fwnode;
> +		}
> +
> +		priv->pocs[index] = NULL;
> +	}
> +
> +	link->enabled = true;
> +
> +	return 0;
> +
> +err_put_source_ep_fwnode:
> +	fwnode_handle_put(source->ep_fwnode);
> +
> +	return ret;
> +}
> +
> +static int max_des_parse_src_dt_endpoint(struct max_des_priv *priv,
> +					 struct max_des_phy *phy,
> +					 struct fwnode_handle *fwnode)
> +{
> +	struct max_des *des = priv->des;
> +	u32 pad = max_des_phy_to_pad(des, phy);
> +	struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = V4L2_MBUS_UNKNOWN };
> +	struct v4l2_mbus_config_mipi_csi2 *mipi = &v4l2_ep.bus.mipi_csi2;
> +	enum v4l2_mbus_type bus_type;
> +	struct fwnode_handle *ep;
> +	u64 link_frequency;
> +	unsigned int i;
> +	int ret;
> +
> +	ep = fwnode_graph_get_endpoint_by_id(fwnode, pad, 0, 0);
> +	if (!ep)
> +		return 0;
> +
> +	ret = v4l2_fwnode_endpoint_alloc_parse(ep, &v4l2_ep);
> +	fwnode_handle_put(ep);
> +	if (ret) {
> +		dev_err(priv->dev, "Could not parse endpoint on port %u\n", pad);
> +		return ret;
> +	}
> +
> +	bus_type = v4l2_ep.bus_type;
> +	if (bus_type != V4L2_MBUS_CSI2_DPHY &&
> +	    bus_type != V4L2_MBUS_CSI2_CPHY) {
> +		v4l2_fwnode_endpoint_free(&v4l2_ep);
> +		dev_err(priv->dev, "Unsupported bus-type %u on port %u\n",
> +			pad, bus_type);
> +		return -EINVAL;
> +	}
> +
> +	ret = 0;

ret is already 0 here.

> +	if (v4l2_ep.nr_of_link_frequencies == 0)
> +		link_frequency = MAX_DES_LINK_FREQUENCY_DEFAULT;

Isn't this required in DT?

> +	else if (v4l2_ep.nr_of_link_frequencies == 1)
> +		link_frequency = v4l2_ep.link_frequencies[0];
> +	else
> +		ret = -EINVAL;
> +
> +	v4l2_fwnode_endpoint_free(&v4l2_ep);
> +
> +	if (ret) {
> +		dev_err(priv->dev, "Invalid link frequencies %u on port %u\n",
> +			v4l2_ep.nr_of_link_frequencies, pad);
> +		return -EINVAL;
> +	}
> +
> +	if (link_frequency < MAX_DES_LINK_FREQUENCY_MIN ||
> +	    link_frequency > MAX_DES_LINK_FREQUENCY_MAX) {
> +		dev_err(priv->dev, "Invalid link frequency %llu on port %u\n",
> +			link_frequency, pad);
> +		return -EINVAL;
> +	}
> +
> +	for (i = 0; i < mipi->num_data_lanes; i++) {
> +		if (mipi->data_lanes[i] > mipi->num_data_lanes) {
> +			dev_err(priv->dev, "Invalid data lane %u on port %u\n",
> +				mipi->data_lanes[i], pad);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	phy->bus_type = bus_type;
> +	phy->mipi = *mipi;
> +	phy->link_frequency = link_frequency;
> +	phy->enabled = true;
> +
> +	return 0;
> +}
> +
> +int max_des_phy_hw_data_lanes(struct max_des *des, struct max_des_phy *phy)
> +{
> +	const struct max_phys_configs *configs = &des->ops->phys_configs;
> +	const struct max_phys_config *config = &configs->configs[des->phys_config];
> +
> +	return config->lanes[phy->index];
> +}
> +EXPORT_SYMBOL(max_des_phy_hw_data_lanes);
> +
> +static int max_des_find_phys_config(struct max_des_priv *priv)
> +{
> +	struct max_des *des = priv->des;
> +	const struct max_phys_configs *configs = &des->ops->phys_configs;
> +	struct max_des_phy *phy;
> +	unsigned int i, j;
> +
> +	if (!configs->num_configs)
> +		return 0;
> +
> +	for (i = 0; i < configs->num_configs; i++) {
> +		const struct max_phys_config *config = &configs->configs[i];
> +		bool matching = true;
> +
> +		for (j = 0; j < des->ops->num_phys; j++) {
> +			phy = &des->phys[j];
> +
> +			if (!phy->enabled)
> +				continue;
> +
> +			if (phy->mipi.num_data_lanes <= config->lanes[j] &&
> +			    phy->mipi.clock_lane == config->clock_lane[j])
> +				continue;
> +
> +			matching = false;
> +
> +			break;
> +		}
> +
> +		if (matching)
> +			break;
> +	}
> +
> +	if (i == configs->num_configs) {
> +		dev_err(priv->dev, "Invalid lane configuration\n");
> +		return -EINVAL;
> +	}
> +
> +	des->phys_config = i;
> +
> +	return 0;
> +}
> +
> +static int max_des_parse_dt(struct max_des_priv *priv)
> +{
> +	struct fwnode_handle *fwnode = dev_fwnode(priv->dev);
> +	struct max_des *des = priv->des;
> +	struct max_des_link *link;
> +	struct max_des_pipe *pipe;
> +	struct max_des_phy *phy;
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < des->ops->num_phys; i++) {
> +		phy = &des->phys[i];
> +		phy->index = i;
> +
> +		ret = max_des_parse_src_dt_endpoint(priv, phy, fwnode);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = max_des_find_phys_config(priv);
> +	if (ret)
> +		return ret;
> +
> +	/* Find an unsed PHY to send unampped data to. */
> +	for (i = 0; i < des->ops->num_phys; i++) {
> +		phy = &des->phys[i];
> +
> +		if (!phy->enabled) {
> +			priv->unused_phy = phy;
> +			break;
> +		}
> +	}
> +
> +	for (i = 0; i < des->ops->num_pipes; i++) {
> +		pipe = &des->pipes[i];
> +		pipe->index = i;
> +
> +		/*
> +		 * Serializers can send data on different stream ids over the
> +		 * same link, and some deserializers support stream id autoselect
> +		 * allowing them to receive data from all stream ids.
> +		 * Deserializers that support that feature should enable it.
> +		 * Deserializers that support per-link stream ids do not need
> +		 * to assign unique stream ids to each serializer.
> +		 */
> +		if (des->ops->needs_unique_stream_id)
> +			pipe->stream_id = i;
> +		else
> +			pipe->stream_id = 0;
> +
> +		/*
> +		 * We already checked that num_pipes >= num_links.
> +		 * Set up pipe to receive data from the link with the same index.
> +		 * This is already the default for most chips, and some of them
> +		 * don't even support receiving pipe data from a different link.
> +		 */
> +		pipe->link_id = i % des->ops->num_links;
> +	}
> +
> +	for (i = 0; i < des->ops->num_links; i++) {
> +		link = &des->links[i];
> +		link->index = i;
> +	}
> +
> +	for (i = 0; i < des->ops->num_links; i++) {
> +		struct max_des_link *link = &des->links[i];
> +		struct max_source *source;
> +
> +		source = max_des_get_link_source(priv, link);
> +		source->index = i;
> +
> +		ret = max_des_parse_sink_dt_endpoint(priv, link, source, fwnode);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max_des_allocate(struct max_des_priv *priv)
> +{
> +	struct max_des *des = priv->des;
> +	unsigned int num_pads = max_des_num_pads(des);
> +
> +	des->phys = devm_kcalloc(priv->dev, des->ops->num_phys,
> +				 sizeof(*des->phys), GFP_KERNEL);
> +	if (!des->phys)
> +		return -ENOMEM;
> +
> +	des->pipes = devm_kcalloc(priv->dev, des->ops->num_pipes,
> +				  sizeof(*des->pipes), GFP_KERNEL);
> +	if (!des->pipes)
> +		return -ENOMEM;
> +
> +	des->links = devm_kcalloc(priv->dev, des->ops->num_links,
> +				  sizeof(*des->links), GFP_KERNEL);
> +	if (!des->links)
> +		return -ENOMEM;
> +
> +	priv->sources = devm_kcalloc(priv->dev, des->ops->num_links,
> +				     sizeof(*priv->sources), GFP_KERNEL);
> +	if (!priv->sources)
> +		return -ENOMEM;
> +
> +	priv->pocs = devm_kcalloc(priv->dev, des->ops->num_links,
> +				  sizeof(*priv->pocs), GFP_KERNEL);
> +	if (!priv->pocs)
> +		return -ENOMEM;
> +
> +	priv->pads = devm_kcalloc(priv->dev, num_pads,
> +				  sizeof(*priv->pads), GFP_KERNEL);
> +	if (!priv->pads)
> +		return -ENOMEM;
> +
> +	priv->streams_masks = devm_kcalloc(priv->dev, num_pads,
> +					   sizeof(*priv->streams_masks),
> +					   GFP_KERNEL);
> +	if (!priv->streams_masks)
> +		return -ENOMEM;
> +
> +	return 0;
> +}
> +
> +int max_des_probe(struct i2c_client *client, struct max_des *des)
> +{
> +	struct device *dev = &client->dev;
> +	struct max_des_priv *priv;
> +	int ret;
> +
> +	if (des->ops->num_phys > MAX_DES_PHYS_NUM)
> +		return -E2BIG;
> +
> +	if (des->ops->num_pipes > MAX_DES_PIPES_NUM)
> +		return -E2BIG;
> +
> +	if (des->ops->num_links > des->ops->num_pipes)
> +		return -E2BIG;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	if (des->ops->set_link_version && !des->ops->select_links) {
> +		dev_err(dev,
> +			"Cannot implement .select_link_version() without .select_links()\n");
> +		return -EINVAL;
> +	}
> +
> +	if (hweight_long(des->ops->versions) >= 1 &&
> +	    !des->ops->set_link_version) {
> +		dev_err(dev, "Multiple version without .select_link_version()\n");
> +		return -EINVAL;
> +	}
> +
> +	priv->client = client;
> +	priv->dev = dev;
> +	priv->des = des;
> +	des->priv = priv;
> +
> +	ret = max_des_allocate(priv);
> +	if (ret)
> +		return ret;
> +
> +	ret = max_des_parse_dt(priv);
> +	if (ret)
> +		return ret;
> +
> +	ret = max_des_init(priv);
> +	if (ret)
> +		return ret;
> +
> +	ret = max_des_update_pocs(priv, true);
> +	if (ret)
> +		return ret;
> +
> +	ret = max_des_i2c_adapter_init(priv);
> +	if (ret)
> +		goto err_disable_pocs;
> +
> +	ret = max_des_v4l2_register(priv);
> +	if (ret)
> +		goto err_i2c_adapter_deinit;
> +
> +	return 0;
> +
> +err_i2c_adapter_deinit:
> +	max_des_i2c_adapter_deinit(priv);
> +
> +err_disable_pocs:
> +	max_des_update_pocs(priv, false);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(max_des_probe);
> +
> +int max_des_remove(struct max_des *des)
> +{
> +	struct max_des_priv *priv = des->priv;
> +
> +	max_des_v4l2_unregister(priv);
> +
> +	max_des_i2c_adapter_deinit(priv);
> +
> +	max_des_update_pocs(priv, false);
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(max_des_remove);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("I2C_ATR");
> diff --git a/drivers/media/i2c/maxim-serdes/max_des.h b/drivers/media/i2c/maxim-serdes/max_des.h
> new file mode 100644
> index 000000000000..c5daccc1af80
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max_des.h
> @@ -0,0 +1,152 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2025 Analog Devices Inc.
> + */
> +
> +#ifndef MAX_DES_H
> +#define MAX_DES_H
> +
> +#include <media/v4l2-mediabus.h>
> +
> +#include "max_serdes.h"
> +
> +#define MAX_DES_DT_VC(dt, vc) (((vc) & 0x3) << 6 | ((dt) & 0x3f))
> +
> +struct max_des_remap {
> +	u8 from_dt;
> +	u8 from_vc;
> +	u8 to_dt;
> +	u8 to_vc;
> +	u8 phy;
> +};
> +
> +struct max_des_link {
> +	unsigned int index;
> +	bool enabled;
> +	enum max_gmsl_version version;
> +	struct max_i2c_xlate ser_xlate;
> +};
> +
> +struct max_des_pipe_mode {
> +	bool dbl8;
> +	bool dbl10;
> +	bool dbl12;
> +	bool dbl8mode;
> +	bool dbl10mode;
> +};
> +
> +struct max_des_pipe {
> +	unsigned int index;
> +	unsigned int stream_id;
> +	unsigned int link_id;
> +	unsigned int phy_id;
> +	struct max_des_remap *remaps;
> +	unsigned int num_remaps;
> +	struct max_vc_remap *vc_remaps;
> +	unsigned int num_vc_remaps;
> +	struct max_des_pipe_mode mode;
> +	bool enabled;
> +};
> +
> +struct max_des_phy_mode {
> +	bool alt_mem_map8;
> +	bool alt2_mem_map8;
> +	bool alt_mem_map10;
> +	bool alt_mem_map12;
> +};
> +
> +struct max_des_phy {
> +	unsigned int index;
> +	s64 link_frequency;
> +	struct v4l2_mbus_config_mipi_csi2 mipi;
> +	enum v4l2_mbus_type bus_type;
> +	struct max_des_phy_mode mode;
> +	bool enabled;
> +	bool active;
> +};
> +
> +struct max_des;
> +
> +struct max_des_ops {
> +	unsigned int num_phys;
> +	unsigned int num_pipes;
> +	unsigned int num_links;
> +	unsigned int num_remaps_per_pipe;
> +	unsigned int versions;
> +	unsigned int modes;
> +	bool fix_tx_ids;
> +	bool use_atr;
> +	bool needs_single_link_version;
> +	bool needs_unique_stream_id;
> +
> +	struct max_phys_configs phys_configs;
> +	struct max_tpg_entries tpg_entries;
> +	enum max_gmsl_mode tpg_mode;
> +
> +	int (*reg_read)(struct max_des *des, unsigned int reg, unsigned int *val);
> +	int (*reg_write)(struct max_des *des, unsigned int reg, unsigned int val);
> +	int (*log_status)(struct max_des *des, const char *name);
> +	int (*log_pipe_status)(struct max_des *des, struct max_des_pipe *pipe,
> +			       const char *name);
> +	int (*log_phy_status)(struct max_des *des, struct max_des_phy *phy,
> +			      const char *name);
> +	int (*set_enable)(struct max_des *des, bool enable);
> +	int (*set_tpg)(struct max_des *des, const struct max_tpg_entry *entry);
> +	int (*init)(struct max_des *des);
> +	int (*init_phy)(struct max_des *des, struct max_des_phy *phy);
> +	int (*set_phy_mode)(struct max_des *des, struct max_des_phy *phy,
> +			    struct max_des_phy_mode *mode);
> +	int (*set_phy_active)(struct max_des *des, struct max_des_phy *phy,
> +			      bool active);
> +	int (*set_pipe_stream_id)(struct max_des *des, struct max_des_pipe *pipe,
> +				  unsigned int stream_id);
> +	int (*set_pipe_link)(struct max_des *des, struct max_des_pipe *pipe,
> +			     struct max_des_link *link);
> +	int (*set_pipe_phy)(struct max_des *des, struct max_des_pipe *pipe,
> +			    struct max_des_phy *phy);
> +	int (*set_pipe_tunnel_phy)(struct max_des *des, struct max_des_pipe *pipe,
> +				   struct max_des_phy *phy);
> +	int (*set_pipe_enable)(struct max_des *des, struct max_des_pipe *pipe,
> +			       bool enable);
> +	int (*set_pipe_remap)(struct max_des *des, struct max_des_pipe *pipe,
> +			      unsigned int i, struct max_des_remap *remap);
> +	int (*set_pipe_remaps_enable)(struct max_des *des, struct max_des_pipe *pipe,
> +				      unsigned int mask);
> +	int (*set_pipe_vc_remap)(struct max_des *des, struct max_des_pipe *pipe,
> +				 unsigned int i, struct max_vc_remap *vc_remap);
> +	int (*set_pipe_vc_remaps_enable)(struct max_des *des, struct max_des_pipe *pipe,
> +					 unsigned int mask);
> +	int (*set_pipe_mode)(struct max_des *des, struct max_des_pipe *pipe,
> +			     struct max_des_pipe_mode *mode);
> +	int (*set_pipe_tunnel_enable)(struct max_des *des, struct max_des_pipe *pipe,
> +				      bool enable);
> +	int (*init_link)(struct max_des *des, struct max_des_link *link);
> +	int (*select_links)(struct max_des *des, unsigned int mask);
> +	int (*set_link_version)(struct max_des *des, struct max_des_link *link,
> +				enum max_gmsl_version version);
> +};
> +
> +struct max_des_priv;
> +
> +struct max_des {
> +	struct max_des_priv *priv;
> +
> +	const struct max_des_ops *ops;
> +
> +	struct max_des_phy *phys;
> +	struct max_des_pipe *pipes;
> +	struct max_des_link *links;
> +	const struct max_tpg_entry *tpg_entry;
> +
> +	unsigned int phys_config;
> +	enum max_gmsl_mode mode;
> +	bool active;
> +};
> +
> +int max_des_probe(struct i2c_client *client, struct max_des *des);
> +
> +int max_des_remove(struct max_des *des);
> +
> +int max_des_phy_hw_data_lanes(struct max_des *des, struct max_des_phy *phy);
> +
> +#endif // MAX_DES_H
> diff --git a/drivers/media/i2c/maxim-serdes/max_ser.c b/drivers/media/i2c/maxim-serdes/max_ser.c
> new file mode 100644
> index 000000000000..cb388a2e4e51
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max_ser.c
> @@ -0,0 +1,2032 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Maxim GMSL2 Serializer Driver
> + *
> + * Copyright (C) 2025 Analog Devices Inc.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/i2c-atr.h>
> +#include <linux/i2c-mux.h>
> +#include <linux/module.h>
> +
> +#include <media/mipi-csi2.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-subdev.h>
> +
> +#include "max_ser.h"
> +#include "max_serdes.h"
> +
> +#define MAX_SER_NUM_LINKS	1
> +
> +struct max_ser_priv {
> +	struct max_ser *ser;
> +	struct device *dev;
> +	struct i2c_client *client;
> +
> +	struct i2c_atr *atr;
> +	struct i2c_mux_core *mux;
> +
> +	struct media_pad *pads;
> +	struct max_source *sources;
> +	u64 *streams_masks;
> +	u32 double_bpps;
> +
> +	struct v4l2_subdev sd;
> +	struct v4l2_async_notifier nf;
> +};
> +
> +struct max_ser_route_hw {
> +	struct max_source *source;
> +	struct max_ser_pipe *pipe;
> +	struct v4l2_mbus_frame_desc_entry entry;
> +	bool is_tpg;
> +};
> +
> +static inline struct max_ser_priv *sd_to_priv(struct v4l2_subdev *sd)
> +{
> +	return container_of(sd, struct max_ser_priv, sd);
> +}
> +
> +static inline struct max_ser_priv *nf_to_priv(struct v4l2_async_notifier *nf)
> +{
> +	return container_of(nf, struct max_ser_priv, nf);
> +}
> +
> +static inline bool max_ser_pad_is_sink(struct max_ser *ser, u32 pad)
> +{
> +	return pad < ser->ops->num_phys;
> +}
> +
> +static inline bool max_ser_pad_is_source(struct max_ser *ser, u32 pad)
> +{
> +	return pad >= ser->ops->num_phys &&
> +	       pad < ser->ops->num_phys + MAX_SER_NUM_LINKS;
> +}
> +
> +static inline bool max_ser_pad_is_tpg(struct max_ser *ser, u32 pad)
> +{
> +	return pad >= ser->ops->num_phys + MAX_SER_NUM_LINKS;
> +}
> +
> +static inline unsigned int max_ser_phy_to_pad(struct max_ser *ser,
> +					      struct max_ser_phy *phy)
> +{
> +	return phy->index;
> +}
> +
> +static inline unsigned int max_ser_num_pads(struct max_ser *ser)
> +{
> +	return ser->ops->num_phys + MAX_SER_NUM_LINKS +
> +	       (ser->ops->set_tpg ? 1 : 0);
> +}
> +
> +static struct max_ser_phy *max_ser_pad_to_phy(struct max_ser *ser, u32 pad)
> +{
> +	if (!max_ser_pad_is_sink(ser, pad))
> +		return NULL;
> +
> +	return &ser->phys[pad];
> +}
> +
> +static struct max_ser_pipe *
> +max_ser_find_phy_pipe(struct max_ser *ser, struct max_ser_phy *phy)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < ser->ops->num_pipes; i++) {
> +		struct max_ser_pipe *pipe = &ser->pipes[i];
> +
> +		if (pipe->phy_id == phy->index)
> +			return pipe;
> +	}
> +
> +	return NULL;
> +}
> +
> +static struct max_source *
> +max_ser_get_phy_source(struct max_ser_priv *priv, struct max_ser_phy *phy)
> +{
> +	return &priv->sources[phy->index];
> +}
> +
> +static const struct max_tpg_entry *
> +max_ser_find_tpg_entry(struct max_ser *ser, u32 target_index,
> +		       u32 width, u32 height, u32 code,
> +		       u32 numerator, u32 denominator)
> +{
> +	const struct max_tpg_entry *entry;
> +	unsigned int index = 0;
> +	unsigned int i;
> +
> +	for (i = 0; i < ser->ops->tpg_entries.num_entries; i++) {
> +		entry = &ser->ops->tpg_entries.entries[i];
> +
> +		if ((width != 0 && width != entry->width) ||
> +		    (height != 0 && height != entry->height) ||
> +		    (code != 0 && code != entry->code) ||
> +		    (numerator != 0 && numerator != entry->interval.numerator) ||
> +		    (denominator != 0 && denominator != entry->interval.denominator))
> +			continue;
> +
> +		if (index == target_index)
> +			break;
> +
> +		index++;
> +	}
> +
> +	if (i == ser->ops->tpg_entries.num_entries)
> +		return NULL;
> +
> +	return &ser->ops->tpg_entries.entries[i];
> +}
> +
> +static const struct max_tpg_entry *
> +max_ser_find_state_tpg_entry(struct max_ser *ser, struct v4l2_subdev_state *state,
> +			     unsigned int pad)
> +{
> +	struct v4l2_mbus_framefmt *fmt;
> +	struct v4l2_fract *in;
> +
> +	fmt = v4l2_subdev_state_get_format(state, pad, MAX_SERDES_TPG_STREAM);
> +	if (!fmt)
> +		return NULL;
> +
> +	in = v4l2_subdev_state_get_interval(state, pad, MAX_SERDES_TPG_STREAM);
> +	if (!in)
> +		return NULL;
> +
> +	return max_ser_find_tpg_entry(ser, 0, fmt->width, fmt->height, fmt->code,
> +				      in->numerator, in->denominator);
> +}
> +
> +static int max_ser_get_tpg_fd_entry_state(struct max_ser *ser,
> +					  struct v4l2_subdev_state *state,
> +					  struct v4l2_mbus_frame_desc_entry *fd_entry,
> +					  unsigned int pad)
> +{
> +	const struct max_tpg_entry *entry;
> +
> +	entry = max_ser_find_state_tpg_entry(ser, state, pad);
> +	if (!entry)
> +		return -EINVAL;
> +
> +	fd_entry->stream = MAX_SERDES_TPG_STREAM;
> +	fd_entry->flags = V4L2_MBUS_FRAME_DESC_FL_LEN_MAX;
> +	fd_entry->length = entry->width * entry->height * entry->bpp / 8;
> +	fd_entry->pixelcode = entry->code;
> +	fd_entry->bus.csi2.vc = 0;
> +	fd_entry->bus.csi2.dt = entry->dt;
> +
> +	return 0;
> +}
> +
> +static int max_ser_tpg_route_to_hw(struct max_ser_priv *priv,
> +				   struct v4l2_subdev_state *state,
> +				   struct v4l2_subdev_route *route,
> +				   struct max_ser_route_hw *hw)
> +{
> +	struct max_ser *ser = priv->ser;
> +
> +	hw->pipe = &ser->pipes[0];
> +
> +	return max_ser_get_tpg_fd_entry_state(ser, state, &hw->entry,
> +					      route->sink_pad);
> +}
> +
> +static int max_ser_route_to_hw(struct max_ser_priv *priv,
> +			       struct v4l2_subdev_state *state,
> +			       struct v4l2_subdev_route *route,
> +			       struct max_ser_route_hw *hw)
> +{
> +	struct max_ser *ser = priv->ser;
> +	struct v4l2_mbus_frame_desc fd;
> +	struct max_ser_phy *phy;
> +	unsigned int i;
> +	int ret;
> +
> +	memset(hw, 0, sizeof(*hw));
> +
> +	hw->is_tpg = max_ser_pad_is_tpg(ser, route->sink_pad);
> +	if (hw->is_tpg)
> +		return max_ser_tpg_route_to_hw(priv, state, route, hw);
> +
> +	phy = max_ser_pad_to_phy(ser, route->sink_pad);
> +	if (!phy)
> +		return -ENOENT;
> +
> +	hw->pipe = max_ser_find_phy_pipe(ser, phy);
> +	if (!hw->pipe)
> +		return -ENOENT;
> +
> +	hw->source = max_ser_get_phy_source(priv, phy);
> +	if (!hw->source->sd)
> +		return 0;
> +
> +	ret = v4l2_subdev_call(hw->source->sd, pad, get_frame_desc,
> +			       hw->source->pad, &fd);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < fd.num_entries; i++)
> +		if (fd.entry[i].stream == route->sink_stream)
> +			break;
> +
> +	if (i == fd.num_entries)
> +		return -ENOENT;
> +
> +	hw->entry = fd.entry[i];
> +
> +	return 0;
> +}
> +
> +static int max_ser_phy_set_active(struct max_ser *ser, struct max_ser_phy *phy,
> +				  bool active)
> +{
> +	int ret;
> +
> +	if (ser->ops->set_phy_active) {
> +		ret = ser->ops->set_phy_active(ser, phy, active);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	phy->active = active;
> +
> +	return 0;
> +}
> +
> +static int max_ser_set_pipe_dts(struct max_ser_priv *priv, struct max_ser_pipe *pipe,
> +				unsigned int *dts, unsigned int num_dts)
> +{
> +	struct max_ser *ser = priv->ser;
> +	unsigned int i;
> +	int ret;
> +
> +	if (!ser->ops->set_pipe_dt || !ser->ops->set_pipe_dt_en)
> +		return 0;
> +
> +	for (i = 0; i < num_dts; i++) {
> +		ret = ser->ops->set_pipe_dt(ser, pipe, i, dts[i]);
> +		if (ret)
> +			return ret;
> +
> +		ret = ser->ops->set_pipe_dt_en(ser, pipe, i, true);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (num_dts == pipe->num_dts)
> +		return 0;
> +
> +	for (i = num_dts; i < ser->ops->num_dts_per_pipe; i++) {
> +		ret = ser->ops->set_pipe_dt_en(ser, pipe, i, false);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max_ser_set_pipe_mode(struct max_ser_priv *priv, struct max_ser_pipe *pipe,
> +				 struct max_ser_pipe_mode *mode)
> +{
> +	struct max_ser *ser = priv->ser;
> +
> +	if (mode->bpp == pipe->mode.bpp &&
> +	    mode->soft_bpp == pipe->mode.soft_bpp &&
> +	    mode->dbl8 == pipe->mode.dbl8 &&
> +	    mode->dbl10 == pipe->mode.dbl10 &&
> +	    mode->dbl12 == pipe->mode.dbl12)
> +		return 0;
> +
> +	if (!ser->ops->set_pipe_mode)
> +		return 0;
> +
> +	return ser->ops->set_pipe_mode(ser, pipe, mode);
> +}
> +
> +static int max_ser_i2c_atr_attach_addr(struct i2c_atr *atr, u32 chan_id,
> +				       u16 addr, u16 alias)
> +{
> +	struct max_i2c_xlate xlate = { .src = alias, .dst = addr, .en = true };
> +	struct max_ser_priv *priv = i2c_atr_get_driver_data(atr);
> +	struct max_ser *ser = priv->ser;
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < ser->ops->num_i2c_xlates; i++)
> +		if (!ser->i2c_xlates[i].en)
> +			break;
> +
> +	if (i == ser->ops->num_i2c_xlates) {
> +		dev_err(priv->dev,
> +			"Reached maximum number of I2C translations\n");
> +		return -EINVAL;
> +	}
> +
> +	ret = ser->ops->set_i2c_xlate(ser, i, &xlate);
> +	if (ret)
> +		return ret;
> +
> +	ser->i2c_xlates[i] = xlate;
> +
> +	return 0;
> +}
> +
> +static void max_ser_i2c_atr_detach_addr(struct i2c_atr *atr, u32 chan_id, u16 addr)
> +{
> +	struct max_ser_priv *priv = i2c_atr_get_driver_data(atr);
> +	struct max_ser *ser = priv->ser;
> +	struct max_i2c_xlate xlate = { 0 };
> +	unsigned int i;
> +
> +	/* Find index of matching I2C translation. */
> +	for (i = 0; i < ser->ops->num_i2c_xlates; i++)
> +		if (ser->i2c_xlates[i].dst == addr)
> +			break;
> +
> +	WARN_ON(i == ser->ops->num_i2c_xlates);
> +
> +	ser->ops->set_i2c_xlate(ser, i, &xlate);
> +	ser->i2c_xlates[i] = xlate;
> +}
> +
> +static const struct i2c_atr_ops max_ser_i2c_atr_ops = {
> +	.attach_addr = max_ser_i2c_atr_attach_addr,
> +	.detach_addr = max_ser_i2c_atr_detach_addr,
> +};
> +
> +static void max_ser_i2c_atr_deinit(struct max_ser_priv *priv)
> +{
> +	/* Deleting adapters that haven't been added does no harm. */
> +	i2c_atr_del_adapter(priv->atr, 0);
> +
> +	i2c_atr_delete(priv->atr);
> +}
> +
> +static int max_ser_i2c_atr_init(struct max_ser_priv *priv)
> +{
> +	struct i2c_atr_adap_desc desc = {
> +		.chan_id = 0,
> +	};
> +
> +	if (!i2c_check_functionality(priv->client->adapter,
> +				     I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
> +		return -ENODEV;
> +
> +	priv->atr = i2c_atr_new(priv->client->adapter, priv->dev,
> +				&max_ser_i2c_atr_ops, 1, 0);
> +	if (IS_ERR(priv->atr))
> +		return PTR_ERR(priv->atr);
> +
> +	i2c_atr_set_driver_data(priv->atr, priv);
> +
> +	return i2c_atr_add_adapter(priv->atr, &desc);
> +}
> +
> +static int max_ser_i2c_mux_select(struct i2c_mux_core *mux, u32 chan)
> +{
> +	return 0;
> +}
> +
> +static void max_ser_i2c_mux_deinit(struct max_ser_priv *priv)
> +{
> +	i2c_mux_del_adapters(priv->mux);
> +}
> +
> +static int max_ser_i2c_mux_init(struct max_ser_priv *priv)
> +{
> +	priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev,
> +				  1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE,
> +				  max_ser_i2c_mux_select, NULL);
> +	if (!priv->mux)
> +		return -ENOMEM;
> +
> +	return i2c_mux_add_adapter(priv->mux, 0, 0);
> +}
> +
> +static int max_ser_i2c_adapter_init(struct max_ser_priv *priv)
> +{
> +	if (device_get_named_child_node(priv->dev, "i2c-gate"))
> +		return max_ser_i2c_mux_init(priv);
> +	else
> +		return max_ser_i2c_atr_init(priv);
> +}
> +
> +static void max_ser_i2c_adapter_deinit(struct max_ser_priv *priv)
> +{
> +	if (device_get_named_child_node(priv->dev, "i2c-gate"))
> +		max_ser_i2c_mux_deinit(priv);
> +	else
> +		max_ser_i2c_atr_deinit(priv);
> +}
> +
> +static int max_ser_set_tpg_fmt(struct v4l2_subdev *sd,
> +			       struct v4l2_subdev_state *state,
> +			       struct v4l2_subdev_format *format)
> +{
> +	struct v4l2_mbus_framefmt *fmt = &format->format;
> +	struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
> +	struct max_ser *ser = priv->ser;
> +	const struct max_tpg_entry *entry;
> +	struct v4l2_fract *in;
> +
> +	if (format->stream != MAX_SERDES_TPG_STREAM)
> +		return -EINVAL;
> +
> +	entry = max_ser_find_tpg_entry(ser, 0, fmt->width, fmt->height,
> +				       fmt->code, 0, 0);
> +	if (!entry)
> +		return -EINVAL;
> +
> +	in = v4l2_subdev_state_get_interval(state, format->pad, format->stream);
> +	if (!in)
> +		return -EINVAL;
> +
> +	in->numerator = entry->interval.numerator;
> +	in->denominator = entry->interval.denominator;
> +
> +	return 0;
> +}
> +
> +static int max_ser_set_fmt(struct v4l2_subdev *sd,
> +			   struct v4l2_subdev_state *state,
> +			   struct v4l2_subdev_format *format)
> +{
> +	struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
> +	struct max_ser *ser = priv->ser;
> +	struct v4l2_mbus_framefmt *fmt;
> +	int ret;
> +
> +	if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && ser->active)
> +		return -EBUSY;
> +
> +	/* No transcoding, source and sink formats must match. */
> +	if (max_ser_pad_is_source(ser, format->pad))
> +		return v4l2_subdev_get_fmt(sd, state, format);
> +
> +	if (max_ser_pad_is_tpg(ser, format->pad)) {
> +		ret = max_ser_set_tpg_fmt(sd, state, format);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
> +	if (!fmt)
> +		return -EINVAL;
> +
> +	*fmt = format->format;
> +
> +	fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
> +							   format->stream);
> +	if (!fmt)
> +		return -EINVAL;
> +
> +	*fmt = format->format;
> +
> +	return 0;
> +}
> +
> +static int max_ser_log_status(struct v4l2_subdev *sd)
> +{
> +	struct max_ser_priv *priv = sd_to_priv(sd);
> +	struct max_ser *ser = priv->ser;
> +	unsigned int i, j;
> +	int ret;
> +
> +	v4l2_info(sd, "mode: %s\n", max_gmsl_mode_str(ser->mode));
> +	if (ser->ops->set_tpg) {
> +		const struct max_tpg_entry *entry = ser->tpg_entry;
> +
> +		if (entry) {
> +			v4l2_info(sd, "tpg: %ux%u@...%u, code: %u, dt: %u, bpp: %u\n",
> +				  entry->width, entry->height,
> +				  entry->interval.numerator,
> +				  entry->interval.denominator,
> +				  entry->code, entry->dt,  entry->bpp);
> +		} else {
> +			v4l2_info(sd, "tpg: disabled\n");
> +		}
> +	}
> +	if (ser->ops->log_status) {
> +		ret = ser->ops->log_status(ser, sd->name);
> +		if (ret)
> +			return ret;
> +	}
> +	v4l2_info(sd, "i2c_xlates:\n");
> +	for (i = 0; i < ser->ops->num_i2c_xlates; i++) {
> +		v4l2_info(sd, "\ten: %u, src: 0x%02x dst: 0x%02x\n",
> +			  ser->i2c_xlates[i].en, ser->i2c_xlates[i].src,
> +			  ser->i2c_xlates[i].dst);
> +		if (!ser->i2c_xlates[i].en)
> +			break;
> +	}
> +	v4l2_info(sd, "\n");
> +
> +	for (i = 0; i < ser->ops->num_pipes; i++) {
> +		struct max_ser_pipe *pipe = &ser->pipes[i];
> +
> +		v4l2_info(sd, "pipe: %u\n", pipe->index);
> +		v4l2_info(sd, "\tenabled: %u\n", pipe->enabled);
> +
> +		if (!pipe->enabled) {
> +			v4l2_info(sd, "\n");
> +			continue;
> +		}
> +
> +		v4l2_info(sd, "\tphy_id: %u\n", pipe->phy_id);
> +		v4l2_info(sd, "\tstream_id: %u\n", pipe->stream_id);
> +		if (ser->ops->set_pipe_phy)
> +			v4l2_info(sd, "\tphy_id: %u\n", pipe->phy_id);
> +		if (ser->ops->set_pipe_dt) {
> +			v4l2_info(sd, "\tdts: %u\n", pipe->num_dts);
> +			for (j = 0; j < pipe->num_dts; j++)
> +				v4l2_info(sd, "\t\tdt: 0x%02x\n", pipe->dts[j]);
> +		}
> +		if (ser->ops->set_pipe_vcs)
> +			v4l2_info(sd, "\tvcs: 0x%08x\n", pipe->vcs);
> +		if (ser->ops->set_pipe_mode) {
> +			v4l2_info(sd, "\tdbl8: %u\n", pipe->mode.dbl8);
> +			v4l2_info(sd, "\tdbl10: %u\n", pipe->mode.dbl10);
> +			v4l2_info(sd, "\tdbl12: %u\n", pipe->mode.dbl12);
> +			v4l2_info(sd, "\tsoft_bpp: %u\n", pipe->mode.soft_bpp);
> +			v4l2_info(sd, "\tbpp: %u\n", pipe->mode.bpp);
> +		}
> +		if (ser->ops->set_pipe_vc_remap) {
> +			v4l2_info(sd, "\tvc_remaps: %u\n", pipe->num_vc_remaps);
> +			for (j = 0; j < pipe->num_vc_remaps; j++) {
> +				v4l2_info(sd, "\t\tvc_remap: src: %u, dst: %u\n",
> +					  pipe->vc_remaps[j].src, pipe->vc_remaps[j].dst);
> +			}
> +		}
> +		if (ser->ops->log_pipe_status) {
> +			ret = ser->ops->log_pipe_status(ser, pipe, sd->name);
> +			if (ret)
> +				return ret;
> +		}
> +		v4l2_info(sd, "\n");
> +	}
> +
> +	for (i = 0; i < ser->ops->num_phys; i++) {
> +		struct max_ser_phy *phy = &ser->phys[i];
> +
> +		v4l2_info(sd, "phy: %u\n", phy->index);
> +		v4l2_info(sd, "\tenabled: %u\n", phy->enabled);
> +
> +		if (!phy->enabled) {
> +			v4l2_info(sd, "\n");
> +			continue;
> +		}
> +
> +		v4l2_info(sd, "\tactive: %u\n", phy->active);
> +		v4l2_info(sd, "\tnum_data_lanes: %u\n", phy->mipi.num_data_lanes);
> +		v4l2_info(sd, "\tclock_lane: %u\n", phy->mipi.clock_lane);
> +		v4l2_info(sd, "\tnoncontinuous_clock: %u\n",
> +			  !!(phy->mipi.flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK));
> +		if (ser->ops->log_phy_status) {
> +			ret = ser->ops->log_phy_status(ser, phy, sd->name);
> +			if (ret)
> +				return ret;
> +		}
> +		v4l2_info(sd, "\n");
> +	}
> +
> +	return 0;
> +}
> +
> +static int max_ser_enum_frame_interval(struct v4l2_subdev *sd,
> +				       struct v4l2_subdev_state *state,
> +				       struct v4l2_subdev_frame_interval_enum *fie)
> +{
> +	struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
> +	struct max_ser *ser = priv->ser;
> +	const struct max_tpg_entry *entry;
> +
> +	if (!max_ser_pad_is_tpg(ser, fie->pad) ||
> +	    fie->stream != MAX_SERDES_TPG_STREAM)
> +		return -EINVAL;
> +
> +	entry = max_ser_find_tpg_entry(ser, fie->index, fie->width, fie->height,
> +				       fie->code, fie->interval.denominator,
> +				       fie->interval.numerator);
> +	if (!entry)
> +		return -EINVAL;
> +
> +	fie->interval.numerator = entry->interval.numerator;
> +	fie->interval.denominator = entry->interval.denominator;
> +
> +	return 0;
> +}
> +
> +static int max_ser_set_frame_interval(struct v4l2_subdev *sd,
> +				      struct v4l2_subdev_state *state,
> +				      struct v4l2_subdev_frame_interval *fi)
> +{
> +	struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
> +	struct max_ser *ser = priv->ser;
> +	const struct max_tpg_entry *entry;
> +	struct v4l2_mbus_framefmt *fmt;
> +	struct v4l2_fract *in;
> +
> +	if (!max_ser_pad_is_tpg(ser, fi->pad) ||
> +	    fi->stream != MAX_SERDES_TPG_STREAM)
> +		return -EINVAL;
> +
> +	if (fi->which == V4L2_SUBDEV_FORMAT_ACTIVE && ser->active)
> +		return -EBUSY;
> +
> +	fmt = v4l2_subdev_state_get_format(state, fi->pad, fi->stream);
> +	if (!fmt)
> +		return -EINVAL;
> +
> +	entry = max_ser_find_tpg_entry(ser, 0, fmt->width, fmt->height,
> +				       fmt->code, fi->interval.denominator,
> +				       fi->interval.numerator);
> +	if (!entry)
> +		return -EINVAL;
> +
> +	in = v4l2_subdev_state_get_interval(state, fi->pad, fi->stream);
> +	if (!in)
> +		return -EINVAL;
> +
> +	in->numerator = fi->interval.numerator;
> +	in->denominator = fi->interval.denominator;
> +
> +	return 0;
> +}
> +
> +static int max_ser_get_frame_desc_state(struct v4l2_subdev *sd,
> +					struct v4l2_subdev_state *state,
> +					struct v4l2_mbus_frame_desc *fd,
> +					unsigned int pad)
> +{
> +	struct max_ser_priv *priv = sd_to_priv(sd);
> +	struct max_ser *ser = priv->ser;
> +	struct v4l2_subdev_route *route;
> +	int ret;
> +
> +	if (!max_ser_pad_is_source(ser, pad))
> +		return -ENOENT;
> +
> +	fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
> +
> +	for_each_active_route(&state->routing, route) {
> +		struct max_ser_route_hw hw;
> +
> +		if (pad != route->source_pad)
> +			continue;
> +
> +		ret = max_ser_route_to_hw(priv, state, route, &hw);
> +		if (ret)
> +			return ret;
> +
> +		hw.entry.stream = route->source_stream;
> +
> +		fd->entry[fd->num_entries++] = hw.entry;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max_ser_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
> +				  struct v4l2_mbus_frame_desc *fd)
> +{
> +	struct max_ser_priv *priv = sd_to_priv(sd);
> +	struct v4l2_subdev_state *state;
> +	int ret;
> +
> +	state = v4l2_subdev_lock_and_get_active_state(&priv->sd);
> +
> +	ret = max_ser_get_frame_desc_state(sd, state, fd, pad);
> +
> +	v4l2_subdev_unlock_state(state);
> +
> +	return ret;
> +}
> +
> +static int max_ser_set_tpg_routing(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state,
> +				   struct v4l2_subdev_krouting *routing)
> +{
> +	struct max_ser_priv *priv = sd_to_priv(sd);
> +	struct max_ser *ser = priv->ser;
> +	const struct max_tpg_entry *entry;
> +	struct v4l2_mbus_framefmt fmt = { 0 };
> +	int ret;
> +
> +	ret = max_validate_tpg_routing(routing);
> +	if (ret)
> +		return ret;
> +
> +	entry = &ser->ops->tpg_entries.entries[0];
> +
> +	fmt.width = entry->width;
> +	fmt.height = entry->height;
> +	fmt.code = entry->code;
> +
> +	return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &fmt);
> +}
> +
> +static int max_ser_set_routing(struct v4l2_subdev *sd,
> +			       struct v4l2_subdev_state *state,
> +			       enum v4l2_subdev_format_whence which,
> +			       struct v4l2_subdev_krouting *routing)
> +{
> +	struct max_ser_priv *priv = sd_to_priv(sd);
> +	struct max_ser *ser = priv->ser;
> +	struct v4l2_subdev_route *route;
> +	bool is_tpg = false;
> +	int ret;
> +
> +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && ser->active)
> +		return -EBUSY;
> +
> +	/*
> +	 * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until
> +	 * frame desc is made dynamically allocated.
> +	 */
> +
> +	if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX)
> +		return -E2BIG;
> +
> +	ret = v4l2_subdev_routing_validate(sd, routing,
> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1 |
> +					   V4L2_SUBDEV_ROUTING_NO_SINK_STREAM_MIX);
> +	if (ret)
> +		return ret;
> +
> +	for_each_active_route(routing, route) {
> +		if (max_ser_pad_is_tpg(ser, route->sink_pad)) {
> +			is_tpg = true;
> +			break;
> +		}
> +	}
> +
> +	if (is_tpg)
> +		return max_ser_set_tpg_routing(sd, state, routing);
> +
> +	ret = max_validate_tpg_routing(routing);
> +	if (ret)
> +		return ret;
> +
> +	return v4l2_subdev_set_routing(sd, state, routing);
> +}
> +
> +static int max_ser_get_pipe_vcs_dts(struct max_ser_priv *priv,
> +				    struct v4l2_subdev_state *state,
> +				    struct max_ser_pipe *pipe,
> +				    unsigned int *vcs,
> +				    unsigned int *dts, unsigned int *num_dts,
> +				    u64 *streams_masks)
> +{
> +	struct v4l2_subdev_route *route;
> +	struct max_ser *ser = priv->ser;
> +	unsigned int i;
> +	int ret;
> +
> +	*vcs = 0;
> +	*num_dts = 0;
> +
> +	if (ser->mode != MAX_GMSL_PIXEL_MODE)
> +		return 0;
> +
> +	for_each_active_route(&state->routing, route) {
> +		struct max_ser_route_hw hw;
> +		unsigned int vc, dt;
> +
> +		if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
> +			continue;
> +
> +		ret = max_ser_route_to_hw(priv, state, route, &hw);
> +		if (ret)
> +			return ret;
> +
> +		if (hw.pipe != pipe)
> +			continue;
> +
> +		vc = hw.entry.bus.csi2.vc;
> +		dt = hw.entry.bus.csi2.dt;
> +
> +		if (vc >= MAX_SERDES_VC_ID_NUM)
> +			return -E2BIG;
> +
> +		*vcs |= BIT(vc);
> +
> +		/* Skip already added DT. */
> +		for (i = 0; i < *num_dts; i++)
> +			if (dts[i] == dt)
> +				break;
> +
> +		if (i < *num_dts)
> +			continue;
> +
> +		dts[*num_dts] = dt;
> +		(*num_dts)++;
> +	}
> +
> +	/*
> +	 * Hardware cannot distinguish between different pairs of VC and DT,
> +	 * issue a warning.
> +	 */
> +	for_each_active_route(&state->routing, route) {
> +		struct max_ser_route_hw hw;
> +		unsigned int vc, dt;
> +
> +		/*
> +		 * Skip enabled streams, we only want to check for leaks
> +		 * among the disabled streams.
> +		 */
> +		if ((BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
> +			continue;
> +
> +		ret = max_ser_route_to_hw(priv, state, route, &hw);
> +		if (ret)
> +			return ret;
> +
> +		if (hw.pipe != pipe)
> +			continue;
> +
> +		vc = hw.entry.bus.csi2.vc;
> +		dt = hw.entry.bus.csi2.dt;
> +
> +		if (vc >= MAX_SERDES_VC_ID_NUM)
> +			return -E2BIG;
> +
> +		if (!(*vcs & BIT(vc)))
> +			continue;
> +
> +		for (i = 0; i < *num_dts; i++)
> +			if (dts[i] == dt)
> +				break;
> +
> +		if (i == *num_dts)
> +			continue;
> +
> +		dev_warn(priv->dev, "Leaked disabled stream %u:%u with VC: %u, DT: %u",
> +			 route->source_pad, route->source_stream, vc, dt);
> +	}
> +
> +	return 0;
> +}
> +
> +static int max_ser_get_pipe_mode(struct max_ser_priv *priv,
> +				 struct v4l2_subdev_state *state,
> +				 struct max_ser_pipe *pipe,
> +				 struct max_ser_pipe_mode *mode)
> +{
> +	struct v4l2_subdev_route *route;
> +	struct max_ser *ser = priv->ser;
> +	bool force_set_bpp = false;
> +	unsigned int doubled_bpp;
> +	unsigned int min_bpp;
> +	unsigned int max_bpp;
> +	u32 bpps = 0;
> +	int ret;
> +
> +	if (ser->mode != MAX_GMSL_PIXEL_MODE)
> +		return 0;
> +
> +	for_each_active_route(&state->routing, route) {
> +		struct max_ser_route_hw hw;
> +		unsigned int bpp;
> +
> +		ret = max_ser_route_to_hw(priv, state, route, &hw);
> +		if (ret)
> +			return ret;
> +
> +		if (hw.pipe != pipe)
> +			continue;
> +
> +		if (hw.is_tpg)
> +			force_set_bpp = true;
> +
> +		ret = max_get_fd_bpp(&hw.entry, &bpp);
> +		if (ret)
> +			return ret;
> +
> +		bpps |= BIT(bpp);
> +	}
> +
> +	ret = max_process_bpps(priv->dev, bpps, priv->double_bpps, &doubled_bpp);
> +	if (ret)
> +		return ret;
> +
> +	if (doubled_bpp == 8)
> +		mode->dbl8 = true;
> +	else if (doubled_bpp == 10)
> +		mode->dbl10 = true;
> +	else if (doubled_bpp == 12)
> +		mode->dbl12 = true;
> +
> +	if (doubled_bpp) {
> +		bpps &= ~BIT(doubled_bpp);
> +		bpps |= BIT(doubled_bpp * 2);
> +	}
> +
> +	min_bpp = __ffs(bpps);
> +	max_bpp = __fls(bpps);
> +
> +	if (doubled_bpp)
> +		mode->soft_bpp = min_bpp;
> +
> +	if (min_bpp != max_bpp || force_set_bpp)
> +		mode->bpp = max_bpp;
> +
> +	return 0;
> +}
> +
> +static int max_ser_update_pipe_enable(struct max_ser_priv *priv,
> +				      struct max_ser_pipe *pipe,
> +				      struct v4l2_subdev_state *state,
> +				      u64 *streams_masks)
> +{
> +	struct max_ser *ser = priv->ser;
> +	struct v4l2_subdev_route *route;
> +	bool enable = false;
> +	int ret;
> +
> +	for_each_active_route(&state->routing, route) {
> +		struct max_ser_route_hw hw;
> +
> +		if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
> +			continue;
> +
> +		ret = max_ser_route_to_hw(priv, state, route, &hw);
> +		if (ret)
> +			return ret;
> +
> +		if (hw.pipe != pipe)
> +			continue;
> +
> +		enable = true;
> +		break;
> +	}
> +
> +	if (enable == pipe->enabled)
> +		return 0;
> +
> +	ret = ser->ops->set_pipe_enable(ser, pipe, enable);
> +	if (ret)
> +		return ret;
> +
> +	pipe->enabled = enable;
> +
> +	return 0;
> +}
> +
> +static int max_ser_update_pipe(struct max_ser_priv *priv,
> +			       struct max_ser_pipe *pipe,
> +			       struct v4l2_subdev_state *state,
> +			       u64 *streams_masks)
> +{
> +	struct max_ser *ser = priv->ser;
> +	struct max_ser_pipe_mode mode = { 0 };
> +	unsigned int num_dts;
> +	unsigned int *dts;
> +	unsigned int vcs;
> +	int ret;
> +
> +	if (!ser->ops->num_dts_per_pipe)
> +		return 0;
> +
> +	dts = devm_kcalloc(priv->dev, ser->ops->num_dts_per_pipe, sizeof(*dts),
> +			   GFP_KERNEL);
> +	if (!dts)
> +		return -ENOMEM;
> +
> +	ret = max_ser_get_pipe_vcs_dts(priv, state, pipe, &vcs, dts, &num_dts,
> +				       streams_masks);
> +	if (ret)
> +		goto err_free_dts;
> +
> +	ret = max_ser_get_pipe_mode(priv, state, pipe, &mode);
> +	if (ret)
> +		goto err_free_dts;
> +
> +	if (ser->ops->set_pipe_vcs) {
> +		ret = ser->ops->set_pipe_vcs(ser, pipe, vcs);
> +		if (ret)
> +			goto err_free_dts;
> +	}
> +
> +	ret = max_ser_set_pipe_mode(priv, pipe, &mode);
> +	if (ret)
> +		goto err_revert_vcs;
> +
> +	ret = max_ser_set_pipe_dts(priv, pipe, dts, num_dts);
> +	if (ret)
> +		goto err_revert_mode;
> +
> +	pipe->vcs = vcs;
> +	pipe->mode = mode;
> +
> +	if (pipe->dts)
> +		devm_kfree(priv->dev, pipe->dts);
> +
> +	pipe->dts = dts;
> +	pipe->num_dts = num_dts;
> +
> +	return 0;
> +
> +err_revert_mode:
> +	max_ser_set_pipe_mode(priv, pipe, &pipe->mode);
> +
> +err_revert_vcs:
> +	if (ser->ops->set_pipe_vcs)
> +		ser->ops->set_pipe_vcs(ser, pipe, pipe->vcs);
> +
> +err_free_dts:
> +	devm_kfree(priv->dev, dts);
> +
> +	return ret;
> +}
> +
> +static int max_ser_update_phy(struct max_ser_priv *priv,
> +			      struct v4l2_subdev_state *state,
> +			      struct max_ser_phy *phy, u64 *streams_masks)
> +{
> +	struct max_ser *ser = priv->ser;
> +	u32 pad = max_ser_phy_to_pad(ser, phy);
> +	bool enable_changed = !streams_masks[pad] != !priv->streams_masks[pad];
> +	bool enable = !!streams_masks[pad];
> +	struct max_ser_pipe *pipe;
> +	int ret;
> +
> +	pipe = max_ser_find_phy_pipe(ser, phy);
> +	if (!pipe)
> +		return -ENOENT;
> +
> +	if (!enable && enable_changed) {
> +		ret = max_ser_phy_set_active(ser, phy, enable);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = max_ser_update_pipe(priv, pipe, state, streams_masks);
> +	if (ret)
> +		goto err_revert_phy_disable;
> +
> +	ret = max_ser_update_pipe_enable(priv, pipe, state, streams_masks);
> +	if (ret)
> +		goto err_revert_pipe_update;
> +
> +	if (enable && enable_changed) {
> +		ret = max_ser_phy_set_active(ser, phy, enable);
> +		if (ret)
> +			goto err_revert_update_pipe_enable;
> +	}
> +
> +	return 0;
> +
> +err_revert_update_pipe_enable:
> +	max_ser_update_pipe_enable(priv, pipe, state, priv->streams_masks);
> +
> +err_revert_pipe_update:
> +	max_ser_update_pipe(priv, pipe, state, priv->streams_masks);
> +
> +err_revert_phy_disable:
> +	if (!enable && enable_changed)
> +		max_ser_phy_set_active(ser, phy, !enable);
> +
> +	return ret;
> +}
> +
> +static int max_ser_update_phys(struct max_ser_priv *priv,
> +			       struct v4l2_subdev_state *state,
> +			       u64 *streams_masks)
> +{
> +	struct max_ser *ser = priv->ser;
> +	unsigned int failed_update_phy_id = ser->ops->num_phys;
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < ser->ops->num_phys; i++) {
> +		struct max_ser_phy *phy = &ser->phys[i];
> +
> +		ret = max_ser_update_phy(priv, state, phy, streams_masks);
> +		if (ret) {
> +			failed_update_phy_id = i;
> +			goto err;
> +		}
> +	}
> +
> +	return 0;
> +
> +err:
> +	for (i = 0; i < failed_update_phy_id; i++) {
> +		struct max_ser_phy *phy = &ser->phys[i];
> +
> +		max_ser_update_phy(priv, state, phy, priv->streams_masks);
> +	}
> +
> +	return ret;
> +}
> +
> +static int max_ser_enable_disable_streams(struct max_ser_priv *priv,
> +					  struct v4l2_subdev_state *state,
> +					  u32 pad, u64 updated_streams_mask,
> +					  bool enable)
> +{
> +	struct max_ser *ser = priv->ser;
> +
> +	return max_xlate_enable_disable_streams(priv->sources, 0, state,
> +						pad, updated_streams_mask, 0,
> +						ser->ops->num_phys, enable);
> +}
> +
> +static bool max_ser_is_tpg_routed(struct max_ser_priv *priv,
> +				  struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_subdev_route *route;
> +	int ret;
> +
> +	for_each_active_route(&state->routing, route) {
> +		struct max_ser_route_hw hw;
> +
> +		ret = max_ser_route_to_hw(priv, state, route, &hw);
> +		if (ret)
> +			return false;
> +
> +		if (hw.is_tpg)
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +static int max_ser_update_tpg(struct max_ser_priv *priv,
> +			      struct v4l2_subdev_state *state,
> +			      u64 *streams_masks)
> +{
> +	const struct max_tpg_entry *entry = NULL;
> +	struct max_ser *ser = priv->ser;
> +	struct v4l2_subdev_route *route;
> +	int ret;
> +
> +	for_each_active_route(&state->routing, route) {
> +		struct max_ser_route_hw hw;
> +
> +		if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
> +			continue;
> +
> +		ret = max_ser_route_to_hw(priv, state, route, &hw);
> +		if (ret)
> +			return ret;
> +
> +		if (!hw.is_tpg)
> +			continue;
> +
> +		entry = max_ser_find_state_tpg_entry(ser, state, route->sink_pad);
> +		break;
> +	}
> +
> +	ret = ser->ops->set_tpg(ser, entry);
> +	if (ret)
> +		return ret;
> +
> +	ser->tpg_entry = entry;
> +
> +	return 0;
> +}
> +
> +static int max_ser_update_streams(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *state,
> +				  u32 pad, u64 updated_streams_mask, bool enable)
> +{
> +	struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
> +	struct max_ser *ser = priv->ser;
> +	unsigned int num_pads = max_ser_num_pads(ser);
> +	u64 *streams_masks;
> +	int ret;
> +
> +	ret = max_get_streams_masks(priv->dev, state, pad, updated_streams_mask,
> +				    num_pads, priv->streams_masks, &streams_masks,
> +				    enable);
> +	if (ret)
> +		return ret;
> +
> +	if (!enable) {
> +		ret = max_ser_enable_disable_streams(priv, state, pad,
> +						     updated_streams_mask, enable);
> +		if (ret)
> +			goto err_free_streams_masks;
> +	}
> +
> +	ret = max_ser_update_tpg(priv, state, streams_masks);
> +	if (ret)
> +		goto err_revert_streams_disable;
> +
> +	ret = max_ser_update_phys(priv, state, streams_masks);
> +	if (ret)
> +		goto err_revert_update_tpg;
> +
> +	if (enable) {
> +		ret = max_ser_enable_disable_streams(priv, state, pad,
> +						     updated_streams_mask, enable);
> +		if (ret)
> +			goto err_revert_phys_update;
> +	}
> +
> +	devm_kfree(priv->dev, priv->streams_masks);
> +	priv->streams_masks = streams_masks;
> +	ser->active = !!streams_masks[pad];
> +
> +	return 0;
> +
> +err_revert_phys_update:
> +	max_ser_update_phys(priv, state, priv->streams_masks);
> +
> +err_revert_update_tpg:
> +	max_ser_update_tpg(priv, state, priv->streams_masks);
> +
> +err_revert_streams_disable:
> +	if (!enable)
> +		max_ser_enable_disable_streams(priv, state, pad,
> +					       updated_streams_mask, !enable);
> +
> +err_free_streams_masks:
> +	devm_kfree(priv->dev, streams_masks);
> +
> +	return ret;
> +}
> +
> +static int max_ser_enable_streams(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *state,
> +				  u32 pad, u64 streams_mask)
> +{
> +	return max_ser_update_streams(sd, state, pad, streams_mask, true);
> +}
> +
> +static int max_ser_disable_streams(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state,
> +				   u32 pad, u64 streams_mask)
> +{
> +	return max_ser_update_streams(sd, state, pad, streams_mask, false);
> +}
> +
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> +static int max_ser_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg)
> +{
> +	struct max_ser_priv *priv = sd_to_priv(sd);
> +	struct max_ser *ser = priv->ser;
> +	unsigned int val;
> +	int ret;
> +
> +	ret = ser->ops->reg_read(ser, reg->reg, &val);
> +	if (ret)
> +		return ret;
> +
> +	reg->val = val;
> +	reg->size = 1;
> +
> +	return 0;
> +}
> +
> +static int max_ser_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg)
> +{
> +	struct max_ser_priv *priv = sd_to_priv(sd);
> +	struct max_ser *ser = priv->ser;
> +
> +	return ser->ops->reg_write(ser, reg->reg, reg->val);
> +}
> +#endif
> +
> +static const struct v4l2_subdev_core_ops max_ser_core_ops = {
> +	.log_status = max_ser_log_status,
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> +	.g_register = max_ser_g_register,
> +	.s_register = max_ser_s_register,
> +#endif
> +};
> +
> +static const struct v4l2_subdev_pad_ops max_ser_pad_ops = {
> +	.enable_streams = max_ser_enable_streams,
> +	.disable_streams = max_ser_disable_streams,
> +
> +	.set_routing = max_ser_set_routing,
> +	.get_frame_desc = max_ser_get_frame_desc,
> +
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = max_ser_set_fmt,
> +
> +	.enum_frame_interval = max_ser_enum_frame_interval,
> +	.get_frame_interval = v4l2_subdev_get_frame_interval,
> +	.set_frame_interval = max_ser_set_frame_interval,
> +};
> +
> +static const struct v4l2_subdev_ops max_ser_subdev_ops = {
> +	.core = &max_ser_core_ops,
> +	.pad = &max_ser_pad_ops,
> +};
> +
> +static const struct media_entity_operations max_ser_media_ops = {
> +	.link_validate = v4l2_subdev_link_validate,
> +	.get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
> +};
> +
> +static int max_ser_init(struct max_ser_priv *priv)
> +{
> +	struct max_ser *ser = priv->ser;
> +	unsigned int i;
> +	int ret;
> +
> +	if (ser->ops->init) {
> +		ret = ser->ops->init(ser);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (ser->ops->set_tunnel_enable) {
> +		ret = ser->ops->set_tunnel_enable(ser, false);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	for (i = 0; i < ser->ops->num_phys; i++) {
> +		struct max_ser_phy *phy = &ser->phys[i];
> +
> +		if (phy->enabled) {
> +			ret = ser->ops->init_phy(ser, phy);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		if (ser->ops->set_phy_active) {
> +			ret = ser->ops->set_phy_active(ser, phy, false);
> +			if (ret)
> +				return ret;
> +		}
> +	}
> +
> +	for (i = 0; i < ser->ops->num_pipes; i++) {
> +		struct max_ser_pipe *pipe = &ser->pipes[i];
> +		struct max_ser_phy *phy = &ser->phys[pipe->phy_id];
> +
> +		ret = ser->ops->set_pipe_enable(ser, pipe, false);
> +		if (ret)
> +			return ret;
> +
> +		if (ser->ops->set_pipe_stream_id) {
> +			ret = ser->ops->set_pipe_stream_id(ser, pipe, pipe->stream_id);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		if (ser->ops->set_pipe_phy) {
> +			ret = ser->ops->set_pipe_phy(ser, pipe, phy);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		if (ser->ops->set_pipe_vcs) {
> +			ret = ser->ops->set_pipe_vcs(ser, pipe, 0);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		if (ser->ops->set_pipe_mode) {
> +			ret = ser->ops->set_pipe_mode(ser, pipe, &pipe->mode);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		ret = max_ser_set_pipe_dts(priv, pipe, NULL, 0);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int max_ser_notify_bound(struct v4l2_async_notifier *nf,
> +				struct v4l2_subdev *subdev,
> +				struct v4l2_async_connection *base_asc)
> +{
> +	struct max_ser_priv *priv = nf_to_priv(nf);
> +	struct max_asc *asc = asc_to_max(base_asc);
> +	struct max_source *source = asc->source;
> +	u32 pad = source->index;
> +	int ret;
> +
> +	ret = media_entity_get_fwnode_pad(&subdev->entity,
> +					  source->ep_fwnode,
> +					  MEDIA_PAD_FL_SOURCE);
> +	if (ret < 0) {
> +		dev_err(priv->dev, "Failed to find pad for %s\n", subdev->name);
> +		return ret;
> +	}
> +
> +	source->sd = subdev;
> +	source->pad = ret;
> +
> +	ret = media_create_pad_link(&source->sd->entity, source->pad,
> +				    &priv->sd.entity, pad,
> +				    MEDIA_LNK_FL_ENABLED |
> +				    MEDIA_LNK_FL_IMMUTABLE);
> +	if (ret) {
> +		dev_err(priv->dev, "Unable to link %s:%u -> %s:%u\n",
> +			source->sd->name, source->pad, priv->sd.name, pad);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void max_ser_notify_unbind(struct v4l2_async_notifier *nf,
> +				  struct v4l2_subdev *subdev,
> +				  struct v4l2_async_connection *base_asc)
> +{
> +	struct max_asc *asc = asc_to_max(base_asc);
> +	struct max_source *source = asc->source;
> +
> +	source->sd = NULL;
> +}
> +
> +static const struct v4l2_async_notifier_operations max_ser_notify_ops = {
> +	.bound = max_ser_notify_bound,
> +	.unbind = max_ser_notify_unbind,
> +};
> +
> +static int max_ser_v4l2_notifier_register(struct max_ser_priv *priv)
> +{
> +	struct max_ser *ser = priv->ser;
> +	unsigned int i;
> +	int ret;
> +
> +	v4l2_async_subdev_nf_init(&priv->nf, &priv->sd);
> +
> +	for (i = 0; i < ser->ops->num_phys; i++) {
> +		struct max_ser_phy *phy = &ser->phys[i];
> +		struct max_source *source;
> +		struct max_asc *asc;
> +
> +		source = max_ser_get_phy_source(priv, phy);
> +		if (!source->ep_fwnode)
> +			continue;
> +
> +		asc = v4l2_async_nf_add_fwnode(&priv->nf, source->ep_fwnode,
> +					       struct max_asc);
> +		if (IS_ERR(asc)) {
> +			dev_err(priv->dev,
> +				"Failed to add subdev for source %u: %pe", i,
> +				asc);
> +
> +			v4l2_async_nf_cleanup(&priv->nf);
> +
> +			return PTR_ERR(asc);
> +		}
> +
> +		asc->source = source;
> +	}
> +
> +	priv->nf.ops = &max_ser_notify_ops;
> +
> +	ret = v4l2_async_nf_register(&priv->nf);
> +	if (ret) {
> +		dev_err(priv->dev, "Failed to register subdev notifier");
> +		v4l2_async_nf_cleanup(&priv->nf);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void max_ser_v4l2_notifier_unregister(struct max_ser_priv *priv)
> +{
> +	v4l2_async_nf_unregister(&priv->nf);
> +	v4l2_async_nf_cleanup(&priv->nf);
> +}
> +
> +static int max_ser_v4l2_register(struct max_ser_priv *priv)
> +{
> +	struct v4l2_subdev *sd = &priv->sd;
> +	struct max_ser *ser = priv->ser;
> +	void *data = i2c_get_clientdata(priv->client);
> +	unsigned int num_pads = max_ser_num_pads(ser);
> +	unsigned int i;
> +	int ret;
> +
> +	v4l2_i2c_subdev_init(sd, priv->client, &max_ser_subdev_ops);
> +	i2c_set_clientdata(priv->client, data);
> +	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> +	sd->entity.ops = &max_ser_media_ops;
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
> +
> +	priv->pads = devm_kcalloc(priv->dev, num_pads, sizeof(*priv->pads), GFP_KERNEL);
> +	if (!priv->pads)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < num_pads; i++) {
> +		if (max_ser_pad_is_sink(ser, i))
> +			priv->pads[i].flags = MEDIA_PAD_FL_SINK;
> +		else if (max_ser_pad_is_source(ser, i))
> +			priv->pads[i].flags = MEDIA_PAD_FL_SOURCE;
> +		else if (max_ser_pad_is_tpg(ser, i))
> +			priv->pads[i].flags = MEDIA_PAD_FL_SINK |
> +					      MEDIA_PAD_FL_INTERNAL;
> +		else
> +			return -EINVAL;
> +	}
> +
> +	v4l2_set_subdevdata(sd, priv);
> +
> +	ret = media_entity_pads_init(&sd->entity, num_pads, priv->pads);
> +	if (ret)
> +		return ret;
> +
> +	ret = max_ser_v4l2_notifier_register(priv);
> +	if (ret)
> +		goto err_media_entity_cleanup;
> +
> +	ret = v4l2_subdev_init_finalize(sd);
> +	if (ret)
> +		goto err_nf_cleanup;
> +
> +	ret = v4l2_async_register_subdev(sd);
> +	if (ret)
> +		goto err_sd_cleanup;
> +
> +	return 0;
> +
> +err_sd_cleanup:
> +	v4l2_subdev_cleanup(sd);
> +err_nf_cleanup:
> +	max_ser_v4l2_notifier_unregister(priv);
> +err_media_entity_cleanup:
> +	media_entity_cleanup(&sd->entity);
> +
> +	return ret;
> +}
> +
> +static void max_ser_v4l2_unregister(struct max_ser_priv *priv)
> +{
> +	struct v4l2_subdev *sd = &priv->sd;
> +
> +	max_ser_v4l2_notifier_unregister(priv);
> +	v4l2_async_unregister_subdev(sd);
> +	v4l2_subdev_cleanup(sd);
> +	media_entity_cleanup(&sd->entity);
> +}
> +
> +static int max_ser_parse_sink_dt_endpoint(struct max_ser_priv *priv,
> +					  struct max_ser_phy *phy,
> +					  struct max_source *source,
> +					  struct fwnode_handle *fwnode)
> +{
> +	struct max_ser *ser = priv->ser;
> +	u32 pad = max_ser_phy_to_pad(ser, phy);
> +	struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
> +	struct fwnode_handle *ep;
> +	int ret;
> +
> +	ep = fwnode_graph_get_endpoint_by_id(fwnode, pad, 0, 0);
> +	if (!ep)
> +		return 0;
> +
> +	source->ep_fwnode = fwnode_graph_get_remote_endpoint(ep);
> +	if (!source->ep_fwnode) {
> +		dev_err(priv->dev,
> +			"Failed to get remote endpoint on port %u\n", pad);
> +		return -EINVAL;
> +	}
> +
> +	ret = v4l2_fwnode_endpoint_parse(ep, &v4l2_ep);
> +	fwnode_handle_put(ep);
> +	if (ret) {
> +		dev_err(priv->dev, "Could not parse endpoint on port %u\n", pad);
> +		return ret;
> +	}
> +
> +	phy->mipi = v4l2_ep.bus.mipi_csi2;
> +	phy->enabled = true;
> +
> +	return 0;
> +}
> +
> +static int max_ser_find_phys_config(struct max_ser_priv *priv)
> +{
> +	struct max_ser *ser = priv->ser;
> +	const struct max_phys_configs *configs = &ser->ops->phys_configs;
> +	struct max_ser_phy *phy;
> +	unsigned int i, j;
> +
> +	if (!configs->num_configs)
> +		return 0;
> +
> +	for (i = 0; i < configs->num_configs; i++) {
> +		const struct max_phys_config *config = &configs->configs[i];
> +		bool matching = true;
> +
> +		for (j = 0; j < ser->ops->num_phys; j++) {
> +			phy = &ser->phys[j];
> +
> +			if (!phy->enabled)
> +				continue;
> +
> +			if (phy->mipi.num_data_lanes <= config->lanes[j])
> +				continue;
> +
> +			matching = false;
> +
> +			break;
> +		}
> +
> +		if (matching)
> +			break;
> +	}
> +
> +	if (i == configs->num_configs) {
> +		dev_err(priv->dev, "Invalid lane configuration\n");
> +		return -EINVAL;
> +	}
> +
> +	ser->phys_config = i;
> +
> +	return 0;
> +}
> +
> +static int max_ser_parse_dt(struct max_ser_priv *priv)
> +{
> +	struct fwnode_handle *fwnode = dev_fwnode(priv->dev);
> +	struct max_ser *ser = priv->ser;
> +	struct max_ser_pipe *pipe;
> +	struct max_ser_phy *phy;
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < ser->ops->num_phys; i++) {
> +		phy = &ser->phys[i];
> +		phy->index = i;
> +	}
> +
> +	for (i = 0; i < ser->ops->num_pipes; i++) {
> +		pipe = &ser->pipes[i];
> +		pipe->index = i;
> +		pipe->phy_id = i % ser->ops->num_phys;
> +		pipe->stream_id = i % MAX_SERDES_STREAMS_NUM;
> +	}
> +
> +	for (i = 0; i < ser->ops->num_phys; i++) {
> +		struct max_ser_phy *phy = &ser->phys[i];
> +		struct max_source *source;
> +
> +		source = max_ser_get_phy_source(priv, phy);
> +		source->index = i;
> +
> +		ret = max_ser_parse_sink_dt_endpoint(priv, phy, source, fwnode);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return max_ser_find_phys_config(priv);
> +}
> +
> +static int max_ser_allocate(struct max_ser_priv *priv)
> +{
> +	struct max_ser *ser = priv->ser;
> +	unsigned int num_pads = max_ser_num_pads(ser);
> +
> +	ser->phys = devm_kcalloc(priv->dev, ser->ops->num_phys,
> +				 sizeof(*ser->phys), GFP_KERNEL);
> +	if (!ser->phys)
> +		return -ENOMEM;
> +
> +	ser->pipes = devm_kcalloc(priv->dev, ser->ops->num_pipes,
> +				  sizeof(*ser->pipes), GFP_KERNEL);
> +	if (!ser->pipes)
> +		return -ENOMEM;
> +
> +	ser->i2c_xlates = devm_kcalloc(priv->dev, ser->ops->num_i2c_xlates,
> +				       sizeof(*ser->i2c_xlates), GFP_KERNEL);
> +	if (!ser->i2c_xlates)
> +		return -ENOMEM;
> +
> +	priv->sources = devm_kcalloc(priv->dev, ser->ops->num_phys,
> +				     sizeof(*priv->sources), GFP_KERNEL);
> +	if (!priv->sources)
> +		return -ENOMEM;
> +
> +	priv->streams_masks = devm_kcalloc(priv->dev, num_pads,
> +					   sizeof(*priv->streams_masks),
> +					   GFP_KERNEL);
> +	if (!priv->streams_masks)
> +		return -ENOMEM;
> +
> +	return 0;
> +}
> +
> +int max_ser_probe(struct i2c_client *client, struct max_ser *ser)
> +{
> +	struct device *dev = &client->dev;
> +	struct max_ser_priv *priv;
> +	int ret;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->client = client;
> +	priv->dev = dev;
> +	priv->ser = ser;
> +	ser->priv = priv;
> +
> +	ret = max_ser_allocate(priv);
> +	if (ret)
> +		return ret;
> +
> +	ret = max_ser_parse_dt(priv);
> +	if (ret)
> +		return ret;
> +
> +	ret = max_ser_init(priv);
> +	if (ret)
> +		return ret;
> +
> +	ret = max_ser_i2c_adapter_init(priv);
> +	if (ret)
> +		return ret;
> +
> +	ret = max_ser_v4l2_register(priv);
> +	if (ret)
> +		goto err_i2c_adapter_deinit;
> +
> +	return 0;
> +
> +err_i2c_adapter_deinit:
> +	max_ser_i2c_adapter_deinit(priv);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(max_ser_probe);
> +
> +int max_ser_remove(struct max_ser *ser)
> +{
> +	struct max_ser_priv *priv = ser->priv;
> +
> +	max_ser_v4l2_unregister(priv);
> +
> +	max_ser_i2c_adapter_deinit(priv);
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(max_ser_remove);
> +
> +int max_ser_set_double_bpps(struct v4l2_subdev *sd, u32 double_bpps)
> +{
> +	struct max_ser_priv *priv = sd_to_priv(sd);
> +
> +	priv->double_bpps = double_bpps;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(max_ser_set_double_bpps);
> +
> +int max_ser_set_stream_id(struct v4l2_subdev *sd, unsigned int stream_id)
> +{
> +	struct max_ser_priv *priv = sd_to_priv(sd);
> +	struct max_ser *ser = priv->ser;
> +	struct max_ser_pipe *pipe = &ser->pipes[0];
> +	int ret;
> +
> +	if (ser->ops->set_pipe_stream_id) {
> +		ret = ser->ops->set_pipe_stream_id(ser, pipe, stream_id);
> +		if (ret)
> +			return ret;
> +	} else {
> +		stream_id = ser->ops->get_pipe_stream_id(ser, pipe);
> +	}
> +
> +	pipe->stream_id = stream_id;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(max_ser_set_stream_id);
> +
> +int max_ser_get_stream_id(struct v4l2_subdev *sd, unsigned int *stream_id)
> +{
> +	struct max_ser_priv *priv = sd_to_priv(sd);
> +	struct max_ser *ser = priv->ser;
> +	struct max_ser_pipe *pipe = &ser->pipes[0];
> +
> +	*stream_id = pipe->stream_id;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(max_ser_get_stream_id);
> +
> +unsigned int max_ser_get_supported_modes(struct v4l2_subdev *sd)
> +{
> +	struct max_ser_priv *priv = sd_to_priv(sd);
> +	struct max_ser *ser = priv->ser;
> +	struct v4l2_subdev_state *state;
> +	unsigned int modes = ser->ops->modes;
> +
> +	state = v4l2_subdev_lock_and_get_active_state(&priv->sd);
> +
> +	if (max_ser_is_tpg_routed(priv, state))
> +		modes = BIT(ser->ops->tpg_mode);
> +
> +	v4l2_subdev_unlock_state(state);
> +
> +	return modes;
> +}
> +EXPORT_SYMBOL(max_ser_get_supported_modes);

EXPORT_SYMBOL_GPL(), please. Same for the rest.

> +
> +bool max_ser_supports_vc_remap(struct v4l2_subdev *sd)
> +{
> +	struct max_ser_priv *priv = sd_to_priv(sd);
> +	struct max_ser *ser = priv->ser;
> +
> +	return !!ser->ops->set_pipe_vc_remap;
> +}
> +EXPORT_SYMBOL(max_ser_supports_vc_remap);
> +
> +int max_ser_set_mode(struct v4l2_subdev *sd, enum max_gmsl_mode mode)
> +{
> +	struct max_ser_priv *priv = sd_to_priv(sd);
> +	struct max_ser *ser = priv->ser;
> +	int ret;
> +
> +	if (!(ser->ops->modes & BIT(mode)))
> +		return -EINVAL;
> +
> +	if (ser->mode == mode)
> +		return 0;
> +
> +	if (ser->ops->set_tunnel_enable) {
> +		bool tunnel_enable = mode == MAX_GMSL_TUNNEL_MODE;
> +
> +		ret = ser->ops->set_tunnel_enable(ser, tunnel_enable);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ser->mode = mode;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(max_ser_set_mode);
> +
> +int max_ser_set_vc_remaps(struct v4l2_subdev *sd, struct max_vc_remap *vc_remaps,
> +			  int num_vc_remaps)
> +{
> +	struct max_ser_priv *priv = sd_to_priv(sd);
> +	struct max_ser *ser = priv->ser;
> +	struct max_ser_pipe *pipe = &ser->pipes[0];
> +	unsigned int mask = 0;
> +	unsigned int i;
> +	int ret;
> +
> +	if (!ser->ops->set_pipe_vc_remap)
> +		return -EOPNOTSUPP;
> +
> +	for (i = 0; i < num_vc_remaps; i++) {
> +		ret = ser->ops->set_pipe_vc_remap(ser, pipe, i, &vc_remaps[i]);
> +		if (ret)
> +			return ret;
> +
> +		mask |= BIT(i);
> +	}
> +
> +	ret = ser->ops->set_pipe_vc_remaps_enable(ser, pipe, mask);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < num_vc_remaps; i++)
> +		pipe->vc_remaps[i] = vc_remaps[i];
> +
> +	pipe->num_vc_remaps = num_vc_remaps;
> +
> +	return 0;
> +}
> +
> +static int max_ser_read_reg(struct i2c_adapter *adapter, u8 addr,
> +			    u16 reg, u8 *val)
> +{
> +	u8 buf[2] = { reg >> 8, reg & 0xff };
> +	struct i2c_msg msg[2] = {
> +		{
> +			.addr = addr,
> +			.flags = 0,
> +			.buf = buf,
> +			.len = sizeof(buf),
> +		},
> +		{
> +			.addr = addr,
> +			.flags = I2C_M_RD,
> +			.buf = buf,
> +			.len = 1,
> +		},
> +	};
> +	int ret;
> +
> +	ret = i2c_transfer(adapter, msg, ARRAY_SIZE(msg));
> +	if (ret < 0)
> +		return ret;
> +
> +	*val = buf[0];
> +
> +	return 0;
> +}
> +
> +static int max_ser_write_reg(struct i2c_adapter *adapter, u8 addr,
> +			     u16 reg, u8 val)
> +{
> +	u8 buf[3] = { reg >> 8, reg & 0xff, val };
> +	struct i2c_msg msg[1] = {
> +		{
> +			.addr = addr,
> +			.flags = 0,
> +			.buf = buf,
> +			.len = sizeof(buf),
> +		},
> +	};
> +	int ret;
> +
> +	ret = i2c_transfer(adapter, msg, ARRAY_SIZE(msg));
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +int max_ser_reset(struct i2c_adapter *adapter, u8 addr)
> +{
> +	int ret;
> +	u8 val;
> +
> +	ret = max_ser_read_reg(adapter, addr, MAX_SER_CTRL0, &val);
> +	if (ret)
> +		return ret;
> +
> +	val |= MAX_SER_CTRL0_RESET_ALL;
> +
> +	return max_ser_write_reg(adapter, addr, MAX_SER_CTRL0, val);
> +}
> +EXPORT_SYMBOL_GPL(max_ser_reset);
> +
> +int max_ser_wait_for_multiple(struct i2c_adapter *adapter, u8 *addrs,
> +			      unsigned int num_addrs, u8 *current_addr)
> +{
> +	unsigned int i, j;
> +	int ret = 0;
> +	u8 val;
> +
> +	for (i = 0; i < 10; i++) {
> +		for (j = 0; j < num_addrs; j++) {
> +			ret = max_ser_read_reg(adapter, addrs[j], MAX_SER_REG0, &val);
> +			if (!ret && val) {
> +				*current_addr = addrs[j];
> +				return 0;
> +			}
> +
> +			msleep(100);
> +		}
> +	}
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(max_ser_wait_for_multiple);
> +
> +int max_ser_wait(struct i2c_adapter *adapter, u8 addr)
> +{
> +	u8 current_addr;
> +
> +	return max_ser_wait_for_multiple(adapter, &addr, 1, &current_addr);
> +}
> +EXPORT_SYMBOL_GPL(max_ser_wait);
> +
> +int max_ser_fix_tx_ids(struct i2c_adapter *adapter, u8 addr)
> +{
> +	unsigned int addr_regs[] = {
> +		MAX_SER_CFGI_INFOFR_TR3,
> +		MAX_SER_CFGL_SPI_TR3,
> +		MAX_SER_CFGC_CC_TR3,
> +		MAX_SER_CFGC_GPIO_TR3,
> +		MAX_SER_CFGL_IIC_X_TR3,
> +		MAX_SER_CFGL_IIC_Y_TR3,
> +	};
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < ARRAY_SIZE(addr_regs); i++) {
> +		ret = max_ser_write_reg(adapter, addr, addr_regs[i], addr);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(max_ser_fix_tx_ids);
> +
> +int max_ser_change_address(struct i2c_adapter *adapter, u8 addr, u8 new_addr)
> +{
> +	u8 val = FIELD_PREP(MAX_SER_REG0_DEV_ADDR, new_addr);
> +
> +	return max_ser_write_reg(adapter, addr, MAX_SER_REG0, val);
> +}
> +EXPORT_SYMBOL_GPL(max_ser_change_address);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("I2C_ATR");
> diff --git a/drivers/media/i2c/maxim-serdes/max_ser.h b/drivers/media/i2c/maxim-serdes/max_ser.h
> new file mode 100644
> index 000000000000..cefbc613d733
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max_ser.h
> @@ -0,0 +1,144 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2025 Analog Devices Inc.
> + */
> +
> +#ifndef MAX_SER_H
> +#define MAX_SER_H
> +
> +#include <linux/i2c.h>
> +
> +#include <media/v4l2-mediabus.h>
> +
> +#include "max_serdes.h"
> +
> +#define MAX_SER_REG0				0x0
> +#define MAX_SER_REG0_DEV_ADDR			GENMASK(7, 1)
> +
> +#define MAX_SER_CTRL0				0x10
> +#define MAX_SER_CTRL0_RESET_ALL			BIT(7)
> +
> +#define MAX_SER_CFGI_INFOFR_TR3			0x7b
> +#define MAX_SER_CFGL_SPI_TR3			0x83
> +#define MAX_SER_CFGC_CC_TR3			0x8b
> +#define MAX_SER_CFGC_GPIO_TR3			0x93
> +#define MAX_SER_CFGL_IIC_X_TR3			0xa3
> +#define MAX_SER_CFGL_IIC_Y_TR3			0xab
> +
> +struct max_ser_phy {
> +	unsigned int index;
> +	struct v4l2_mbus_config_mipi_csi2 mipi;
> +	bool enabled;
> +	bool active;
> +};
> +
> +struct max_ser_pipe_mode {
> +	unsigned int soft_bpp;
> +	unsigned int bpp;
> +	bool dbl8;
> +	bool dbl10;
> +	bool dbl12;
> +};
> +
> +struct max_ser_pipe {
> +	unsigned int index;
> +	unsigned int phy_id;
> +	unsigned int stream_id;
> +	unsigned int *dts;
> +	unsigned int num_dts;
> +	unsigned int vcs;
> +	struct max_vc_remap vc_remaps[MAX_SERDES_VC_ID_NUM];
> +	unsigned int num_vc_remaps;
> +	struct max_ser_pipe_mode mode;
> +	bool enabled;
> +};
> +
> +struct max_ser;
> +
> +struct max_ser_ops {
> +	unsigned int modes;
> +	unsigned int num_pipes;
> +	unsigned int num_dts_per_pipe;
> +	unsigned int num_phys;
> +	unsigned int num_i2c_xlates;
> +
> +	struct max_phys_configs phys_configs;
> +	struct max_tpg_entries tpg_entries;
> +	enum max_gmsl_mode tpg_mode;
> +
> +	int (*reg_read)(struct max_ser *ser, unsigned int reg, unsigned int *val);
> +	int (*reg_write)(struct max_ser *ser, unsigned int reg, unsigned int val);
> +	int (*log_status)(struct max_ser *ser, const char *name);
> +	int (*log_pipe_status)(struct max_ser *ser, struct max_ser_pipe *pipe,
> +			       const char *name);
> +	int (*log_phy_status)(struct max_ser *ser, struct max_ser_phy *phy,
> +			      const char *name);
> +	int (*init)(struct max_ser *ser);
> +	int (*set_i2c_xlate)(struct max_ser *ser, unsigned int i,
> +			     struct max_i2c_xlate *i2c_xlate);
> +	int (*set_tunnel_enable)(struct max_ser *ser, bool enable);
> +	int (*set_tpg)(struct max_ser *ser, const struct max_tpg_entry *entry);
> +	int (*init_phy)(struct max_ser *ser, struct max_ser_phy *phy);
> +	int (*set_phy_active)(struct max_ser *ser, struct max_ser_phy *phy,
> +			      bool enable);
> +	int (*set_pipe_enable)(struct max_ser *ser, struct max_ser_pipe *pipe,
> +			       bool enable);
> +	int (*set_pipe_dt)(struct max_ser *ser, struct max_ser_pipe *pipe,
> +			   unsigned int i, unsigned int dt);
> +	int (*set_pipe_dt_en)(struct max_ser *ser, struct max_ser_pipe *pipe,
> +			      unsigned int i, bool enable);
> +	int (*set_pipe_vcs)(struct max_ser *ser, struct max_ser_pipe *pipe,
> +			    unsigned int vcs);
> +	int (*set_pipe_mode)(struct max_ser *ser, struct max_ser_pipe *pipe,
> +			     struct max_ser_pipe_mode *mode);
> +	int (*set_pipe_vc_remap)(struct max_ser *ser, struct max_ser_pipe *pipe,
> +				 unsigned int i, struct max_vc_remap *vc_remap);
> +	int (*set_pipe_vc_remaps_enable)(struct max_ser *ser, struct max_ser_pipe *pipe,
> +					 unsigned int mask);
> +	int (*set_pipe_stream_id)(struct max_ser *ser, struct max_ser_pipe *pipe,
> +				  unsigned int stream_id);
> +	unsigned int (*get_pipe_stream_id)(struct max_ser *ser, struct max_ser_pipe *pipe);
> +	int (*set_pipe_phy)(struct max_ser *ser, struct max_ser_pipe *pipe,
> +			    struct max_ser_phy *phy);
> +};
> +
> +struct max_ser_priv;
> +
> +struct max_ser {
> +	struct max_ser_priv *priv;
> +
> +	const struct max_ser_ops *ops;
> +
> +	struct max_i2c_xlate *i2c_xlates;
> +
> +	struct max_ser_phy *phys;
> +	struct max_ser_pipe *pipes;
> +	const struct max_tpg_entry *tpg_entry;
> +
> +	unsigned int phys_config;
> +	unsigned int active;
> +	enum max_gmsl_mode mode;
> +};
> +
> +int max_ser_probe(struct i2c_client *client, struct max_ser *ser);
> +
> +int max_ser_remove(struct max_ser *ser);
> +
> +int max_ser_set_double_bpps(struct v4l2_subdev *sd, u32 double_bpps);
> +unsigned int max_ser_get_supported_modes(struct v4l2_subdev *sd);
> +int max_ser_set_mode(struct v4l2_subdev *sd, enum max_gmsl_mode mode);
> +bool max_ser_supports_vc_remap(struct v4l2_subdev *sd);
> +int max_ser_set_stream_id(struct v4l2_subdev *sd, unsigned int stream_id);
> +int max_ser_get_stream_id(struct v4l2_subdev *sd, unsigned int *stream_id);
> +int max_ser_set_vc_remaps(struct v4l2_subdev *sd, struct max_vc_remap *vc_remaps,
> +			  int num_vc_remaps);
> +
> +int max_ser_reset(struct i2c_adapter *adapter, u8 addr);
> +int max_ser_wait(struct i2c_adapter *adapter, u8 addr);
> +int max_ser_wait_for_multiple(struct i2c_adapter *adapter, u8 *addrs,
> +			      unsigned int num_addrs, u8 *current_addr);
> +
> +int max_ser_change_address(struct i2c_adapter *adapter, u8 addr, u8 new_addr);
> +int max_ser_fix_tx_ids(struct i2c_adapter *adapter, u8 addr);
> +
> +#endif // MAX_SER_H
> diff --git a/drivers/media/i2c/maxim-serdes/max_serdes.c b/drivers/media/i2c/maxim-serdes/max_serdes.c
> new file mode 100644
> index 000000000000..55c4a22ce563
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max_serdes.c
> @@ -0,0 +1,415 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2025 Analog Devices Inc.
> + */
> +
> +#include <linux/export.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/stringify.h>
> +
> +#include <media/mipi-csi2.h>
> +
> +#include <uapi/linux/media-bus-format.h>
> +
> +#include "max_serdes.h"
> +
> +#define MAX_MIPI_FMT(_dt, _bpp)	\
> +{				\
> +	.dt = (_dt),		\
> +	.bpp = (_bpp),		\
> +}
> +
> +static const char * const max_gmsl_versions[] = {
> +	[MAX_GMSL_2_3GBPS] = "GMSL2 3Gbps",
> +	[MAX_GMSL_2_6GBPS] = "GMSL2 6Gbps",
> +	[MAX_GMSL_3] = "GMSL3",
> +};
> +
> +const char *max_gmsl_version_str(enum max_gmsl_version version)
> +{
> +	if (version > MAX_GMSL_3)
> +		return NULL;
> +
> +	return max_gmsl_versions[version];
> +}
> +EXPORT_SYMBOL(max_gmsl_version_str);
> +
> +static const char * const max_gmsl_mode[] = {
> +	[MAX_GMSL_PIXEL_MODE] = "pixel",
> +	[MAX_GMSL_TUNNEL_MODE] = "tunnel",
> +};
> +
> +const char *max_gmsl_mode_str(enum max_gmsl_mode mode)
> +{
> +	if (mode > MAX_GMSL_TUNNEL_MODE)
> +		return NULL;
> +
> +	return max_gmsl_mode[mode];
> +}
> +EXPORT_SYMBOL(max_gmsl_mode_str);
> +
> +static const struct max_mipi_format max_mipi_formats[] = {
> +	MAX_MIPI_FMT(MIPI_CSI2_DT_EMBEDDED_8B, 8),
> +	MAX_MIPI_FMT(MIPI_CSI2_DT_YUV422_8B, 16),
> +	MAX_MIPI_FMT(MIPI_CSI2_DT_YUV422_10B, 20),
> +	MAX_MIPI_FMT(MIPI_CSI2_DT_RGB565, 16),
> +	MAX_MIPI_FMT(MIPI_CSI2_DT_RGB666, 18),
> +	MAX_MIPI_FMT(MIPI_CSI2_DT_RGB888, 24),
> +	MAX_MIPI_FMT(MIPI_CSI2_DT_RAW8, 8),
> +	MAX_MIPI_FMT(MIPI_CSI2_DT_RAW10, 10),
> +	MAX_MIPI_FMT(MIPI_CSI2_DT_RAW12, 12),
> +	MAX_MIPI_FMT(MIPI_CSI2_DT_RAW14, 14),
> +	MAX_MIPI_FMT(MIPI_CSI2_DT_RAW16, 16),
> +};
> +
> +const struct max_mipi_format *max_mipi_format_by_dt(u8 dt)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(max_mipi_formats); i++)
> +		if (max_mipi_formats[i].dt == dt)
> +			return &max_mipi_formats[i];
> +
> +	return NULL;
> +}
> +EXPORT_SYMBOL_GPL(max_mipi_format_by_dt);
> +
> +int max_get_fd_stream_entry(struct v4l2_subdev *sd, u32 pad, u32 stream,
> +			    struct v4l2_mbus_frame_desc_entry *entry)
> +{
> +	struct v4l2_mbus_frame_desc fd;
> +	unsigned int i;
> +	int ret;
> +
> +	ret = v4l2_subdev_call(sd, pad, get_frame_desc, pad, &fd);
> +	if (ret)
> +		return ret;
> +
> +	if (fd.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2)
> +		return -EOPNOTSUPP;
> +
> +	for (i = 0; i < fd.num_entries; i++) {
> +		if (fd.entry[i].stream == stream) {
> +			*entry = fd.entry[i];
> +			return 0;
> +		}
> +	}
> +
> +	return -ENOENT;
> +}
> +EXPORT_SYMBOL(max_get_fd_stream_entry);
> +
> +int max_get_fd_bpp(struct v4l2_mbus_frame_desc_entry *entry, unsigned int *bpp)
> +{
> +	const struct max_mipi_format *format;
> +
> +	format = max_mipi_format_by_dt(entry->bus.csi2.dt);
> +	if (!format)
> +		return -ENOENT;
> +
> +	*bpp = format->bpp;
> +
> +	return 0;
> +}
> +
> +int max_process_bpps(struct device *dev, u32 bpps, u32 allowed_double_bpps,
> +		     unsigned int *doubled_bpp)
> +{
> +	unsigned int min_bpp;
> +	unsigned int max_bpp;
> +	bool doubled = false;
> +
> +	if (!bpps)
> +		return 0;
> +
> +	*doubled_bpp = 0;
> +
> +	/*
> +	 * Hardware can double bpps 8, 10, 12, and it can pad bpps < 16
> +	 * to another bpp <= 16:
> +	 * Hardware can only stream a single constant bpp up to 24.
> +	 *
> +	 * From these features and limitations, the following rules
> +	 * can be deduced:
> +	 *
> +	 * A bpp of 8 can always be doubled if present.
> +	 * A bpp of 10 can be doubled only if there are no other bpps or the
> +	 * only other bpp is 20.
> +	 * A bpp of 12 can be doubled only if there are no other bpps or the
> +	 * only other bpp is 24.
> +	 * Bpps <= 16 cannot coexist with bpps > 16.
> +	 * Bpps <= 16 need to be padded to the biggest bpp.
> +	 */
> +
> +	min_bpp = __ffs(bpps);
> +	max_bpp = __fls(bpps);
> +
> +	if (min_bpp == 8) {
> +		doubled = true;
> +	} else if (min_bpp == 10 || min_bpp == 12) {
> +		u32 bpp_or_double = BIT(min_bpp) | BIT(min_bpp * 2);
> +		u32 other_bpps = bpps & ~bpp_or_double;
> +
> +		if (!other_bpps)
> +			doubled = true;
> +	}
> +
> +	if (doubled && (allowed_double_bpps & BIT(min_bpp))) {
> +		*doubled_bpp = min_bpp;
> +		bpps &= ~BIT(min_bpp);
> +		bpps |= BIT(min_bpp * 2);
> +	}
> +
> +	min_bpp = __ffs(bpps);
> +	max_bpp = __fls(bpps);
> +
> +	if (max_bpp > 24) {
> +		dev_err(dev, "Cannot stream bpps > 24\n");
> +		return -EINVAL;
> +	}
> +
> +	if (min_bpp <= 16 && max_bpp > 16) {
> +		dev_err(dev, "Cannot stream bpps <= 16 with bpps > 16\n");
> +		return -EINVAL;
> +	}
> +
> +	if (max_bpp > 16 && min_bpp != max_bpp) {
> +		dev_err(dev, "Cannot stream multiple bpps when one is > 16\n");
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(max_process_bpps);
> +
> +int max_xlate_enable_disable_streams(struct max_source *sources,
> +				     u32 source_sink_pad_offset,
> +				     const struct v4l2_subdev_state *state,
> +				     u32 pad, u64 updated_streams_mask,
> +				     u32 sink_pad_start, u32 num_sink_pads,
> +				     bool enable)
> +{
> +	u32 failed_sink_pad;
> +	int ret;
> +	u32 i;
> +
> +	for (i = sink_pad_start; i < sink_pad_start + num_sink_pads; i++) {
> +		u64 matched_streams_mask = updated_streams_mask;
> +		u64 updated_sink_streams_mask;
> +		struct max_source *source;
> +
> +		updated_sink_streams_mask =
> +			v4l2_subdev_state_xlate_streams(state, pad, i,
> +							&matched_streams_mask);
> +		if (!updated_sink_streams_mask)
> +			continue;
> +
> +		source = &sources[i + source_sink_pad_offset];
> +		if (!source)
> +			continue;
> +
> +		if (enable)
> +			ret = v4l2_subdev_enable_streams(source->sd, source->pad,
> +							 updated_sink_streams_mask);
> +		else
> +			ret = v4l2_subdev_disable_streams(source->sd, source->pad,
> +							  updated_sink_streams_mask);
> +		if (ret) {
> +			failed_sink_pad = i;
> +			goto err;
> +		}
> +	}
> +
> +	return 0;
> +
> +err:
> +	for (i = sink_pad_start; i < failed_sink_pad; i++) {
> +		u64 matched_streams_mask = updated_streams_mask;
> +		u64 updated_sink_streams_mask;
> +		struct max_source *source;
> +
> +		updated_sink_streams_mask =
> +			v4l2_subdev_state_xlate_streams(state, pad, i,
> +							&matched_streams_mask);
> +		if (!updated_sink_streams_mask)
> +			continue;
> +
> +		source = &sources[i + source_sink_pad_offset];
> +		if (!source)
> +			continue;
> +
> +		if (!enable)
> +			v4l2_subdev_enable_streams(source->sd, source->pad,
> +						   updated_sink_streams_mask);
> +		else
> +			v4l2_subdev_disable_streams(source->sd, source->pad,
> +						    updated_sink_streams_mask);
> +	}
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(max_xlate_enable_disable_streams);
> +
> +int max_get_streams_masks(struct device *dev,
> +			  const struct v4l2_subdev_state *state,
> +			  u32 pad, u64 updated_streams_mask,
> +			  u32 num_pads, u64 *old_streams_masks,
> +			  u64 **new_streams_masks, bool enable)
> +{
> +	u64 *streams_masks;
> +	unsigned int i;
> +
> +	streams_masks = devm_kcalloc(dev, num_pads, sizeof(*streams_masks), GFP_KERNEL);
> +	if (!streams_masks)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < num_pads; i++) {
> +		u64 matched_streams_mask = updated_streams_mask;
> +		u64 updated_sink_streams_mask;
> +
> +		updated_sink_streams_mask =
> +			v4l2_subdev_state_xlate_streams(state, pad, i,
> +							&matched_streams_mask);
> +		if (!updated_sink_streams_mask)
> +			continue;
> +
> +		streams_masks[i] = old_streams_masks[i];
> +		if (enable)
> +			streams_masks[i] |= updated_sink_streams_mask;
> +		else
> +			streams_masks[i] &= ~updated_sink_streams_mask;
> +	}
> +
> +	if (enable)
> +		streams_masks[pad] |= updated_streams_mask;
> +	else
> +		streams_masks[pad] &= ~updated_streams_mask;
> +
> +	*new_streams_masks = streams_masks;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(max_get_streams_masks);
> +
> +static const struct videomode max_tpg_pixel_videomodes[] = {
> +	{
> +		.pixelclock = 25000000,
> +		.hactive = 640,
> +		.hfront_porch = 10,
> +		.hsync_len = 96,
> +		.hback_porch = 40,
> +		.vactive = 480,
> +		.vfront_porch = 2,
> +		.vsync_len = 24,
> +		.vback_porch = 24,
> +	},
> +	{
> +		.pixelclock = 75000000,
> +		.hactive = 1920,
> +		.hfront_porch = 88,
> +		.hsync_len = 44,
> +		.hback_porch = 148,
> +		.vactive = 1080,
> +		.vfront_porch = 4,
> +		.vsync_len = 16,
> +		.vback_porch = 36,
> +	},
> +	{
> +		.pixelclock = 150000000,
> +		.hactive = 1920,
> +		.hfront_porch = 88,
> +		.hsync_len = 44,
> +		.hback_porch = 148,
> +		.vactive = 1080,
> +		.vfront_porch = 4,
> +		.vsync_len = 16,
> +		.vback_porch = 36,
> +	},
> +};
> +
> +void max_get_tpg_timings(const struct videomode *vm,
> +			 struct max_tpg_timings *timings)
> +{
> +	u32 hact = vm->hactive;
> +	u32 hfp = vm->hfront_porch;
> +	u32 hsync = vm->hsync_len;
> +	u32 hbp = vm->hback_porch;
> +	u32 htot = hact + hfp + hbp + hsync;
> +
> +	u32 vact = vm->vactive;
> +	u32 vfp = vm->vfront_porch;
> +	u32 vsync = vm->vsync_len;
> +	u32 vbp = vm->vback_porch;
> +	u32 vtot = vact + vfp + vbp + vsync;
> +
> +	*timings = (struct max_tpg_timings) {
> +		.gen_vs = true,
> +		.gen_hs = true,
> +		.gen_de = true,
> +		.vs_inv = true,
> +		.vs_dly = 0,
> +		.vs_high = vsync * htot,
> +		.vs_low = (vact + vfp + vbp) * htot,
> +		.v2h = 0,
> +		.hs_high = hsync,
> +		.hs_low = hact + hfp + hbp,
> +		.hs_cnt = vact + vfp + vbp + vsync,
> +		.v2d = htot * (vsync + vbp) + (hsync + hbp),
> +		.de_high = hact,
> +		.de_low = hfp + hsync + hbp,
> +		.de_cnt = vact,
> +		.fps = DIV_ROUND_CLOSEST(vm->pixelclock, vtot * htot),
> +	};
> +}
> +EXPORT_SYMBOL(max_get_tpg_timings);
> +
> +const struct videomode *
> +max_find_tpg_videomode(const struct max_tpg_entry *entry)
> +{
> +	u32 fps;
> +
> +	if (!entry)
> +		return NULL;
> +
> +	fps = DIV_ROUND_CLOSEST(1 * entry->interval.denominator,
> +				entry->interval.numerator);
> +
> +	for (unsigned int i = 0; i < ARRAY_SIZE(max_tpg_pixel_videomodes); i++) {
> +		const struct videomode *vm = &max_tpg_pixel_videomodes[i];
> +		struct max_tpg_timings timings;
> +
> +		max_get_tpg_timings(vm, &timings);
> +
> +		if (vm->hactive == entry->width &&
> +		    vm->vactive == entry->height &&
> +		    timings.fps == fps)
> +			return &max_tpg_pixel_videomodes[i];
> +	}
> +
> +	return NULL;
> +}
> +EXPORT_SYMBOL(max_find_tpg_videomode);
> +
> +int max_validate_tpg_routing(struct v4l2_subdev_krouting *routing)
> +{
> +	const struct v4l2_subdev_route *route;
> +
> +	if (routing->num_routes != 1)
> +		return -EINVAL;
> +
> +	route = &routing->routes[0];
> +
> +	if (!(route->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE))
> +		return -EINVAL;
> +
> +	if (route->sink_stream != MAX_SERDES_TPG_STREAM)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(max_validate_tpg_routing);
> +
> +MODULE_DESCRIPTION("Maxim GMSL2 Serializer/Deserializer Driver");
> +MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@...log.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/media/i2c/maxim-serdes/max_serdes.h b/drivers/media/i2c/maxim-serdes/max_serdes.h
> new file mode 100644
> index 000000000000..a4cb59bdf5c2
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max_serdes.h
> @@ -0,0 +1,166 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2025 Analog Devices Inc.
> + */
> +
> +#ifndef MAX_SERDES_H
> +#define MAX_SERDES_H
> +
> +#include <linux/types.h>
> +
> +#include <media/mipi-csi2.h>
> +#include <media/v4l2-subdev.h>
> +
> +#include <video/videomode.h>
> +
> +#define REG_SEQUENCE_2(reg, val) \
> +	{ (reg),     ((val) >> 8) & 0xff }, \
> +	{ (reg) + 1, ((val) >> 0) & 0xff }
> +
> +#define REG_SEQUENCE_3(reg, val) \
> +	{ (reg),     ((val) >> 16) & 0xff }, \
> +	{ (reg) + 1, ((val) >> 8)  & 0xff }, \
> +	{ (reg) + 2, ((val) >> 0)  & 0xff }
> +
> +#define field_get(mask, val) (((val) & (mask)) >> __ffs(mask))
> +#define field_prep(mask, val) (((val) << __ffs(mask)) & (mask))
> +
> +#define MAX_SERDES_PHYS_MAX		4
> +#define MAX_SERDES_STREAMS_NUM		4
> +#define MAX_SERDES_VC_ID_NUM		4
> +#define MAX_SERDES_TPG_STREAM		0
> +
> +enum max_gmsl_version {

How about "maxim_serdes" prefix for the symbols here?

> +	MAX_GMSL_MIN,
> +	MAX_GMSL_2_3GBPS = MAX_GMSL_MIN,
> +	MAX_GMSL_2_6GBPS,
> +	MAX_GMSL_3,
> +	MAX_GMSL_MAX = MAX_GMSL_3,
> +};
> +
> +enum max_gmsl_mode {
> +	MAX_GMSL_PIXEL_MODE,
> +	MAX_GMSL_TUNNEL_MODE,
> +};
> +
> +struct max_phys_config {
> +	unsigned int lanes[MAX_SERDES_PHYS_MAX];
> +	unsigned int clock_lane[MAX_SERDES_PHYS_MAX];
> +};
> +
> +struct max_phys_configs {
> +	const struct max_phys_config *configs;
> +	unsigned int num_configs;
> +};
> +
> +struct max_i2c_xlate {
> +	u8 src;
> +	u8 dst;
> +	bool en;
> +};
> +
> +struct max_mipi_format {
> +	u8 dt;
> +	u8 bpp;
> +};
> +
> +struct max_vc_remap {
> +	u8 src;
> +	u8 dst;
> +};
> +
> +struct max_source {
> +	struct v4l2_subdev *sd;
> +	u16 pad;
> +	struct fwnode_handle *ep_fwnode;
> +
> +	unsigned int index;
> +};
> +
> +struct max_asc {
> +	struct v4l2_async_connection base;
> +	struct max_source *source;
> +};
> +
> +struct max_tpg_entry {
> +	u32 width;
> +	u32 height;
> +	struct v4l2_fract interval;
> +	u32 code;
> +	u8 dt;
> +	u8 bpp;
> +};
> +
> +#define MAX_TPG_ENTRY_640X480P60_RGB888 \
> +	{ 640, 480, { 1, 60 }, MEDIA_BUS_FMT_RGB888_1X24, MIPI_CSI2_DT_RGB888, 24 }
> +
> +#define MAX_TPG_ENTRY_1920X1080P30_RGB888 \
> +	{ 1920, 1080, { 1, 30 }, MEDIA_BUS_FMT_RGB888_1X24, MIPI_CSI2_DT_RGB888, 24 }
> +
> +#define MAX_TPG_ENTRY_1920X1080P60_RGB888 \
> +	{ 1920, 1080, { 1, 60 }, MEDIA_BUS_FMT_RGB888_1X24, MIPI_CSI2_DT_RGB888, 24 }
> +
> +struct max_tpg_entries {
> +	const struct max_tpg_entry *entries;
> +	unsigned int num_entries;
> +};
> +
> +struct max_tpg_timings {
> +	bool gen_vs;
> +	bool gen_hs;
> +	bool gen_de;
> +	bool vs_inv;
> +	bool hs_inv;
> +	bool de_inv;
> +	u32 vs_dly;
> +	u32 vs_high;
> +	u32 vs_low;
> +	u32 v2h;
> +	u32 hs_high;
> +	u32 hs_low;
> +	u32 hs_cnt;
> +	u32 v2d;
> +	u32 de_high;
> +	u32 de_low;
> +	u32 de_cnt;
> +	u32 fps;
> +};
> +
> +static inline struct max_asc *asc_to_max(struct v4l2_async_connection *asc)
> +{
> +	return container_of(asc, struct max_asc, base);
> +}
> +
> +const char *max_gmsl_version_str(enum max_gmsl_version version);
> +const char *max_gmsl_mode_str(enum max_gmsl_mode mode);
> +
> +const struct max_mipi_format *max_mipi_format_by_dt(u8 dt);
> +
> +int max_get_fd_stream_entry(struct v4l2_subdev *sd, u32 pad, u32 stream,
> +			    struct v4l2_mbus_frame_desc_entry *entry);
> +
> +int max_get_fd_bpp(struct v4l2_mbus_frame_desc_entry *entry, unsigned int *bpp);
> +int max_process_bpps(struct device *dev, u32 bpps, u32 allowed_double_bpps,
> +		     unsigned int *doubled_bpp);
> +
> +int max_xlate_enable_disable_streams(struct max_source *sources,
> +				     u32 source_sink_pad_offset,
> +				     const struct v4l2_subdev_state *state,
> +				     u32 pad, u64 updated_streams_mask,
> +				     u32 sink_pad_start, u32 num_sink_pads,
> +				     bool enable);
> +
> +int max_get_streams_masks(struct device *dev,
> +			  const struct v4l2_subdev_state *state,
> +			  u32 pad, u64 updated_streams_mask,
> +			  u32 num_pads, u64 *old_streams_masks,
> +			  u64 **new_streams_masks, bool enable);
> +
> +void max_get_tpg_timings(const struct videomode *vm,
> +			 struct max_tpg_timings *timings);
> +const struct videomode *
> +max_find_tpg_videomode(const struct max_tpg_entry *entry);
> +
> +int max_validate_tpg_routing(struct v4l2_subdev_krouting *routing);
> +
> +#endif // MAX_SERDES_H

-- 
Kinds regards,

Sakari Ailus

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ