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: <20250702132104.1537926-21-demonsingur@gmail.com>
Date: Wed,  2 Jul 2025 16:20:46 +0300
From: Cosmin Tanislav <demonsingur@...il.com>
To: Cosmin Tanislav <cosmin.tanislav@...log.com>,
	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>,
	Sakari Ailus <sakari.ailus@...ux.intel.com>,
	Laurent Pinchart <laurent.pinchart@...asonboard.com>,
	Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
	Linus Walleij <linus.walleij@...aro.org>
Cc: linux-media@...r.kernel.org,
	devicetree@...r.kernel.org,
	linux-kernel@...r.kernel.org,
	linux-arm-kernel@...ts.infradead.org,
	linux-staging@...ts.linux.dev,
	linux-gpio@...r.kernel.org,
	Cosmin Tanislav <demonsingur@...il.com>
Subject: [PATCH v5 20/24] media: i2c: maxim-serdes: add MAX9296A driver

Add a new MAX9296A driver that also supports MAX96714, MAX96714F,
MAX96714R, MAX96716A and MAX96792A.

Integrate it with the common Deserializer framework, while keeping
compatibility with existing usecases, avoiding code duplication, and
also enabling more features across all chips.

Signed-off-by: Cosmin Tanislav <demonsingur@...il.com>
---
 drivers/media/i2c/maxim-serdes/Kconfig    |   12 +
 drivers/media/i2c/maxim-serdes/Makefile   |    1 +
 drivers/media/i2c/maxim-serdes/max9296a.c | 1354 +++++++++++++++++++++
 3 files changed, 1367 insertions(+)
 create mode 100644 drivers/media/i2c/maxim-serdes/max9296a.c

diff --git a/drivers/media/i2c/maxim-serdes/Kconfig b/drivers/media/i2c/maxim-serdes/Kconfig
index 2acd96cdbfa4..05868624f3b6 100644
--- a/drivers/media/i2c/maxim-serdes/Kconfig
+++ b/drivers/media/i2c/maxim-serdes/Kconfig
@@ -41,3 +41,15 @@ config VIDEO_MAX96724
 
 	  To compile this driver as a module, choose M here: the module
 	  will be called max96724.
+
+config VIDEO_MAX9296A
+	tristate "Maxim MAX9296A Dual Deserializer support"
+	select VIDEO_MAXIM_SERDES
+	help
+	  This driver supports the Maxim MAX9296A, MAX96716A, MAX96792A
+	  Dual Deserializers, and the MAX96714, MAX96714F, MAX96714R
+	  Single Deserializers, which  convert from up to two GMSL2/3
+	  links to up to two 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
index b6d5aebfaee1..ae306bc33bfb 100644
--- a/drivers/media/i2c/maxim-serdes/Makefile
+++ b/drivers/media/i2c/maxim-serdes/Makefile
@@ -3,3 +3,4 @@ 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..946fdc402829
--- /dev/null
+++ b/drivers/media/i2c/maxim-serdes/max9296a.c
@@ -0,0 +1,1354 @@
+// 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)
+
+#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_CHECKER	0b11
+#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_CHKR_COLOR_A_L			0x25e
+#define MAX9296A_CHKR_COLOR_B_L			0x261
+#define MAX9296A_CHKR_RPT_A			0x264
+#define MAX9296A_CHKR_RPT_B			0x265
+#define MAX9296A_CHKR_ALT			0x266
+
+#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)	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 {
+	const struct max_des_ops *ops;
+	unsigned int max_register;
+	unsigned int pipe_hw_ids[MAX9296A_PIPES_NUM];
+	unsigned int phy_hw_ids[MAX9296A_PHYS_NUM];
+	bool use_atr;
+	bool has_per_link_reset;
+	bool phy0_lanes_0_1_on_second_phy;
+	bool polarity_on_physical_lanes;
+	bool supports_cphy;
+	bool supports_phy_log;
+	bool adjust_rlms;
+};
+
+#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);
+
+	return max9296a_wait_for_device(priv);
+}
+
+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)
+{
+	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;
+
+	dev_info(priv->dev, "\tvideo_lock: %u\n",
+		 !!(val & MAX9296A_VPRBS_VIDEO_LOCK));
+
+	return 0;
+}
+
+static int max9296a_log_phy_status(struct max_des *des,
+				   struct max_des_phy *phy)
+{
+	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;
+
+	dev_info(priv->dev, "\tcsi2_pkt_cnt: %lu\n",
+		 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;
+
+	dev_info(priv->dev, "\tphy_pkt_cnt: %u\n", val);
+
+	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_tpg(struct max_des *des)
+{
+	const struct reg_sequence regs[] = {
+		{ MAX9296A_GRAD_INCR, MAX_SERDES_GRAD_INCR },
+		REG_SEQUENCE_3_LE(MAX9296A_CHKR_COLOR_A_L,
+				  MAX_SERDES_CHECKER_COLOR_A),
+		REG_SEQUENCE_3_LE(MAX9296A_CHKR_COLOR_B_L,
+				  MAX_SERDES_CHECKER_COLOR_B),
+		{ MAX9296A_CHKR_RPT_A, MAX_SERDES_CHECKER_SIZE },
+		{ MAX9296A_CHKR_RPT_B, MAX_SERDES_CHECKER_SIZE },
+		{ MAX9296A_CHKR_ALT, MAX_SERDES_CHECKER_SIZE },
+	};
+	struct max9296a_priv *priv = des_to_priv(des);
+
+	return regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs));
+}
+
+static int max9296a_init(struct max_des *des)
+{
+	return max9296a_init_tpg(des);
+}
+
+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;
+	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.
+	 */
+
+	for (i = 0, val = 0; i < num_data_lanes; i++) {
+		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;
+
+	return regmap_assign_bits(priv->regmap, MAX9296A_MIPI_TX51(phy_id),
+				  MAX9296A_MIPI_TX51_ALT2_MEM_MAP_8,
+				  mode->alt2_mem_map8);
+}
+
+static int max9296a_set_phy_enable(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;
+	}
+
+	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;
+	/*
+	 * These register writes are described as required in MAX96714 datasheet
+	 * Page 53, Section Register Map, to optimize link performance in 6Gbps
+	 * and 3Gbps links for all cable lengths.
+	 */
+	const struct reg_sequence regs[] = {
+		{ MAX9296A_RLMS3E(index), 0xfd },
+		{ MAX9296A_RLMS3F(index), 0x3d },
+		{ MAX9296A_RLMS49(index), 0xf5 },
+		{ MAX9296A_RLMS7E(index), 0xa8 },
+		{ MAX9296A_RLMS7F(index), 0x68 },
+		{ MAX9296A_RLMSA3(index), 0x30 },
+		{ MAX9296A_RLMSA5(index), 0x70 },
+		{ MAX9296A_RLMSD8(index), 0x07 },
+	};
+	int ret;
+
+	ret = regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs));
+	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;
+	}
+
+	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 (des->ops->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_serdes_gmsl_version version)
+{
+	struct max9296a_priv *priv = des_to_priv(des);
+	unsigned int index = link->index;
+	bool gmsl3_en = version == MAX_SERDES_GMSL_3;
+	unsigned int reg, mask, val;
+	int ret;
+
+	if (des->ops->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_SERDES_GMSL_3)
+		val = MAX9296A_REG1_RX_RATE_12GBPS;
+	else if (version == MAX_SERDES_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 (!(des->ops->versions & BIT(MAX_SERDES_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_serdes_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, u32 clock)
+{
+	bool patgen_clk_src = 0;
+	u8 pin_drv_en;
+	int ret;
+
+	switch (clock) {
+	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;
+	case 0:
+		return 0;
+	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_mode(struct max9296a_priv *priv, bool enable)
+{
+	unsigned int patgen_mode;
+
+	switch (priv->des.tpg_pattern) {
+	case MAX_SERDES_TPG_PATTERN_GRADIENT:
+		patgen_mode = MAX9296A_PATGEN_1_PATGEN_MODE_GRADIENT;
+		break;
+	case MAX_SERDES_TPG_PATTERN_CHECKERBOARD:
+		patgen_mode = MAX9296A_PATGEN_1_PATGEN_MODE_CHECKER;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return regmap_update_bits(priv->regmap, MAX9296A_PATGEN_1,
+				  MAX9296A_PATGEN_1_PATGEN_MODE,
+				  FIELD_PREP(MAX9296A_PATGEN_1_PATGEN_MODE,
+					     enable ? patgen_mode
+						    : MAX9296A_PATGEN_1_PATGEN_MODE_DISABLED));
+}
+
+static int max9296a_set_tpg(struct max_des *des,
+			    const struct max_serdes_tpg_entry *entry)
+{
+	struct max9296a_priv *priv = des_to_priv(des);
+	struct max_serdes_tpg_timings timings = { 0 };
+	int ret;
+
+	ret = max_serdes_get_tpg_timings(entry, &timings);
+	if (ret)
+		return ret;
+
+	ret = max9296a_set_tpg_timings(priv, &timings);
+	if (ret)
+		return ret;
+
+	ret = max9296a_set_tpg_clk(priv, timings.clock);
+	if (ret)
+		return ret;
+
+	ret = max9296a_set_tpg_mode(priv, entry);
+	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_serdes_tpg_entry max9296a_tpg_entries[] = {
+	MAX_TPG_ENTRY_640X480P60_RGB888,
+	MAX_TPG_ENTRY_1920X1080P30_RGB888,
+	MAX_TPG_ENTRY_1920X1080P60_RGB888,
+};
+
+static const struct max_des_ops max9296a_common_ops = {
+	.num_remaps_per_pipe = 16,
+	.tpg_entries = {
+		.num_entries = ARRAY_SIZE(max9296a_tpg_entries),
+		.entries = max9296a_tpg_entries,
+	},
+	.tpg_patterns = BIT(MAX_SERDES_TPG_PATTERN_CHECKERBOARD) |
+			BIT(MAX_SERDES_TPG_PATTERN_GRADIENT),
+	.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 = max9296a_init,
+	.init_phy = max9296a_init_phy,
+	.set_phy_mode = max9296a_set_phy_mode,
+	.set_phy_enable = max9296a_set_phy_enable,
+	.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_common_ops;
+
+	ops->versions = priv->info->ops->versions;
+	ops->modes = priv->info->ops->modes;
+	ops->needs_single_link_version = priv->info->ops->needs_single_link_version;
+	ops->needs_unique_stream_id = priv->info->ops->needs_unique_stream_id;
+	ops->fix_tx_ids = priv->info->ops->fix_tx_ids;
+	ops->num_phys = priv->info->ops->num_phys;
+	ops->num_pipes = priv->info->ops->num_pipes;
+	ops->num_links = priv->info->ops->num_links;
+	ops->phys_configs = priv->info->ops->phys_configs;
+	ops->set_pipe_enable = priv->info->ops->set_pipe_enable;
+	ops->set_pipe_stream_id = priv->info->ops->set_pipe_stream_id;
+	ops->set_pipe_tunnel_phy = priv->info->ops->set_pipe_tunnel_phy;
+	ops->set_pipe_tunnel_enable = priv->info->ops->set_pipe_tunnel_enable;
+	ops->use_atr = priv->info->ops->use_atr;
+	ops->tpg_mode = priv->info->ops->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_serdes_phys_config max9296a_phys_configs[] = {
+	{ { 4, 4 } },
+};
+
+static const struct max_serdes_phys_config max96714_phys_configs[] = {
+	{ { 4 } },
+};
+
+static const struct max_des_ops max9296a_ops = {
+	.versions = BIT(MAX_SERDES_GMSL_2_3GBPS) |
+		    BIT(MAX_SERDES_GMSL_2_6GBPS),
+	.modes = BIT(MAX_SERDES_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,
+	.phys_configs = {
+		.num_configs = ARRAY_SIZE(max9296a_phys_configs),
+		.configs = max9296a_phys_configs,
+	},
+	.fix_tx_ids = true,
+	.num_pipes = 4,
+	.num_phys = 2,
+	.num_links = 2,
+};
+
+static const struct max9296a_chip_info max9296a_info = {
+	.ops = &max9296a_ops,
+	.max_register = 0x1f00,
+	.use_atr = true,
+	.phy0_lanes_0_1_on_second_phy = true,
+	.pipe_hw_ids = { 0, 1, 2, 3 },
+	.phy_hw_ids = { 1, 2 },
+};
+
+static const struct max_des_ops max96714_ops = {
+	.versions = BIT(MAX_SERDES_GMSL_2_3GBPS) |
+		    BIT(MAX_SERDES_GMSL_2_6GBPS),
+	.modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) |
+		 BIT(MAX_SERDES_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_SERDES_GMSL_PIXEL_MODE,
+	.num_pipes = 1,
+	.num_phys = 1,
+	.num_links = 1,
+};
+
+static const struct max9296a_chip_info max96714_info = {
+	.ops = &max96714_ops,
+	.max_register = 0x5011,
+	.polarity_on_physical_lanes = true,
+	.supports_phy_log = true,
+	.adjust_rlms = true,
+	.pipe_hw_ids = { 1 },
+	.phy_hw_ids = { 1 },
+};
+
+static const struct max_des_ops max96714f_ops = {
+	.versions = BIT(MAX_SERDES_GMSL_2_3GBPS),
+	.modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) |
+		 BIT(MAX_SERDES_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_SERDES_GMSL_PIXEL_MODE,
+	.num_pipes = 1,
+	.num_phys = 1,
+	.num_links = 1,
+};
+
+static const struct max9296a_chip_info max96714f_info = {
+	.ops = &max96714f_ops,
+	.max_register = 0x5011,
+	.polarity_on_physical_lanes = true,
+	.supports_phy_log = true,
+	.adjust_rlms = true,
+	.pipe_hw_ids = { 1 },
+	.phy_hw_ids = { 1 },
+};
+
+static const struct max_des_ops max96716a_ops = {
+	.versions = BIT(MAX_SERDES_GMSL_2_3GBPS) |
+		    BIT(MAX_SERDES_GMSL_2_6GBPS),
+	.modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) |
+		 BIT(MAX_SERDES_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_SERDES_GMSL_PIXEL_MODE,
+	.num_pipes = 2,
+	.num_phys = 2,
+	.num_links = 2,
+};
+
+static const struct max9296a_chip_info max96716a_info = {
+	.ops = &max96716a_ops,
+	.max_register = 0x52d6,
+	.has_per_link_reset = true,
+	.phy0_lanes_0_1_on_second_phy = true,
+	.supports_cphy = true,
+	.supports_phy_log = true,
+	.pipe_hw_ids = { 1, 2 },
+	.phy_hw_ids = { 1, 2 },
+};
+
+static const struct max_des_ops max96792a_ops = {
+	.versions = BIT(MAX_SERDES_GMSL_2_3GBPS) |
+		    BIT(MAX_SERDES_GMSL_2_6GBPS) |
+		    BIT(MAX_SERDES_GMSL_3),
+	.modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) |
+		 BIT(MAX_SERDES_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_SERDES_GMSL_PIXEL_MODE,
+	.num_pipes = 2,
+	.num_phys = 2,
+	.num_links = 2,
+};
+
+static const struct max9296a_chip_info max96792a_info = {
+	.ops = &max96792a_ops,
+	.max_register = 0x52d6,
+	.has_per_link_reset = true,
+	.phy0_lanes_0_1_on_second_phy = true,
+	.supports_cphy = true,
+	.supports_phy_log = true,
+	.pipe_hw_ids = { 1, 2 },
+	.phy_hw_ids = { 1, 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	= 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");
-- 
2.50.0


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ