linux-media.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers
@ 2025-07-16 19:30 Cosmin Tanislav
  2025-07-16 19:30 ` [PATCH v6 01/24] media: mc: Add INTERNAL pad flag Cosmin Tanislav
                   ` (23 more replies)
  0 siblings, 24 replies; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:30 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

This series adds new drivers for multiple Maxim GMSL2 and GMSL3 devices,
replacing the few GMSL2 drivers already in upstream, and introducing a
common framework that can be used to implement such GMSL chips, which
avoids code duplication while also adding support for previously
unsupported features.

While the normally acceptable and polite way would be to extend the
current mainline drivers, the choice was made here to add a totally new
set of drivers. The current drivers support only a small subset of the
possible features, and only a few devices, so the end result after
extending them would in any case be essentially fully rewritten, new
drivers.

This series depends on support for internal pads, for which a patch has
been added.

The previous version is at:
https://lore.kernel.org/lkml/20250702132104.1537926-1-demonsingur@gmail.com

The following deserializers are supported:
 * MAX96712 (already exists in staging)
 * MAX96714 (already exists)
 * MAX96714F (already exists)
 * MAX96714R (GMSL2)
 * MAX96716 (GMSL2)
 * MAX96724 (already exists as part of existing MAX96712 driver)
 * MAX96724F (GMSL2)
 * MAX96724R (GMSL2)
 * MAX9296A (GMSL2)
 * MAX96792A (GMSL3)

The following serializers are supported:
 * MAX96717 (already exists)
 * MAX9295A (GMSL2)
 * MAX96793 (GMSL3)

The following list enumerates new features that are supported by the
common framework and their respective chip-specific drivers:
 * Full Streams API support. Most deserializers have support for more
   than one link, and more than one PHY. Streams support allows
   configuration of routing between these links and PHYs.

 * .get_frame_desc() support. Both the serializers and deserializers
   implement this to query and provide frame descriptor data. This is
   used in features explained in-depth below.

 * .get_mbus_config() support. The deserializers implement this to allow
   upstream devices to query the link frequency of its pads.

 * Address translation with I2C ATR for the serializers.

 * I2C ATR translation - some deserializers cannot do muxing since I2C
   communication channel masking is not available per-link, and the only
   other way to select links is to turn them off, causing link resets.
   For such cases, I2C ATR is used to change the address of the
   serializers at probe time.

 * Automatic GMSL link version negotiation between GMSL3, GMSL2 6Gbps, GMSL2
   3Gbps.

 * Automatic stream id selection for deserializers which need serializers to
   stream on unique stream ids.

 * Automatic VC remapping on the deserializers. VCs are picked so that
   if they were unique on the sink pad, they will end up as unique on
   the source pad they are routed to too, prioritizing using the same
   VC ID as the sink pad, to facilitate the possibility of using tunnel
   mode.

 * Automatic pixel mode / tunnel mode selection. Tunnel mode is used
   when VC IDs do not need to be changed and all hardware supports
   tunnel mode, otherwise, pixel mode is used. The serializers are
   automatically switched between the two by using a private API.

 * Automatic double mode selection. In pixel mode, double mode can be
   used to pack two pixels into a single data unit, optimizing bandwidth
   usage. The serializers are automatically set up to support the double
   modes determined by the deserializers using a private API.

 * Automatic data padding. In pixel mode, if the data being transferred
   uses two different BPPs, data needs to be padded. The serializers
   automatically set this up depending on the configured double mode
   settings and incoming data types.

 * Logging. Both the deserializers and serializers implement the V4L2
   .log_status() ops to allow debugging of the internal state and
   important chip status registers.

 * PHY modes. Deserializer chips commonly have more than a single PHY.
   The firmware ports are parsed to determine the modes in which to
   configure the PHYs (2x4, 4x2, 1x4+2x2, 2x2+1x4, and variations using
   fewer lanes).

 * Serializer pinctrl. Serializers implement pinctrl to allow setting
   configs which would otherwise be inaccessible through GPIO: TX/RX via
   GMSL link, pull-up & pull-down (with strength), open-drain &
   push-pull, slew rate, RCLK pin selection.

 * TPG with selectable formats, resolutions and framerates for both
   serializers and deserializers.

The drivers have been tested on the following hardware combinations, but
further testing is welcome to ensure no / minimal breakage:
 * Raspberry Pi 5 + MAX9296A + 2xMAX96717 + 2xIMX219
 * Raspberry Pi 5 + MAX96714 + 1xMAX96717 + 1xIMX219
 * Raspberry Pi 5 + MAX96716A + 2xMAX96717 + 2xIMX219
 * Raspberry Pi 5 + MAX96712 + 4xMAX96717 + 4xIMX219
 * Raspberry Pi 5 + MAX96724 + 4xMAX96717 + 4xIMX219
 * Raspberry Pi 5 + MAX96792A + 1xMAX96793 + 1xMAX96717 + 2xIMX219
 * Raspberry Pi 5 + MAX96792A + 2xMAX96717 + 2xIMX219
 * Renesas V4H + MAX96712 + 2xMAX96717 + 2xIMX219

Analog Devices is taking responsibility for the maintenance of these
drivers and common framework, and plans to add support for new
broad-market chips on top of them.

Special thanks go to Tomi Valkeinen <tomi.valkeinen+renesas@ideasonboard.com>
for testing the drivers, helping debug and coming up with ideas /
implementations for various features.

V6:
 * max9296a: put rlms sequence in max9296a_chip_info
 * max_des: reflow stream id a comment
 * max_ser: remove exported symbols not used in other modules
 * max_ser: init mode to a supported value
 * add default routing
 * MAX_SERDES_GMSL_3 -> MAX_SERDES_GMSL_3_12GBPS
 * guard reg_read/write with CONFIG_VIDEO_ADV_DEBUG
 * put exported symbols in MAXIM_SERDES namespace

V5:
 * dt-bindings: max96717: restrict RCLKOUT to pins 2 & 4
 * dt-bindings: max96717: remove confusing rclksel pinconf property
 * dt-bindings: max96717: remove maxim,gmsl-tx/rx pinconf property
 * dt-bindings: max96717: remove gmsl prefix from maxim,gmsl-tx-id/rx-id
 * dt-bindings: max96717: remove minimum: 0
 * dt-bindings: max96717: better document slew-rate
 * dt-bindings: max96717: better document maxim,jitter-compensation
 * dt-bindings: max96717: better document maxim,tx-id/rx-id

 * max_serdes: add default TPG values
 * max_serdes: remove MAX_MIPI_FMT macro
 * max_serdes: EXPORT_SYMBOL -> EXPORT_SYMBOL_GPL
 * max_serdes: remove EXPORT_SYMBOL_GPL from symbols not used in other
   modules
 * max_serdes: rename symbols/macros/types to have max_serdes prefix
 * max_serdes: slim down TPG functions

 * max_des: fix may be used uninitialized errors
 * max_des: fix misplaced TPG validation
 * max_des: fix setting pipe PHY in tunnel mode for chips that support
   both set_pipe_phy() and set_pipe_tunnel_phy()
 * max_des: move doubled_bpp/sink_bpps variables to usage place
 * max_des: do not dynamically control PHY enable, letting lanes be in
   LP-11 when not streaming
 * max_des: refactor get/set_pipe_stream_id() logic
 * max_des: remove explicit ret = 0

 * max_ser: make VC remaps not pipe-specific, allocate dynamically

 * max9296a: add missing 1080p30 TPG entry
 * max9296a: move BIT() left shift into macro
 * max9296a: move BIT() ternary into macro
 * max9296a: reuse max_des_ops for chip-specific ops\
 * max9296a: document and compress RLMS register writes

 * max96717: restrict RCLKOUT to pins 2 & 4 because of hardware
   capabilities
 * max96717: add support for XTAL/1, XTAL/2, XTAL/4 clocks
 * max96717: set RX_EN/TX_EN automatically
 * max96717: reorder custom pinconf flags
 * max96717: drop OF dependency

 * drop of_match_ptr
 * re-do some indentation
 * implement TPG pattern control
 * remove pr_info() usage
 * inline lane polarity val = 0
 * inline returns
 * rewrite some Kconfig docs
 * split up patches for easier review

V4:
 * max_des: fix infinite version loop
 * max_des: fix pipe link id when there are more pipes than links
 * max_des: implement setting pipe link
 * max_des: do not pass routing to phy update
 * max_des: move GMSL version strings to max_serdes
 * max_des: split finding existing VC remap from adding a new one
 * max_des: add tracking for in-use pipes
 * max_des: skip unused pipes when finding / setting pixel/tunnel mode
 * max_des: simplify remap code
 * max_des: split set_pipe_phy() into set_pipe_tunnel_phy()

 * max_ser: clean up i2c_xlates printing
 * max_ser: fix changing serializer address
 * max_ser: move non-continuous mode check into max96717 driver

 * max96724: use regmap_set_bits for STREAM_SEL_ALL
 * max96724: match surrounding indent for MAX96724_PHY1_ALT_CLOCK
 * max96724: fix setting invalid PHY to 1 when PHY 0 is in 4-lane mode
 * max96724: remove support for setting pipe phy from max96712
 * max96724: fix setting double mode on pipes 4-7
 * max96724: drop powerdown gpios

 * max96717: use gpio_chip's set_rv

 * max9296a: switch versions to unsigned int
 * max9296a: remove parantheses from MAX9296A_MIPI_PHY18/20
 * max9296a: fix printing of PHY packet counts
 * max9296a: fix phy_hw_ids size

 * remove usage of cammel case in defines
 * move field_get/prep to max_serdes.h
 * rework stream id setup
 * rework tunnel/pixel mode finding
 * rework bpps retrieval
 * pass whole subdev state around
 * add helper for retrieving a route's hw components / frame desc
 * update pipe enable based on active routes
 * add support for tunnel-only chips and VC remaps in tunnel mode
 * simplify max_get_streams_masks()
 * add support for TPG

V3:
 * dt-bindings: drop reflow text patches

 * dt-bindings: max96717: move pinctrl configuration into main file
 * dt-bindings: max96717: allow a single level of pins configuration
 * dt-bindings: max96717: use regex for matching pins nodes
 * dt-bindings: max96717: drop extra allOf in pinctrl configuration
 * dt-bindings: max96717: fix i2c-atr channel name regex
 * dt-bindings: max96717: limit pinctrl functions to gpio / rclkout
 * dt-bindings: max96717: limit pins for gpio / rclkout
 * dt-bindings: max96717: add description for bias-pull-up/down
 * dt-bindings: max96717: require pins and function properties
 * dt-bindings: max96717: turn single compatible strings into an enum

 * dt-bindings: max9296a: include indices in port descriptions
 * dt-bindings: max9296a: remove property-less schema from input ports
 * dt-bindings: max9296a: use ATR for MAX96716A too, removing MUX entirely

 * dt-bindings: max96712: include indices in port descriptions
 * dt-bindings: max96712: deprecate enable-gpios in favor of powerdown-gpios
 * dt-bindings: max96712: switch from MUX to ATR

 * dt-bindings: max96714: add support for MAX96714R

 * max_des: fix POC NULL check
 * max_des: remove index var in POC enable
 * max_des: fix writing empty remaps
 * max_des: skip mode setting in tunnel mode
 * max_des: remove a duplicate source->sd NULL check
 * max_des: set pipe tunnel mode even for disabled links

 * max_ser: apply TX ID changes irrespective of serializer ID

 * max9296a: fix typo in BACKTOP22
 * max9296a: make register macros more consistent
 * max9296a: switch MAX96716 from MUX to ATR
 * max9296a: deduplicate max9296a_phy_id() logic
 * max9296a: use proper PHY id in remaps
 * max9296a: fix DPLL reset clear
 * max9296a: limit MAX96714F to GMSL2 3Gbps
 * max9296a: add support for MAX96714R
 * max9296a: do not write GMSL3 link select registers in GMSL2 devices
 * max9296a: use field_prep when setting RX_RATE
 * max9296a: simplify setting SEL_STREAM for MAX96714
 * max9296a: max96716_set_pipe_phy -> max96716a_set_pipe_phy
 * max9296a: fix off-by-one in lane polarity when using
   polarity_on_physical_lanes

 * max96724: fix typo in BACKTOP22
 * max96724: switch from MUX to ATR
 * max96724: add support for powerdown GPIO
 * max96724: remove support for tunneling from MAX96712
 * max96724: only set tunnel-related bits when in tunnel mode
 * max96724: add support for MAX96724F/R
 * max96724: oneshot reset links after link selection

 * remove GMSL2 version defaults, set all supported versions explicitly
 * reorder GMSL versions to start from 0
 * add support for GMSL2 3Gbps
 * support GMSL version finding for devices using MUX / GATE
 * add support for deserializers which don't have individual control
   of each link's GMSL version
 * add support for deserializers that need unique stream ids across all
   serializers
 * select_link_version -> set_link_version
 * select_resets_link -> use_atr

V2:
 * add missing compatible for MAX96717F
 * fix embarrassing dt-bindings mistakes
 * move MAX9296A/MAX96716/MAX96792A to a separate file as they have two
   links / PHYs, and adding those conditionally seems impossible

Cosmin Tanislav (23):
  dt-bindings: media: i2c: max96717: add myself as maintainer
  dt-bindings: media: i2c: max96717: add support for I2C ATR
  dt-bindings: media: i2c: max96717: add support for pinctrl/pinconf
  dt-bindings: media: i2c: max96717: add support for MAX9295A
  dt-bindings: media: i2c: max96717: add support for MAX96793
  dt-bindings: media: i2c: max96712: add myself as maintainer
  dt-bindings: media: i2c: max96712: use pattern properties for ports
  dt-bindings: media: i2c: max96712: add support for I2C ATR
  dt-bindings: media: i2c: max96712: add support for POC supplies
  dt-bindings: media: i2c: max96712: add support for MAX96724F/R
  dt-bindings: media: i2c: max96714: add myself as maintainer
  dt-bindings: media: i2c: max96714: add support for MAX96714R
  dt-bindings: media: i2c: add MAX9296A, MAX96716A, MAX96792A
  media: i2c: add Maxim GMSL2/3 serializer and deserializer framework
  media: i2c: add Maxim GMSL2/3 serializer framework
  media: i2c: add Maxim GMSL2/3 deserializer framework
  media: i2c: maxim-serdes: add MAX96717 driver
  media: i2c: maxim-serdes: add MAX96724 driver
  media: i2c: maxim-serdes: add MAX9296A driver
  arm64: defconfig: disable deprecated MAX96712 driver
  staging: media: remove MAX96712 driver
  media: i2c: remove MAX96717 driver
  media: i2c: remove MAX96714 driver

Sakari Ailus (1):
  media: mc: Add INTERNAL pad flag

 .../bindings/media/i2c/maxim,max9296a.yaml    |  242 ++
 .../bindings/media/i2c/maxim,max96712.yaml    |   70 +-
 .../bindings/media/i2c/maxim,max96714.yaml    |    6 +-
 .../bindings/media/i2c/maxim,max96717.yaml    |  155 +-
 .../media/mediactl/media-types.rst            |    8 +
 MAINTAINERS                                   |   13 +-
 arch/arm64/configs/defconfig                  |    1 -
 drivers/media/i2c/Kconfig                     |   34 +-
 drivers/media/i2c/Makefile                    |    3 +-
 drivers/media/i2c/max96714.c                  | 1017 ------
 drivers/media/i2c/max96717.c                  | 1102 ------
 drivers/media/i2c/maxim-serdes/Kconfig        |   55 +
 drivers/media/i2c/maxim-serdes/Makefile       |    6 +
 drivers/media/i2c/maxim-serdes/max9296a.c     | 1344 +++++++
 drivers/media/i2c/maxim-serdes/max96717.c     | 1688 +++++++++
 drivers/media/i2c/maxim-serdes/max96724.c     | 1183 ++++++
 drivers/media/i2c/maxim-serdes/max_des.c      | 3180 +++++++++++++++++
 drivers/media/i2c/maxim-serdes/max_des.h      |  153 +
 drivers/media/i2c/maxim-serdes/max_ser.c      | 2130 +++++++++++
 drivers/media/i2c/maxim-serdes/max_ser.h      |  147 +
 drivers/media/i2c/maxim-serdes/max_serdes.c   |  413 +++
 drivers/media/i2c/maxim-serdes/max_serdes.h   |  183 +
 drivers/media/mc/mc-entity.c                  |   10 +-
 drivers/staging/media/Kconfig                 |    2 -
 drivers/staging/media/Makefile                |    1 -
 drivers/staging/media/max96712/Kconfig        |   14 -
 drivers/staging/media/max96712/Makefile       |    2 -
 drivers/staging/media/max96712/max96712.c     |  487 ---
 include/uapi/linux/media.h                    |    1 +
 29 files changed, 10963 insertions(+), 2687 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/media/i2c/maxim,max9296a.yaml
 delete mode 100644 drivers/media/i2c/max96714.c
 delete mode 100644 drivers/media/i2c/max96717.c
 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
 delete mode 100644 drivers/staging/media/max96712/Kconfig
 delete mode 100644 drivers/staging/media/max96712/Makefile
 delete mode 100644 drivers/staging/media/max96712/max96712.c

-- 
2.50.1


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

* [PATCH v6 01/24] media: mc: Add INTERNAL pad flag
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
@ 2025-07-16 19:30 ` Cosmin Tanislav
  2025-07-16 19:30 ` [PATCH v6 02/24] dt-bindings: media: i2c: max96717: add myself as maintainer Cosmin Tanislav
                   ` (22 subsequent siblings)
  23 siblings, 0 replies; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:30 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio

From: Sakari Ailus <sakari.ailus@linux.intel.com>

Internal source pads will be used as routing endpoints in V4L2
[GS]_ROUTING IOCTLs, to indicate that the stream begins in the entity.
Internal source pads are pads that have both SINK and INTERNAL flags set.

Also prevent creating links to pads that have been flagged as internal and
initialising SOURCE pads with INTERNAL flag set.

Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
---
 .../userspace-api/media/mediactl/media-types.rst       |  8 ++++++++
 drivers/media/mc/mc-entity.c                           | 10 ++++++++--
 include/uapi/linux/media.h                             |  1 +
 3 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/Documentation/userspace-api/media/mediactl/media-types.rst b/Documentation/userspace-api/media/mediactl/media-types.rst
index 6332e8395263b..f55ef055bcf85 100644
--- a/Documentation/userspace-api/media/mediactl/media-types.rst
+++ b/Documentation/userspace-api/media/mediactl/media-types.rst
@@ -361,6 +361,7 @@ Types and flags used to represent the media graph elements
 .. _MEDIA-PAD-FL-SINK:
 .. _MEDIA-PAD-FL-SOURCE:
 .. _MEDIA-PAD-FL-MUST-CONNECT:
+.. _MEDIA-PAD-FL-INTERNAL:
 
 .. flat-table:: Media pad flags
     :header-rows:  0
@@ -381,6 +382,13 @@ Types and flags used to represent the media graph elements
 	  enabled links even when this flag isn't set; the absence of the flag
 	  doesn't imply there is none.
 
+    *  -  ``MEDIA_PAD_FL_INTERNAL``
+       -  The internal flag indicates an internal pad that has no external
+	  connections. Such a pad shall not be connected with a link.
+
+	  The internal flag may currently be present only in a source pad where
+	  it indicates that the :ref:``stream <media-glossary-stream>``
+	  originates from within the entity.
 
 One and only one of ``MEDIA_PAD_FL_SINK`` and ``MEDIA_PAD_FL_SOURCE``
 must be set for every pad.
diff --git a/drivers/media/mc/mc-entity.c b/drivers/media/mc/mc-entity.c
index 0455909055820..d1feacc608072 100644
--- a/drivers/media/mc/mc-entity.c
+++ b/drivers/media/mc/mc-entity.c
@@ -213,7 +213,9 @@ int media_entity_pads_init(struct media_entity *entity, u16 num_pads,
 		iter->index = i++;
 
 		if (hweight32(iter->flags & (MEDIA_PAD_FL_SINK |
-					     MEDIA_PAD_FL_SOURCE)) != 1) {
+					     MEDIA_PAD_FL_SOURCE)) != 1 ||
+		    (iter->flags & MEDIA_PAD_FL_INTERNAL &&
+		     !(iter->flags & MEDIA_PAD_FL_SINK))) {
 			ret = -EINVAL;
 			break;
 		}
@@ -1118,7 +1120,8 @@ int media_get_pad_index(struct media_entity *entity, u32 pad_type,
 
 	for (i = 0; i < entity->num_pads; i++) {
 		if ((entity->pads[i].flags &
-		     (MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_SOURCE)) != pad_type)
+		     (MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_SOURCE |
+		      MEDIA_PAD_FL_INTERNAL)) != pad_type)
 			continue;
 
 		if (entity->pads[i].sig_type == sig_type)
@@ -1148,6 +1151,9 @@ media_create_pad_link(struct media_entity *source, u16 source_pad,
 		return -EINVAL;
 	if (WARN_ON(!(sink->pads[sink_pad].flags & MEDIA_PAD_FL_SINK)))
 		return -EINVAL;
+	if (WARN_ON(source->pads[source_pad].flags & MEDIA_PAD_FL_INTERNAL) ||
+	    WARN_ON(sink->pads[sink_pad].flags & MEDIA_PAD_FL_INTERNAL))
+		return -EINVAL;
 
 	link = media_add_link(&source->links);
 	if (link == NULL)
diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h
index 1c80b1d6bbaf3..80cfd12a43fc1 100644
--- a/include/uapi/linux/media.h
+++ b/include/uapi/linux/media.h
@@ -208,6 +208,7 @@ struct media_entity_desc {
 #define MEDIA_PAD_FL_SINK			(1U << 0)
 #define MEDIA_PAD_FL_SOURCE			(1U << 1)
 #define MEDIA_PAD_FL_MUST_CONNECT		(1U << 2)
+#define MEDIA_PAD_FL_INTERNAL			(1U << 3)
 
 struct media_pad_desc {
 	__u32 entity;		/* entity ID */
-- 
2.50.1


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

* [PATCH v6 02/24] dt-bindings: media: i2c: max96717: add myself as maintainer
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
  2025-07-16 19:30 ` [PATCH v6 01/24] media: mc: Add INTERNAL pad flag Cosmin Tanislav
@ 2025-07-16 19:30 ` Cosmin Tanislav
  2025-08-01 12:17   ` Julien Massot
  2025-07-16 19:30 ` [PATCH v6 03/24] dt-bindings: media: i2c: max96717: add support for I2C ATR Cosmin Tanislav
                   ` (21 subsequent siblings)
  23 siblings, 1 reply; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:30 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

Analog Devices is taking responsability for the maintenance of the Maxim
GMSL2/3 devices.
Add myself to the maintainers list and to the device tree bindings.

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
---
 Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml | 1 +
 MAINTAINERS                                                     | 1 +
 2 files changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
index d1e8ba6e368ec..15ab37702a927 100644
--- a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
@@ -9,6 +9,7 @@ title: MAX96717 CSI-2 to GMSL2 Serializer
 
 maintainers:
   - Julien Massot <julien.massot@collabora.com>
+  - Cosmin Tanislav <cosmin.tanislav@analog.com>
 
 description:
   The MAX96717 serializer converts MIPI CSI-2 D-PHY formatted input
diff --git a/MAINTAINERS b/MAINTAINERS
index 24c557ee091d7..e973b0a985815 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14761,6 +14761,7 @@ F:	drivers/media/i2c/max96714.c
 
 MAX96717 GMSL2 SERIALIZER DRIVER
 M:	Julien Massot <julien.massot@collabora.com>
+M:	Cosmin Tanislav <cosmin.tanislav@analog.com>
 L:	linux-media@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
-- 
2.50.1


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

* [PATCH v6 03/24] dt-bindings: media: i2c: max96717: add support for I2C ATR
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
  2025-07-16 19:30 ` [PATCH v6 01/24] media: mc: Add INTERNAL pad flag Cosmin Tanislav
  2025-07-16 19:30 ` [PATCH v6 02/24] dt-bindings: media: i2c: max96717: add myself as maintainer Cosmin Tanislav
@ 2025-07-16 19:30 ` Cosmin Tanislav
  2025-08-01 12:19   ` Julien Massot
  2025-07-16 19:30 ` [PATCH v6 04/24] dt-bindings: media: i2c: max96717: add support for pinctrl/pinconf Cosmin Tanislav
                   ` (20 subsequent siblings)
  23 siblings, 1 reply; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:30 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

MAX96717 is capable of address translation for the connected I2C slaves.

Add support for I2C ATR while keeping I2C gate for compatibility to
support this usecase.

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
---
 .../bindings/media/i2c/maxim,max96717.yaml    | 39 +++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
index 15ab37702a927..167c3dd50683c 100644
--- a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
@@ -92,6 +92,30 @@ properties:
       incoming GMSL2 link. Therefore, it supports an i2c-gate
       subnode to configure a sensor.
 
+  i2c-alias-pool:
+    maxItems: 2
+
+  i2c-atr:
+    type: object
+    additionalProperties: false
+
+    properties:
+      '#address-cells':
+        const: 1
+
+      '#size-cells':
+        const: 0
+
+    patternProperties:
+      '^i2c@[01]$':
+        $ref: /schemas/i2c/i2c-controller.yaml#
+        unevaluatedProperties: false
+        properties:
+          reg:
+            items:
+              minimum: 0
+              maximum: 1
+
 required:
   - compatible
   - reg
@@ -99,6 +123,21 @@ required:
 
 additionalProperties: false
 
+allOf:
+  - $ref: /schemas/i2c/i2c-atr.yaml#
+
+  - anyOf:
+      - oneOf:
+          - required: [i2c-atr]
+          - required: [i2c-gate]
+
+      - not:
+          required: [i2c-atr, i2c-gate]
+
+dependentRequired:
+  i2c-atr: [i2c-alias-pool]
+  i2c-alias-pool: [i2c-atr]
+
 examples:
   - |
     #include <dt-bindings/gpio/gpio.h>
-- 
2.50.1


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

* [PATCH v6 04/24] dt-bindings: media: i2c: max96717: add support for pinctrl/pinconf
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (2 preceding siblings ...)
  2025-07-16 19:30 ` [PATCH v6 03/24] dt-bindings: media: i2c: max96717: add support for I2C ATR Cosmin Tanislav
@ 2025-07-16 19:30 ` Cosmin Tanislav
  2025-08-01 12:20   ` Julien Massot
  2025-07-16 19:30 ` [PATCH v6 05/24] dt-bindings: media: i2c: max96717: add support for MAX9295A Cosmin Tanislav
                   ` (19 subsequent siblings)
  23 siblings, 1 reply; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:30 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

MAX96717 is capable of configuring various pin properties.

Add pinctrl/pinconf properties to support this usecase.

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---
 .../bindings/media/i2c/maxim,max96717.yaml    | 105 ++++++++++++++++++
 1 file changed, 105 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
index 167c3dd50683c..9afaa8a7a3f52 100644
--- a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
@@ -121,6 +121,111 @@ required:
   - reg
   - ports
 
+patternProperties:
+  '-pins$':
+    type: object
+    additionalProperties: false
+
+    properties:
+      function:
+        enum: [gpio, rclkout]
+
+      pins: true
+      drive-open-drain: true
+      drive-push-pull: true
+      bias-disable: true
+      output-disable: true
+      output-enable: true
+      output-low: true
+      output-high: true
+      input-enable: true
+
+      slew-rate:
+        description: |
+          Slew rate.
+          Rise and fall times represent the time needed for a GPIO to go
+          from 20% to 80% of VDDIO.
+          0 - Fastest
+              rise:  1.0ns @ 1.8V,  0.6ns @ 3.3V,
+              fall:  0.8ns @ 1.8V,  0.5ns @ 3.3V
+          1 - Fast
+              rise:  2.1ns @ 1.8V,  1.1ns @ 3.3V,
+              fall:  2.0ns @ 1.8V,  1.1ns @ 3.3V
+          2 - Slow
+              rise:  4.0ns @ 1.8V, 2.3ns @3.3V,
+              fall: 10.0ns @ 1.8V, 5.0ns @3.3V
+          3 - Slowest
+              rise:  9.0ns @ 1.8V, 5.0ns @3.3V,
+              fall: 10.0ns @ 1.8V, 5.0ns @3.3V
+        maximum: 3
+
+      bias-pull-up:
+        oneOf:
+          - type: boolean
+            description: Enable regular 40kOhm pull-up
+          - enum: [ 40000, 1000000 ]
+            description: Enable either the 40kOhm or the 1MOhm pull-up
+
+      bias-pull-down:
+        oneOf:
+          - type: boolean
+            description: Enable regular 40kOhm pull-down
+          - enum: [ 40000, 1000000 ]
+            description: Enable either the 40kOhm or the 1MOhm pull-down
+
+      maxim,jitter-compensation:
+        type: boolean
+        description: |
+          Enables jitter compensation.
+          Jitter compensation is used to minimize the jitter of the
+          signals transmitted from the deserializer to the serializer
+          by adding a fixed delay to every transition on the serializer
+          side. This can be used for pulse generation where timing is
+          critical.
+
+      maxim,tx-id:
+        $ref: /schemas/types.yaml#/definitions/uint32
+        description:
+          Enable transmission of the pin state from the serializer to
+          the deserializer using the specified identifier.
+        maximum: 31
+
+      maxim,rx-id:
+        $ref: /schemas/types.yaml#/definitions/uint32
+        description:
+          Enable transmission of the pin state from the deserializer to
+          the serializer using the specified identifier.
+        maximum: 31
+
+    required:
+      - pins
+      - function
+
+    allOf:
+      - $ref: /schemas/pinctrl/pincfg-node.yaml#
+      - $ref: /schemas/pinctrl/pinmux-node.yaml#
+
+      - if:
+          properties:
+            function:
+              const: gpio
+        then:
+          properties:
+            pins:
+              items:
+                enum: [mfp0, mfp1, mfp2, mfp3, mfp4, mfp5, mfp6, mfp7,
+                       mfp8, mfp9, mfp10]
+
+      - if:
+          properties:
+            function:
+              const: rclkout
+        then:
+          properties:
+            pins:
+              items:
+                enum: [mfp2, mfp4]
+
 additionalProperties: false
 
 allOf:
-- 
2.50.1


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

* [PATCH v6 05/24] dt-bindings: media: i2c: max96717: add support for MAX9295A
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (3 preceding siblings ...)
  2025-07-16 19:30 ` [PATCH v6 04/24] dt-bindings: media: i2c: max96717: add support for pinctrl/pinconf Cosmin Tanislav
@ 2025-07-16 19:30 ` Cosmin Tanislav
  2025-08-01 12:21   ` Julien Massot
  2025-08-01 12:34   ` Julien Massot
  2025-07-16 19:30 ` [PATCH v6 06/24] dt-bindings: media: i2c: max96717: add support for MAX96793 Cosmin Tanislav
                   ` (18 subsequent siblings)
  23 siblings, 2 replies; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:30 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

MAX9295A is an older variant of the MAX96717 which does not support
tunnel mode.

Document the compatibility.

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
---
 .../devicetree/bindings/media/i2c/maxim,max96717.yaml      | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
index 9afaa8a7a3f52..78ecbab8205a5 100644
--- a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
@@ -25,12 +25,17 @@ description:
 
   The GMSL2 serial link operates at a fixed rate of 3Gbps or 6Gbps in the
   forward direction and 187.5Mbps in the reverse direction.
+
   MAX96717F only supports a fixed rate of 3Gbps in the forward direction.
 
+  MAX9295A only supports pixel mode.
+
 properties:
   compatible:
     oneOf:
-      - const: maxim,max96717f
+      - enum:
+          - maxim,max9295a
+          - maxim,max96717f
       - items:
           - enum:
               - maxim,max96717
-- 
2.50.1


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

* [PATCH v6 06/24] dt-bindings: media: i2c: max96717: add support for MAX96793
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (4 preceding siblings ...)
  2025-07-16 19:30 ` [PATCH v6 05/24] dt-bindings: media: i2c: max96717: add support for MAX9295A Cosmin Tanislav
@ 2025-07-16 19:30 ` Cosmin Tanislav
  2025-08-01 12:35   ` Julien Massot
  2025-07-16 19:30 ` [PATCH v6 07/24] dt-bindings: media: i2c: max96712: add myself as maintainer Cosmin Tanislav
                   ` (17 subsequent siblings)
  23 siblings, 1 reply; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:30 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

MAX96793 is a newer variant of the MAX96717 which also supports GMSL3
links.

Document this compatibility.

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
---
 .../devicetree/bindings/media/i2c/maxim,max96717.yaml          | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
index 78ecbab8205a5..02a44db982852 100644
--- a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
@@ -30,6 +30,8 @@ description:
 
   MAX9295A only supports pixel mode.
 
+  MAX96793 also supports GMSL3 mode.
+
 properties:
   compatible:
     oneOf:
@@ -39,6 +41,7 @@ properties:
       - items:
           - enum:
               - maxim,max96717
+              - maxim,max96793
           - const: maxim,max96717f
 
   '#gpio-cells':
-- 
2.50.1


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

* [PATCH v6 07/24] dt-bindings: media: i2c: max96712: add myself as maintainer
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (5 preceding siblings ...)
  2025-07-16 19:30 ` [PATCH v6 06/24] dt-bindings: media: i2c: max96717: add support for MAX96793 Cosmin Tanislav
@ 2025-07-16 19:30 ` Cosmin Tanislav
  2025-07-16 19:30 ` [PATCH v6 08/24] dt-bindings: media: i2c: max96712: use pattern properties for ports Cosmin Tanislav
                   ` (16 subsequent siblings)
  23 siblings, 0 replies; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:30 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav, Niklas Söderlund

Analog Devices is taking responsability for the maintenance of the Maxim
GMSL2/3 devices.
Add myself to the maintainers list and to the device tree bindings.

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
Acked-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
---
 Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml | 1 +
 MAINTAINERS                                                     | 1 +
 2 files changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
index 26f85151afbd3..efdece2b33b96 100644
--- a/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
@@ -9,6 +9,7 @@ title: Quad GMSL2 to CSI-2 Deserializer with GMSL1 Compatibility
 
 maintainers:
   - Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
+  - Cosmin Tanislav <cosmin.tanislav@analog.com>
 
 description: |
   The MAX96712 deserializer converts GMSL2 or GMSL1 serial inputs into MIPI
diff --git a/MAINTAINERS b/MAINTAINERS
index e973b0a985815..3117345d0619c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14747,6 +14747,7 @@ F:	drivers/media/i2c/max9286.c
 
 MAX96712 QUAD GMSL2 DESERIALIZER DRIVER
 M:	Niklas Söderlund <niklas.soderlund@ragnatech.se>
+M:	Cosmin Tanislav <cosmin.tanislav@analog.com>
 L:	linux-media@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
-- 
2.50.1


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

* [PATCH v6 08/24] dt-bindings: media: i2c: max96712: use pattern properties for ports
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (6 preceding siblings ...)
  2025-07-16 19:30 ` [PATCH v6 07/24] dt-bindings: media: i2c: max96712: add myself as maintainer Cosmin Tanislav
@ 2025-07-16 19:30 ` Cosmin Tanislav
  2025-07-16 19:30 ` [PATCH v6 09/24] dt-bindings: media: i2c: max96712: add support for I2C ATR Cosmin Tanislav
                   ` (15 subsequent siblings)
  23 siblings, 0 replies; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:30 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav, Niklas Söderlund

The MAX96712 and MAX96724 support up to 4 separate PHYs, depending on
the selected PHY configuration. Use patternProperties to document this.

The input ports are all the same, use patternProperties for them.

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
Reviewed-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
---
 .../bindings/media/i2c/maxim,max96712.yaml    | 29 +++++++------------
 1 file changed, 10 insertions(+), 19 deletions(-)

diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
index efdece2b33b96..f712d7cfc35f5 100644
--- a/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
@@ -40,27 +40,15 @@ properties:
   ports:
     $ref: /schemas/graph.yaml#/properties/ports
 
-    properties:
-      port@0:
+    patternProperties:
+      '^port@[0-3]$':
         $ref: /schemas/graph.yaml#/properties/port
-        description: GMSL Input 0
+        description: GMSL Input ports 0-3
 
-      port@1:
-        $ref: /schemas/graph.yaml#/properties/port
-        description: GMSL Input 1
-
-      port@2:
-        $ref: /schemas/graph.yaml#/properties/port
-        description: GMSL Input 2
-
-      port@3:
-        $ref: /schemas/graph.yaml#/properties/port
-        description: GMSL Input 3
-
-      port@4:
+      '^port@[4-7]$':
         $ref: /schemas/graph.yaml#/$defs/port-base
         unevaluatedProperties: false
-        description: CSI-2 Output
+        description: CSI-2 Output port 0-3
 
         properties:
           endpoint:
@@ -78,8 +66,11 @@ properties:
               - data-lanes
               - bus-type
 
-    required:
-      - port@4
+    anyOf:
+      - required: [port@4]
+      - required: [port@5]
+      - required: [port@6]
+      - required: [port@7]
 
 required:
   - compatible
-- 
2.50.1


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

* [PATCH v6 09/24] dt-bindings: media: i2c: max96712: add support for I2C ATR
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (7 preceding siblings ...)
  2025-07-16 19:30 ` [PATCH v6 08/24] dt-bindings: media: i2c: max96712: use pattern properties for ports Cosmin Tanislav
@ 2025-07-16 19:30 ` Cosmin Tanislav
  2025-07-16 19:30 ` [PATCH v6 10/24] dt-bindings: media: i2c: max96712: add support for POC supplies Cosmin Tanislav
                   ` (14 subsequent siblings)
  23 siblings, 0 replies; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:30 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

MAX96712 and MAX96724 have more than one GMSL2 link, and each link is
capable of connecting to a separate serializer. If these serializers
have the same CFG pins configuration, they will also have the same I2C
address, causing conflicts unless the deserializer changes the address
of the connected serializers.

The MAX96712 and MAX96724 support changing the I2C address of the
connected serializers.

Document this capability.

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
---
 .../bindings/media/i2c/maxim,max96712.yaml    | 31 +++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
index f712d7cfc35f5..758c0223977d4 100644
--- a/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
@@ -37,6 +37,30 @@ properties:
 
   enable-gpios: true
 
+  i2c-alias-pool:
+    maxItems: 4
+
+  i2c-atr:
+    type: object
+    additionalProperties: false
+
+    properties:
+      '#address-cells':
+        const: 1
+
+      '#size-cells':
+        const: 0
+
+    patternProperties:
+      '^i2c@[0-3]$':
+        $ref: /schemas/i2c/i2c-controller.yaml#
+        unevaluatedProperties: false
+        properties:
+          reg:
+            items:
+              minimum: 0
+              maximum: 3
+
   ports:
     $ref: /schemas/graph.yaml#/properties/ports
 
@@ -79,6 +103,13 @@ required:
 
 additionalProperties: false
 
+allOf:
+  - $ref: /schemas/i2c/i2c-atr.yaml#
+
+dependentRequired:
+  i2c-atr: [i2c-alias-pool]
+  i2c-alias-pool: [i2c-atr]
+
 examples:
   - |
     #include <dt-bindings/gpio/gpio.h>
-- 
2.50.1


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

* [PATCH v6 10/24] dt-bindings: media: i2c: max96712: add support for POC supplies
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (8 preceding siblings ...)
  2025-07-16 19:30 ` [PATCH v6 09/24] dt-bindings: media: i2c: max96712: add support for I2C ATR Cosmin Tanislav
@ 2025-07-16 19:30 ` Cosmin Tanislav
  2025-07-16 19:30 ` [PATCH v6 11/24] dt-bindings: media: i2c: max96712: add support for MAX96724F/R Cosmin Tanislav
                   ` (13 subsequent siblings)
  23 siblings, 0 replies; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:30 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav, Niklas Söderlund

The GMSL links can carry power to the serializer when using coaxial
cables.

Document this capability.

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
Reviewed-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
---
 .../devicetree/bindings/media/i2c/maxim,max96712.yaml         | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
index 758c0223977d4..b345305acc4c3 100644
--- a/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
@@ -96,6 +96,10 @@ properties:
       - required: [port@6]
       - required: [port@7]
 
+patternProperties:
+  '^port[0-3]-poc-supply$':
+    description: Regulator providing Power over Coax for GMSL ports
+
 required:
   - compatible
   - reg
-- 
2.50.1


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

* [PATCH v6 11/24] dt-bindings: media: i2c: max96712: add support for MAX96724F/R
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (9 preceding siblings ...)
  2025-07-16 19:30 ` [PATCH v6 10/24] dt-bindings: media: i2c: max96712: add support for POC supplies Cosmin Tanislav
@ 2025-07-16 19:30 ` Cosmin Tanislav
  2025-07-16 19:30 ` [PATCH v6 12/24] dt-bindings: media: i2c: max96714: add myself as maintainer Cosmin Tanislav
                   ` (12 subsequent siblings)
  23 siblings, 0 replies; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:30 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav, Niklas Söderlund

MAX96724F/MAX96724R are a lower capability variant of the MAX96724 which
only support a fixed rate of 3Gbps in the forward direction.

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
Reviewed-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
---
 .../devicetree/bindings/media/i2c/maxim,max96712.yaml        | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
index b345305acc4c3..5dcafd46344cc 100644
--- a/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
@@ -24,12 +24,17 @@ description: |
   MAX96712 can be paired with first-generation 3.12Gbps or 1.5Gbps GMSL1
   serializers or operate up to 3.12Gbps with GMSL2 serializers in GMSL1 mode.
 
+  MAX96724F and MAX96724R only support a fixed rate of 3Gbps in the forward
+  direction.
+
 properties:
   compatible:
     items:
       - enum:
           - maxim,max96712
           - maxim,max96724
+          - maxim,max96724f
+          - maxim,max96724r
 
   reg:
     description: I2C device address
-- 
2.50.1


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

* [PATCH v6 12/24] dt-bindings: media: i2c: max96714: add myself as maintainer
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (10 preceding siblings ...)
  2025-07-16 19:30 ` [PATCH v6 11/24] dt-bindings: media: i2c: max96712: add support for MAX96724F/R Cosmin Tanislav
@ 2025-07-16 19:30 ` Cosmin Tanislav
  2025-08-01 12:22   ` Julien Massot
  2025-07-16 19:30 ` [PATCH v6 13/24] dt-bindings: media: i2c: max96714: add support for MAX96714R Cosmin Tanislav
                   ` (11 subsequent siblings)
  23 siblings, 1 reply; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:30 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

Analog Devices is taking responsability for the maintenance of the Maxim
GMSL2/3 devices.
Add myself to the maintainers list and to the device tree bindings.

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
---
 Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml | 1 +
 MAINTAINERS                                                     | 1 +
 2 files changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml
index 3ace50e11921b..f53c72e5c5727 100644
--- a/Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml
@@ -8,6 +8,7 @@ $schema: http://devicetree.org/meta-schemas/core.yaml#
 title: Maxim MAX96714 GMSL2 to CSI-2 Deserializer
 
 maintainers:
+  - Cosmin Tanislav <cosmin.tanislav@analog.com>
   - Julien Massot <julien.massot@collabora.com>
 
 description:
diff --git a/MAINTAINERS b/MAINTAINERS
index 3117345d0619c..93e22dfd61c17 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14755,6 +14755,7 @@ F:	drivers/staging/media/max96712/max96712.c
 
 MAX96714 GMSL2 DESERIALIZER DRIVER
 M:	Julien Massot <julien.massot@collabora.com>
+M:	Cosmin Tanislav <cosmin.tanislav@analog.com>
 L:	linux-media@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml
-- 
2.50.1


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

* [PATCH v6 13/24] dt-bindings: media: i2c: max96714: add support for MAX96714R
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (11 preceding siblings ...)
  2025-07-16 19:30 ` [PATCH v6 12/24] dt-bindings: media: i2c: max96714: add myself as maintainer Cosmin Tanislav
@ 2025-07-16 19:30 ` Cosmin Tanislav
  2025-08-01 12:23   ` Julien Massot
  2025-07-16 19:30 ` [PATCH v6 14/24] dt-bindings: media: i2c: add MAX9296A, MAX96716A, MAX96792A Cosmin Tanislav
                   ` (10 subsequent siblings)
  23 siblings, 1 reply; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:30 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

MAX96714R is a lower capability variant of the MAX96714 which only
supports a fixed rate of 3Gbps in the forward direction.

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
---
 .../devicetree/bindings/media/i2c/maxim,max96714.yaml        | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml
index f53c72e5c5727..1c97624833ebd 100644
--- a/Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml
@@ -23,7 +23,9 @@ description:
 
   The GMSL2 serial link operates at a fixed rate of 3Gbps or 6Gbps in the
   forward direction and 187.5Mbps in the reverse direction.
-  MAX96714F only supports a fixed rate of 3Gbps in the forward direction.
+
+  MAX96714F and MAX96714R only support a fixed rate of 3Gbps in the forward
+  direction.
 
 properties:
   compatible:
@@ -32,6 +34,7 @@ properties:
       - items:
           - enum:
               - maxim,max96714
+              - maxim,max96714r
           - const: maxim,max96714f
 
   reg:
-- 
2.50.1


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

* [PATCH v6 14/24] dt-bindings: media: i2c: add MAX9296A, MAX96716A, MAX96792A
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (12 preceding siblings ...)
  2025-07-16 19:30 ` [PATCH v6 13/24] dt-bindings: media: i2c: max96714: add support for MAX96714R Cosmin Tanislav
@ 2025-07-16 19:30 ` Cosmin Tanislav
  2025-07-16 21:35   ` Rob Herring (Arm)
  2025-07-16 19:31 ` [PATCH v6 15/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer framework Cosmin Tanislav
                   ` (9 subsequent siblings)
  23 siblings, 1 reply; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:30 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

The MAX9296A deserializer converts single or dual serial inputs to MIPI
CSI-2 outputs. The GMSL2 links operate at a fixed rate of 3Gbps or 6Gbps
in the forward direction and 187.5Mbps in the reverse direction.
In GMSL1 mode, each serial link can be paired with 3.12Gbps or 1.5Gbps
GMSL1 serializers or operate up to 4.5Gbps with GMSL2 serializers with
GMSL1 backward compatibility. The MAX9296A supports mixed GMSL2 and
GMSL1 links. The serial inputs operate independently, allowing videos
with different timings and resolutions to be received on each input.

MAX96716A supports both tunnel and pixel mode.
MAX96792A supports both tunnel and pixel mode, and has two GMSL3 links.

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
---
 .../bindings/media/i2c/maxim,max9296a.yaml    | 242 ++++++++++++++++++
 MAINTAINERS                                   |   6 +
 2 files changed, 248 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/i2c/maxim,max9296a.yaml

diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max9296a.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max9296a.yaml
new file mode 100644
index 0000000000000..c0a8916353f6a
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/maxim,max9296a.yaml
@@ -0,0 +1,242 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) 2024 Collabora Ltd.
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/maxim,max9296a.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Maxim MAX9296A GMSL2 to CSI-2 Deserializer
+
+maintainers:
+  - Cosmin Tanislav <cosmin.tanislav@analog.com>
+
+description: >
+  The MAX9296A deserializer converts single or dual serial inputs to
+  MIPI CSI-2 outputs. The GMSL2 links operate at a fixed rate of 3Gbps
+  or 6Gbps in the forward direction and 187.5Mbps in the reverse
+  direction. In GMSL1 mode, each serial link can be paired with 3.12Gbps
+  or 1.5Gbps GMSL1 serializers or operate up to 4.5Gbps with GMSL2
+  serializers with GMSL1 backward compatibility. The MAX9296A supports
+  mixed GMSL2 and GMSL1 links. The serial inputs operate independently,
+  allowing videos with different timings and resolutions to be received
+  on each input.
+
+  MAX96716A supports both tunnel and pixel mode.
+
+  MAX96792A supports both tunnel and pixel mode, and has two GMSL3 links.
+
+properties:
+  compatible:
+    enum:
+      - maxim,max9296a
+      - maxim,max96716a
+      - maxim,max96792a
+
+  reg:
+    maxItems: 1
+
+  powerdown-gpios:
+    maxItems: 1
+    description: Specifier for the GPIO connected to the PWDNB pin.
+
+  port0-poc-supply:
+    description: Regulator providing Power over Coax for GMSL port 0
+
+  port1-poc-supply:
+    description: Regulator providing Power over Coax for GMSL port 1
+
+  i2c-alias-pool:
+    maxItems: 2
+
+  i2c-atr:
+    type: object
+    additionalProperties: false
+
+    properties:
+      '#address-cells':
+        const: 1
+
+      '#size-cells':
+        const: 0
+
+    patternProperties:
+      '^i2c@[0-1]$':
+        $ref: /schemas/i2c/i2c-controller.yaml#
+        unevaluatedProperties: false
+        properties:
+          reg:
+            items:
+              minimum: 0
+              maximum: 1
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    patternProperties:
+      '^port@[0-1]$':
+        $ref: /schemas/graph.yaml#/properties/port
+        description: GMSL Input ports 0-1
+
+      '^port@[2-3]$':
+        $ref: /schemas/graph.yaml#/$defs/port-base
+        unevaluatedProperties: false
+        description: CSI-2 Output ports 0-1
+        properties:
+          endpoint:
+            $ref: /schemas/media/video-interfaces.yaml#
+            unevaluatedProperties: false
+
+            properties:
+              data-lanes:
+                minItems: 1
+                maxItems: 4
+
+              lane-polarities:
+                minItems: 1
+                maxItems: 5
+
+              link-frequencies:
+                maxItems: 1
+
+            required:
+              - data-lanes
+
+    anyOf:
+      - required:
+          - port@2
+      - required:
+          - port@3
+
+required:
+  - compatible
+  - reg
+  - ports
+
+additionalProperties: false
+
+allOf:
+  - $ref: /schemas/i2c/i2c-atr.yaml#
+
+dependentRequired:
+  i2c-atr: [i2c-alias-pool]
+  i2c-alias-pool: [i2c-atr]
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/media/video-interfaces.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        deserializer@28 {
+            compatible = "maxim,max9296a";
+            reg = <0x28>;
+            powerdown-gpios = <&main_gpio0 37 GPIO_ACTIVE_LOW>;
+
+            i2c-alias-pool = <0x40 0x41>;
+
+            ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@0 {
+                    reg = <0>;
+                    des_gmsl_in_0: endpoint {
+                        remote-endpoint = <&ser_0_gmsl_out>;
+                    };
+                };
+
+                port@1 {
+                    reg = <1>;
+                    des_gmsl_in_1: endpoint {
+                        remote-endpoint = <&ser_1_gmsl_out>;
+                    };
+                };
+
+                port@2 {
+                    reg = <2>;
+                    des_csi_out: endpoint {
+                        data-lanes = <1 2 3 4>;
+                        link-frequencies = /bits/ 64 <400000000>;
+                        remote-endpoint = <&csi_in>;
+                    };
+                };
+            };
+
+            i2c-atr {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                i2c@0 {
+                    #address-cells = <1>;
+                    #size-cells = <0>;
+                    reg = <0>;
+
+                    serializer@40 {
+                        compatible = "maxim,max96717";
+                        reg = <0x40>;
+                        gpio-controller;
+                        #gpio-cells = <2>;
+                        #clock-cells = <0>;
+
+                        ports {
+                            #address-cells = <1>;
+                            #size-cells = <0>;
+
+                            port@0 {
+                                reg = <0>;
+                                ser_0_csi_in: endpoint {
+                                    data-lanes = <1 2>;
+                                    remote-endpoint = <&sensor_0_out>;
+                                };
+                            };
+
+                            port@1 {
+                                reg = <1>;
+                                ser_0_gmsl_out: endpoint {
+                                    remote-endpoint = <&des_gmsl_in_0>;
+                                };
+                            };
+                        };
+                    };
+                };
+
+                i2c@1 {
+                    #address-cells = <1>;
+                    #size-cells = <0>;
+                    reg = <1>;
+
+                    serializer@40 {
+                        compatible = "maxim,max96717";
+                        reg = <0x40>;
+                        gpio-controller;
+                        #gpio-cells = <2>;
+                        #clock-cells = <0>;
+
+                        ports {
+                            #address-cells = <1>;
+                            #size-cells = <0>;
+
+                            port@0 {
+                                reg = <0>;
+                                ser_1_csi_in: endpoint {
+                                    data-lanes = <1 2>;
+                                    remote-endpoint = <&sensor_1_out>;
+                                };
+                            };
+
+                            port@1 {
+                                reg = <1>;
+                                ser_1_gmsl_out: endpoint {
+                                    remote-endpoint = <&des_gmsl_in_1>;
+                                };
+                            };
+                        };
+                    };
+                };
+            };
+        };
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 93e22dfd61c17..0eb1729ae1647 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14783,6 +14783,12 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/iio/proximity/maxbotix,mb1232.yaml
 F:	drivers/iio/proximity/mb1232.c
 
+MAXIM GMSL2/3 SERIALIZERS AND DESERIALIZERS
+M:	Cosmin Tanislav <cosmin.tanislav@analog.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/media/i2c/maxim,max9296a.yaml
+
 MAXIM MAX11205 DRIVER
 M:	Ramona Bolboaca <ramona.bolboaca@analog.com>
 L:	linux-iio@vger.kernel.org
-- 
2.50.1


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

* [PATCH v6 15/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer framework
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (13 preceding siblings ...)
  2025-07-16 19:30 ` [PATCH v6 14/24] dt-bindings: media: i2c: add MAX9296A, MAX96716A, MAX96792A Cosmin Tanislav
@ 2025-07-16 19:31 ` Cosmin Tanislav
  2025-07-18  0:25   ` kernel test robot
  2025-07-18  1:37   ` kernel test robot
  2025-07-16 19:31 ` [PATCH v6 16/24] media: i2c: add Maxim GMSL2/3 serializer framework Cosmin Tanislav
                   ` (8 subsequent siblings)
  23 siblings, 2 replies; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:31 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

These drivers are meant to be used as a common framework for Maxim
GMSL2/3 serializers and deserializers.

This framework enables support for the following new features across
all the chips:
 * Full Streams API support
 * .get_frame_desc()
 * .get_mbus_config()
 * I2C ATR
 * automatic GMSL link version negotiation
 * automatic stream id selection
 * automatic VC remapping
 * automatic pixel mode / tunnel mode selection
 * automatic double mode selection / data padding
 * logging of internal state and chip status registers via .log_status()
 * PHY modes
 * serializer pinctrl
 * TPG

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
---
 MAINTAINERS                                 |   1 +
 drivers/media/i2c/Kconfig                   |   2 +
 drivers/media/i2c/Makefile                  |   1 +
 drivers/media/i2c/maxim-serdes/Kconfig      |  16 +
 drivers/media/i2c/maxim-serdes/Makefile     |   3 +
 drivers/media/i2c/maxim-serdes/max_serdes.c | 413 ++++++++++++++++++++
 drivers/media/i2c/maxim-serdes/max_serdes.h | 183 +++++++++
 7 files changed, 619 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/max_serdes.c
 create mode 100644 drivers/media/i2c/maxim-serdes/max_serdes.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 0eb1729ae1647..0c75a5c195c28 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14788,6 +14788,7 @@ M:	Cosmin Tanislav <cosmin.tanislav@analog.com>
 L:	linux-media@vger.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@analog.com>
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 6237fe804a5c8..801a712a31808 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1686,6 +1686,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 5873d29433ee5..25a0093d40ecf 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 0000000000000..cae1d5a1293ee
--- /dev/null
+++ b/drivers/media/i2c/maxim-serdes/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config VIDEO_MAXIM_SERDES
+	tristate "Maxim GMSL2/3 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/3 common Serializer and
+	  Deserializer framework.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called max_serdes.
diff --git a/drivers/media/i2c/maxim-serdes/Makefile b/drivers/media/i2c/maxim-serdes/Makefile
new file mode 100644
index 0000000000000..630fbb486bab1
--- /dev/null
+++ b/drivers/media/i2c/maxim-serdes/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+max-serdes-objs := max_serdes.o
+obj-$(CONFIG_VIDEO_MAXIM_SERDES) += max-serdes.o
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 0000000000000..bed70b8ce99a4
--- /dev/null
+++ b/drivers/media/i2c/maxim-serdes/max_serdes.c
@@ -0,0 +1,413 @@
+// 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 <video/videomode.h>
+
+#include <uapi/linux/media-bus-format.h>
+
+#include "max_serdes.h"
+
+const char * const max_serdes_tpg_patterns[] = {
+	[MAX_SERDES_TPG_PATTERN_GRADIENT] = "Gradient",
+	[MAX_SERDES_TPG_PATTERN_CHECKERBOARD] = "Checkerboard",
+};
+
+static const char * const max_gmsl_versions[] = {
+	[MAX_SERDES_GMSL_2_3GBPS] = "GMSL2 3Gbps",
+	[MAX_SERDES_GMSL_2_6GBPS] = "GMSL2 6Gbps",
+	[MAX_SERDES_GMSL_3_12GBPS] = "GMSL3 12Gbps",
+};
+
+const char *max_serdes_gmsl_version_str(enum max_serdes_gmsl_version version)
+{
+	if (version > MAX_SERDES_GMSL_3_12GBPS)
+		return NULL;
+
+	return max_gmsl_versions[version];
+}
+
+static const char * const max_gmsl_mode[] = {
+	[MAX_SERDES_GMSL_PIXEL_MODE] = "pixel",
+	[MAX_SERDES_GMSL_TUNNEL_MODE] = "tunnel",
+};
+
+const char *max_serdes_gmsl_mode_str(enum max_serdes_gmsl_mode mode)
+{
+	if (mode > MAX_SERDES_GMSL_TUNNEL_MODE)
+		return NULL;
+
+	return max_gmsl_mode[mode];
+}
+
+static const struct max_serdes_mipi_format max_serdes_mipi_formats[] = {
+	{ MIPI_CSI2_DT_EMBEDDED_8B, 8 },
+	{ MIPI_CSI2_DT_YUV422_8B, 16 },
+	{ MIPI_CSI2_DT_YUV422_10B, 20 },
+	{ MIPI_CSI2_DT_RGB565, 16 },
+	{ MIPI_CSI2_DT_RGB666, 18 },
+	{ MIPI_CSI2_DT_RGB888, 24 },
+	{ MIPI_CSI2_DT_RAW8, 8 },
+	{ MIPI_CSI2_DT_RAW10, 10 },
+	{ MIPI_CSI2_DT_RAW12, 12 },
+	{ MIPI_CSI2_DT_RAW14, 14 },
+	{ MIPI_CSI2_DT_RAW16, 16 },
+};
+
+const struct max_serdes_mipi_format *max_serdes_mipi_format_by_dt(u8 dt)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(max_serdes_mipi_formats); i++)
+		if (max_serdes_mipi_formats[i].dt == dt)
+			return &max_serdes_mipi_formats[i];
+
+	return NULL;
+}
+
+int max_serdes_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;
+}
+
+int max_serdes_get_fd_bpp(struct v4l2_mbus_frame_desc_entry *entry,
+			  unsigned int *bpp)
+{
+	const struct max_serdes_mipi_format *format;
+
+	format = max_serdes_mipi_format_by_dt(entry->bus.csi2.dt);
+	if (!format)
+		return -ENOENT;
+
+	*bpp = format->bpp;
+
+	return 0;
+}
+
+int max_serdes_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;
+}
+
+int max_serdes_xlate_enable_disable_streams(struct max_serdes_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_serdes_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_serdes_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;
+}
+
+int max_serdes_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;
+}
+
+static const struct videomode max_serdes_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,
+	},
+};
+
+static void max_serdes_get_vm_timings(const struct videomode *vm,
+				      struct max_serdes_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_serdes_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,
+		.clock = vm->pixelclock,
+		.fps = DIV_ROUND_CLOSEST(vm->pixelclock, vtot * htot),
+	};
+}
+
+int max_serdes_get_tpg_timings(const struct max_serdes_tpg_entry *entry,
+			       struct max_serdes_tpg_timings *timings)
+{
+	u32 fps;
+
+	if (!entry)
+		return 0;
+
+	fps = DIV_ROUND_CLOSEST(1 * entry->interval.denominator,
+				entry->interval.numerator);
+
+	for (unsigned int i = 0; i < ARRAY_SIZE(max_serdes_tpg_pixel_videomodes); i++) {
+		struct max_serdes_tpg_timings vm_timings;
+		const struct videomode *vm;
+
+		vm = &max_serdes_tpg_pixel_videomodes[i];
+
+		max_serdes_get_vm_timings(vm, &vm_timings);
+
+		if (vm->hactive == entry->width &&
+		    vm->vactive == entry->height &&
+		    vm_timings.fps == fps) {
+			*timings = vm_timings;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+EXPORT_SYMBOL_NS_GPL(max_serdes_get_tpg_timings, "MAX_SERDES");
+
+int max_serdes_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;
+}
+
+MODULE_DESCRIPTION("Maxim GMSL2 Serializer/Deserializer Driver");
+MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@analog.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 0000000000000..d1d513e464d6c
--- /dev/null
+++ b/drivers/media/i2c/maxim-serdes/max_serdes.h
@@ -0,0 +1,183 @@
+/* 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>
+
+#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 REG_SEQUENCE_3_LE(reg, val) \
+	{ (reg),     ((val) >> 0) & 0xff }, \
+	{ (reg) + 1, ((val) >> 8)  & 0xff }, \
+	{ (reg) + 2, ((val) >> 16)  & 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
+
+#define MAX_SERDES_GRAD_INCR		4
+#define MAX_SERDES_CHECKER_COLOR_A	0x00ccfe
+#define MAX_SERDES_CHECKER_COLOR_B	0xa76a00
+#define MAX_SERDES_CHECKER_SIZE		60
+
+extern const char * const max_serdes_tpg_patterns[];
+
+enum max_serdes_gmsl_version {
+	MAX_SERDES_GMSL_MIN,
+	MAX_SERDES_GMSL_2_3GBPS = MAX_SERDES_GMSL_MIN,
+	MAX_SERDES_GMSL_2_6GBPS,
+	MAX_SERDES_GMSL_3_12GBPS,
+	MAX_SERDES_GMSL_MAX = MAX_SERDES_GMSL_3_12GBPS,
+};
+
+enum max_serdes_gmsl_mode {
+	MAX_SERDES_GMSL_PIXEL_MODE,
+	MAX_SERDES_GMSL_TUNNEL_MODE,
+};
+
+enum max_serdes_tpg_pattern {
+	MAX_SERDES_TPG_PATTERN_MIN,
+	MAX_SERDES_TPG_PATTERN_CHECKERBOARD = MAX_SERDES_TPG_PATTERN_MIN,
+	MAX_SERDES_TPG_PATTERN_GRADIENT,
+	MAX_SERDES_TPG_PATTERN_MAX = MAX_SERDES_TPG_PATTERN_GRADIENT,
+};
+
+struct max_serdes_phys_config {
+	unsigned int lanes[MAX_SERDES_PHYS_MAX];
+	unsigned int clock_lane[MAX_SERDES_PHYS_MAX];
+};
+
+struct max_serdes_phys_configs {
+	const struct max_serdes_phys_config *configs;
+	unsigned int num_configs;
+};
+
+struct max_serdes_i2c_xlate {
+	u8 src;
+	u8 dst;
+	bool en;
+};
+
+struct max_serdes_mipi_format {
+	u8 dt;
+	u8 bpp;
+};
+
+struct max_serdes_vc_remap {
+	u8 src;
+	u8 dst;
+};
+
+struct max_serdes_source {
+	struct v4l2_subdev *sd;
+	u16 pad;
+	struct fwnode_handle *ep_fwnode;
+
+	unsigned int index;
+};
+
+struct max_serdes_asc {
+	struct v4l2_async_connection base;
+	struct max_serdes_source *source;
+};
+
+struct max_serdes_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_serdes_tpg_entries {
+	const struct max_serdes_tpg_entry *entries;
+	unsigned int num_entries;
+};
+
+struct max_serdes_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 clock;
+	u32 fps;
+};
+
+static inline struct max_serdes_asc *asc_to_max(struct v4l2_async_connection *asc)
+{
+	return container_of(asc, struct max_serdes_asc, base);
+}
+
+const char *max_serdes_gmsl_version_str(enum max_serdes_gmsl_version version);
+const char *max_serdes_gmsl_mode_str(enum max_serdes_gmsl_mode mode);
+
+const struct max_serdes_mipi_format *max_serdes_mipi_format_by_dt(u8 dt);
+
+int max_serdes_get_fd_stream_entry(struct v4l2_subdev *sd, u32 pad, u32 stream,
+				   struct v4l2_mbus_frame_desc_entry *entry);
+
+int max_serdes_get_fd_bpp(struct v4l2_mbus_frame_desc_entry *entry,
+			  unsigned int *bpp);
+int max_serdes_process_bpps(struct device *dev, u32 bpps,
+			    u32 allowed_double_bpps, unsigned int *doubled_bpp);
+
+int max_serdes_xlate_enable_disable_streams(struct max_serdes_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_serdes_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);
+
+int max_serdes_get_tpg_timings(const struct max_serdes_tpg_entry *entry,
+			       struct max_serdes_tpg_timings *timings);
+
+int max_serdes_validate_tpg_routing(struct v4l2_subdev_krouting *routing);
+
+#endif // MAX_SERDES_H
-- 
2.50.1


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

* [PATCH v6 16/24] media: i2c: add Maxim GMSL2/3 serializer framework
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (14 preceding siblings ...)
  2025-07-16 19:31 ` [PATCH v6 15/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer framework Cosmin Tanislav
@ 2025-07-16 19:31 ` Cosmin Tanislav
  2025-07-16 19:31 ` [PATCH v6 17/24] media: i2c: add Maxim GMSL2/3 deserializer framework Cosmin Tanislav
                   ` (7 subsequent siblings)
  23 siblings, 0 replies; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:31 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

These drivers are meant to be used as a common framework for Maxim
GMSL2/3 serializers.

This framework enables support for the following new features across
all the chips:
 * Full Streams API support
 * .get_frame_desc()
 * .get_mbus_config()
 * I2C ATR
 * automatic GMSL link version negotiation
 * automatic stream id selection
 * automatic VC remapping
 * automatic pixel mode / tunnel mode selection
 * automatic double mode selection / data padding
 * logging of internal state and chip status registers via .log_status()
 * PHY modes
 * serializer pinctrl
 * TPG

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
---
 drivers/media/i2c/maxim-serdes/Makefile  |    2 +-
 drivers/media/i2c/maxim-serdes/max_ser.c | 2130 ++++++++++++++++++++++
 drivers/media/i2c/maxim-serdes/max_ser.h |  147 ++
 3 files changed, 2278 insertions(+), 1 deletion(-)
 create mode 100644 drivers/media/i2c/maxim-serdes/max_ser.c
 create mode 100644 drivers/media/i2c/maxim-serdes/max_ser.h

diff --git a/drivers/media/i2c/maxim-serdes/Makefile b/drivers/media/i2c/maxim-serdes/Makefile
index 630fbb486bab1..17511cb033690 100644
--- a/drivers/media/i2c/maxim-serdes/Makefile
+++ b/drivers/media/i2c/maxim-serdes/Makefile
@@ -1,3 +1,3 @@
 # SPDX-License-Identifier: GPL-2.0
-max-serdes-objs := max_serdes.o
+max-serdes-objs := max_serdes.o max_ser.o
 obj-$(CONFIG_VIDEO_MAXIM_SERDES) += max-serdes.o
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 0000000000000..dd28699c1e9fe
--- /dev/null
+++ b/drivers/media/i2c/maxim-serdes/max_ser.c
@@ -0,0 +1,2130 @@
+// 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-ctrls.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
+#define MAX_SER_NUM_PHYS	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_serdes_source *sources;
+	u64 *streams_masks;
+	u32 double_bpps;
+
+	struct v4l2_subdev sd;
+	struct v4l2_async_notifier nf;
+	struct v4l2_ctrl_handler ctrl_handler;
+};
+
+struct max_ser_route_hw {
+	struct max_serdes_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 struct max_ser_priv *ctrl_to_priv(struct v4l2_ctrl_handler *handler)
+{
+	return container_of(handler, struct max_ser_priv, ctrl_handler);
+}
+
+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 u32 max_ser_source_pad(struct max_ser *ser)
+{
+	return ser->ops->num_phys;
+}
+
+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_serdes_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_serdes_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_serdes_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_serdes_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_serdes_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_serdes_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_serdes_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_serdes_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_serdes_gmsl_mode_str(ser->mode));
+	if (ser->ops->set_tpg) {
+		const struct max_serdes_tpg_entry *entry = ser->tpg_entry;
+
+		if (entry) {
+			v4l2_info(sd, "tpg: %ux%u@%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);
+		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");
+	if (ser->ops->set_vc_remap) {
+		v4l2_info(sd, "vc_remaps: %u\n", ser->num_vc_remaps);
+		for (j = 0; j < ser->num_vc_remaps; j++) {
+			v4l2_info(sd, "\tvc_remap: src: %u, dst: %u\n",
+				  ser->vc_remaps[j].src, ser->vc_remaps[j].dst);
+		}
+	}
+	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->log_pipe_status) {
+			ret = ser->ops->log_pipe_status(ser, pipe);
+			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);
+			if (ret)
+				return ret;
+		}
+		v4l2_info(sd, "\n");
+	}
+
+	return 0;
+}
+
+static int max_ser_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct max_ser_priv *priv = ctrl_to_priv(ctrl->handler);
+	struct max_ser *ser = priv->ser;
+
+	switch (ctrl->id) {
+	case V4L2_CID_TEST_PATTERN:
+		ser->tpg_pattern = ctrl->val;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+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_serdes_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_serdes_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_serdes_tpg_entry *entry;
+	struct v4l2_mbus_framefmt fmt = { 0 };
+	int ret;
+
+	ret = max_serdes_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,
+				 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;
+
+	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);
+
+	return v4l2_subdev_set_routing(sd, state, routing);
+}
+
+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;
+
+	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && ser->active)
+		return -EBUSY;
+
+	return __max_ser_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_SERDES_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_SERDES_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_serdes_get_fd_bpp(&hw.entry, &bpp);
+		if (ret)
+			return ret;
+
+		bpps |= BIT(bpp);
+	}
+
+	ret = max_serdes_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_serdes_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_serdes_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;
+	}
+
+	if (entry == ser->tpg_entry)
+		return 0;
+
+	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_serdes_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);
+}
+
+static int max_ser_init_state(struct v4l2_subdev *sd,
+			      struct v4l2_subdev_state *state)
+{
+	struct v4l2_subdev_route routes[MAX_SER_NUM_PHYS] = { 0 };
+	struct v4l2_subdev_krouting routing = {
+		.routes = routes,
+	};
+	struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
+	struct max_ser *ser = priv->ser;
+	unsigned int stream = 0;
+	unsigned int i;
+
+	for (i = 0; i < ser->ops->num_phys; i++) {
+		struct max_ser_phy *phy = &ser->phys[i];
+
+		if (!phy->enabled)
+			continue;
+
+		routing.routes[routing.num_routes++] = (struct v4l2_subdev_route) {
+			.sink_pad = max_ser_phy_to_pad(ser, phy),
+			.sink_stream = 0,
+			.source_pad = max_ser_source_pad(ser),
+			.source_stream = stream,
+			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+		};
+		stream++;
+
+		/*
+		 * The Streams API is an experimental feature.
+		 * If multiple routes are provided here, userspace will not be
+		 * able to configure them unless the Streams API is enabled.
+		 * Provide a single route until it is enabled.
+		 */
+		break;
+	}
+
+	return __max_ser_set_routing(sd, state, &routing);
+}
+
+#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_ctrl_ops max_ser_ctrl_ops = {
+	.s_ctrl = max_ser_s_ctrl,
+};
+
+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 v4l2_subdev_internal_ops max_ser_internal_ops = {
+	.init_state = &max_ser_init_state,
+};
+
+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_serdes_asc *asc = asc_to_max(base_asc);
+	struct max_serdes_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_serdes_asc *asc = asc_to_max(base_asc);
+	struct max_serdes_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_serdes_source *source;
+		struct max_serdes_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_serdes_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->internal_ops = &max_ser_internal_ops;
+	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);
+
+	if (ser->ops->tpg_patterns) {
+		v4l2_ctrl_handler_init(&priv->ctrl_handler, 1);
+		priv->sd.ctrl_handler = &priv->ctrl_handler;
+
+		v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler,
+					     &max_ser_ctrl_ops,
+					     V4L2_CID_TEST_PATTERN,
+					     MAX_SERDES_TPG_PATTERN_MAX,
+					     ~ser->ops->tpg_patterns,
+					     __ffs(ser->ops->tpg_patterns),
+					     max_serdes_tpg_patterns);
+		if (priv->ctrl_handler.error) {
+			ret = priv->ctrl_handler.error;
+			goto err_free_ctrl;
+		}
+	}
+
+	ret = media_entity_pads_init(&sd->entity, num_pads, priv->pads);
+	if (ret)
+		goto err_free_ctrl;
+
+	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);
+err_free_ctrl:
+	v4l2_ctrl_handler_free(&priv->ctrl_handler);
+
+	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_serdes_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_serdes_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_serdes_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_serdes_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->vc_remaps = devm_kcalloc(priv->dev, ser->ops->num_vc_remaps,
+				      sizeof(*ser->vc_remaps), GFP_KERNEL);
+	if (!ser->vc_remaps)
+		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;
+
+	if (ser->ops->num_phys > MAX_SER_NUM_PHYS)
+		return -E2BIG;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->client = client;
+	priv->dev = dev;
+	priv->ser = ser;
+	ser->priv = priv;
+	ser->mode = __ffs(ser->ops->modes);
+
+	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_NS_GPL(max_ser_probe, "MAX_SERDES");
+
+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_NS_GPL(max_ser_remove, "MAX_SERDES");
+
+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;
+}
+
+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];
+
+	if (!ser->ops->set_pipe_stream_id)
+		return -EOPNOTSUPP;
+
+	return ser->ops->set_pipe_stream_id(ser, pipe, 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];
+
+	if (!ser->ops->get_pipe_stream_id)
+		return -EOPNOTSUPP;
+
+	*stream_id = ser->ops->get_pipe_stream_id(ser, pipe);
+
+	return 0;
+}
+
+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;
+}
+
+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_vc_remap;
+}
+
+int max_ser_set_mode(struct v4l2_subdev *sd, enum max_serdes_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_SERDES_GMSL_TUNNEL_MODE;
+
+		ret = ser->ops->set_tunnel_enable(ser, tunnel_enable);
+		if (ret)
+			return ret;
+	}
+
+	ser->mode = mode;
+
+	return 0;
+}
+
+int max_ser_set_vc_remaps(struct v4l2_subdev *sd,
+			  struct max_serdes_vc_remap *vc_remaps,
+			  int num_vc_remaps)
+{
+	struct max_ser_priv *priv = sd_to_priv(sd);
+	struct max_ser *ser = priv->ser;
+	unsigned int mask = 0;
+	unsigned int i;
+	int ret;
+
+	if (!ser->ops->set_vc_remap)
+		return -EOPNOTSUPP;
+
+	if (num_vc_remaps > ser->ops->num_vc_remaps)
+		return -E2BIG;
+
+	for (i = 0; i < num_vc_remaps; i++) {
+		ret = ser->ops->set_vc_remap(ser, i, &vc_remaps[i]);
+		if (ret)
+			return ret;
+
+		mask |= BIT(i);
+	}
+
+	ret = ser->ops->set_vc_remaps_enable(ser, mask);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < num_vc_remaps; i++)
+		ser->vc_remaps[i] = vc_remaps[i];
+
+	ser->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);
+}
+
+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;
+}
+
+int max_ser_wait(struct i2c_adapter *adapter, u8 addr)
+{
+	u8 current_addr;
+
+	return max_ser_wait_for_multiple(adapter, &addr, 1, &current_addr);
+}
+
+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;
+}
+
+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);
+}
+
+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 0000000000000..a9365be5e62dd
--- /dev/null
+++ b/drivers/media/i2c/maxim-serdes/max_ser.h
@@ -0,0 +1,147 @@
+/* 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_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;
+	unsigned int num_vc_remaps;
+
+	struct max_serdes_phys_configs phys_configs;
+	struct max_serdes_tpg_entries tpg_entries;
+	enum max_serdes_gmsl_mode tpg_mode;
+	unsigned int tpg_patterns;
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	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);
+#endif
+	int (*log_status)(struct max_ser *ser);
+	int (*log_pipe_status)(struct max_ser *ser, struct max_ser_pipe *pipe);
+	int (*log_phy_status)(struct max_ser *ser, struct max_ser_phy *phy);
+	int (*init)(struct max_ser *ser);
+	int (*set_i2c_xlate)(struct max_ser *ser, unsigned int i,
+			     struct max_serdes_i2c_xlate *i2c_xlate);
+	int (*set_tunnel_enable)(struct max_ser *ser, bool enable);
+	int (*set_tpg)(struct max_ser *ser, const struct max_serdes_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_vc_remap)(struct max_ser *ser, unsigned int i,
+			    struct max_serdes_vc_remap *vc_remap);
+	int (*set_vc_remaps_enable)(struct max_ser *ser, 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_serdes_i2c_xlate *i2c_xlates;
+
+	struct max_ser_phy *phys;
+	struct max_ser_pipe *pipes;
+	const struct max_serdes_tpg_entry *tpg_entry;
+	enum max_serdes_tpg_pattern tpg_pattern;
+
+	struct max_serdes_vc_remap *vc_remaps;
+	unsigned int num_vc_remaps;
+
+	unsigned int phys_config;
+	unsigned int active;
+	enum max_serdes_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_serdes_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_serdes_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
-- 
2.50.1


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

* [PATCH v6 17/24] media: i2c: add Maxim GMSL2/3 deserializer framework
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (15 preceding siblings ...)
  2025-07-16 19:31 ` [PATCH v6 16/24] media: i2c: add Maxim GMSL2/3 serializer framework Cosmin Tanislav
@ 2025-07-16 19:31 ` Cosmin Tanislav
  2025-07-16 19:31 ` [PATCH v6 18/24] media: i2c: maxim-serdes: add MAX96717 driver Cosmin Tanislav
                   ` (6 subsequent siblings)
  23 siblings, 0 replies; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:31 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

These drivers are meant to be used as a common framework for Maxim
GMSL2/3 deserializer.

This framework enables support for the following new features across
all the chips:
 * Full Streams API support
 * .get_frame_desc()
 * .get_mbus_config()
 * I2C ATR
 * automatic GMSL link version negotiation
 * automatic stream id selection
 * automatic VC remapping
 * automatic pixel mode / tunnel mode selection
 * automatic double mode selection / data padding
 * logging of internal state and chip status registers via .log_status()
 * PHY modes
 * serializer pinctrl
 * TPG

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
---
 drivers/media/i2c/maxim-serdes/Makefile  |    2 +-
 drivers/media/i2c/maxim-serdes/max_des.c | 3180 ++++++++++++++++++++++
 drivers/media/i2c/maxim-serdes/max_des.h |  153 ++
 3 files changed, 3334 insertions(+), 1 deletion(-)
 create mode 100644 drivers/media/i2c/maxim-serdes/max_des.c
 create mode 100644 drivers/media/i2c/maxim-serdes/max_des.h

diff --git a/drivers/media/i2c/maxim-serdes/Makefile b/drivers/media/i2c/maxim-serdes/Makefile
index 17511cb033690..b54326a5c81b5 100644
--- a/drivers/media/i2c/maxim-serdes/Makefile
+++ b/drivers/media/i2c/maxim-serdes/Makefile
@@ -1,3 +1,3 @@
 # SPDX-License-Identifier: GPL-2.0
-max-serdes-objs := max_serdes.o max_ser.o
+max-serdes-objs := max_serdes.o max_ser.o max_des.o
 obj-$(CONFIG_VIDEO_MAXIM_SERDES) += max-serdes.o
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 0000000000000..0633a47ed1983
--- /dev/null
+++ b/drivers/media/i2c/maxim-serdes/max_des.c
@@ -0,0 +1,3180 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Maxim GMSL2 Deserializer 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 <linux/regulator/consumer.h>
+
+#include <media/mipi-csi2.h>
+#include <media/v4l2-ctrls.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_NUM_PHYS			4
+#define MAX_DES_NUM_LINKS			4
+#define MAX_DES_NUM_PIPES			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_serdes_source *sources;
+	u64 *streams_masks;
+
+	struct notifier_block i2c_nb;
+	struct v4l2_subdev sd;
+	struct v4l2_async_notifier nf;
+	struct v4l2_ctrl_handler ctrl_handler;
+
+	struct max_des_phy *unused_phy;
+};
+
+struct max_des_remap_context {
+	enum max_serdes_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_NUM_PIPES];
+	/* Mark the pipes in use. */
+	bool pipe_in_use[MAX_DES_NUM_PIPES];
+	/* Mark whether pipe has remapped VC ids. */
+	bool vc_ids_remapped[MAX_DES_NUM_PIPES];
+	/* Map between pipe VC ids and PHY VC ids. */
+	unsigned int vc_ids_map[MAX_DES_NUM_PIPES][MAX_DES_NUM_PHYS][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_NUM_PIPES][MAX_DES_NUM_PHYS];
+	/* Mark whether a PHY VC id has been mapped. */
+	unsigned long dst_vc_ids_masks[MAX_DES_NUM_PHYS];
+};
+
+struct max_des_mode_context {
+	bool phys_bpp8_shared_with_16[MAX_DES_NUM_PHYS];
+	bool pipes_bpp8_shared_with_16[MAX_DES_NUM_PIPES];
+	u32 phys_double_bpps[MAX_DES_NUM_PHYS];
+	u32 pipes_double_bpps[MAX_DES_NUM_PIPES];
+};
+
+struct max_des_route_hw {
+	struct max_serdes_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_serdes_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 struct max_des_priv *ctrl_to_priv(struct v4l2_ctrl_handler *handler)
+{
+	return container_of(handler, struct max_des_priv, ctrl_handler);
+}
+
+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_serdes_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_serdes_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_serdes_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_serdes_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_serdes_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_serdes_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_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_SERDES_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_SERDES_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_SERDES_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_SERDES_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_serdes_gmsl_mode mode)
+{
+	bool bpp8_not_shared_with_16_phys[MAX_DES_NUM_PHYS] = { 0 };
+	u32 undoubled_bpps_phys[MAX_DES_NUM_PHYS] = { 0 };
+	u32 bpps_pipes[MAX_DES_NUM_PIPES] = { 0 };
+	struct max_des *des = priv->des;
+	struct v4l2_subdev_route *route;
+	unsigned int i;
+	int ret;
+
+	if (mode != MAX_SERDES_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_serdes_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, doubled_bpp;
+		unsigned int pipe_id, phy_id;
+		struct max_des_route_hw hw;
+		u32 sink_bpps;
+
+		ret = max_des_route_to_hw(priv, state, route, &hw);
+		if (ret)
+			return ret;
+
+		ret = max_serdes_get_fd_bpp(&hw.entry, &bpp);
+		if (ret)
+			return ret;
+
+		sink_bpps = bpps_pipes[hw.pipe->index];
+
+		ret = max_serdes_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_serdes_vc_remap *vc_remaps,
+				unsigned int *num_vc_remaps, unsigned int src_vc_id,
+				unsigned int dst_vc_id)
+{
+	struct max_serdes_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_serdes_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_SERDES_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_SERDES_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_serdes_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)
+{
+	bool stream_id_usage[MAX_SERDES_STREAMS_NUM] = { 0 };
+	struct max_des *des = priv->des;
+	unsigned int i;
+	int ret;
+
+	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 == -EOPNOTSUPP) {
+			/*
+			 * Serializer does not support setting the stream id,
+			 * retrieve its hardcoded stream id.
+			 */
+			ret = max_ser_get_stream_id(hw.source->sd, &stream_id);
+		}
+
+		if (ret)
+			return ret;
+
+		if (stream_id_usage[stream_id] && des->ops->needs_unique_stream_id) {
+			dev_err(priv->dev, "Duplicate stream id %u\n", stream_id);
+			return -EINVAL;
+		}
+
+		ret = des->ops->set_pipe_stream_id(des, hw.pipe, stream_id);
+		if (ret)
+			return ret;
+
+		stream_id_usage[stream_id] = true;
+		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_SERDES_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 (context->mode == MAX_SERDES_GMSL_PIXEL_MODE &&
+			    des->ops->set_pipe_phy)
+				ret = des->ops->set_pipe_phy(des, pipe, phy);
+			else if (context->mode == MAX_SERDES_GMSL_TUNNEL_MODE &&
+				 des->ops->set_pipe_tunnel_phy)
+				ret = des->ops->set_pipe_tunnel_phy(des, pipe, phy);
+			else
+				ret = 0;
+
+			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 = { 0 };
+	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_SERDES_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_serdes_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_enable(des, phy, phy->enabled);
+		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_SERDES_GMSL_MIN;
+	*max = MAX_SERDES_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 = 0;
+
+	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_serdes_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_serdes_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_serdes_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_serdes_gmsl_mode_str(des->mode));
+	if (des->ops->set_tpg) {
+		const struct max_serdes_tpg_entry *entry = des->tpg_entry;
+
+		if (entry) {
+			v4l2_info(sd, "tpg: %ux%u@%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);
+		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_serdes_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);
+			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, "\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);
+			if (ret)
+				return ret;
+		}
+		v4l2_info(sd, "\n");
+	}
+
+	return 0;
+}
+
+static int max_des_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct max_des_priv *priv = ctrl_to_priv(ctrl->handler);
+	struct max_des *des = priv->des;
+
+	switch (ctrl->id) {
+	case V4L2_CID_TEST_PATTERN:
+		des->tpg_pattern = ctrl->val;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+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_serdes_tpg_entry *entry;
+	struct v4l2_mbus_framefmt fmt = { 0 };
+	int ret;
+
+	ret = max_serdes_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,
+				 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;
+
+	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_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;
+
+	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && des->active)
+		return -EBUSY;
+
+	return __max_des_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_tpg(struct max_des_priv *priv,
+			      struct v4l2_subdev_state *state,
+			      u64 *streams_masks)
+{
+	const struct max_serdes_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;
+	}
+
+	if (entry == des->tpg_entry)
+		return 0;
+
+	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_serdes_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_serdes_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_tpg(priv, state, streams_masks);
+	if (ret)
+		goto err_revert_links_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_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);
+}
+
+static int max_des_init_state(struct v4l2_subdev *sd,
+			      struct v4l2_subdev_state *state)
+{
+	struct v4l2_subdev_route routes[MAX_DES_NUM_LINKS] = { 0 };
+	struct v4l2_subdev_krouting routing = {
+		.routes = routes,
+	};
+	struct max_des_priv *priv = v4l2_get_subdevdata(sd);
+	struct max_des *des = priv->des;
+	struct max_des_phy *phy = NULL;
+	unsigned int stream = 0;
+	unsigned int i;
+
+	for (i = 0; i < des->ops->num_phys; i++) {
+		if (des->phys[i].enabled) {
+			phy = &des->phys[i];
+			break;
+		}
+	}
+
+	if (!phy)
+		return 0;
+
+	for (i = 0; i < des->ops->num_links; i++) {
+		struct max_des_link *link = &des->links[i];
+
+		if (!link->enabled)
+			continue;
+
+		routing.routes[routing.num_routes++] = (struct v4l2_subdev_route) {
+			.sink_pad = max_des_link_to_pad(des, link),
+			.sink_stream = 0,
+			.source_pad = max_des_phy_to_pad(des, phy),
+			.source_stream = stream,
+			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+		};
+		stream++;
+
+		/*
+		 * The Streams API is an experimental feature.
+		 * If multiple routes are provided here, userspace will not be
+		 * able to configure them unless the Streams API is enabled.
+		 * Provide a single route until it is enabled.
+		 */
+		break;
+	}
+
+	return __max_des_set_routing(sd, state, &routing);
+}
+
+#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_ctrl_ops max_des_ctrl_ops = {
+	.s_ctrl = max_des_s_ctrl,
+};
+
+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 v4l2_subdev_internal_ops max_des_internal_ops = {
+	.init_state = &max_des_init_state,
+};
+
+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_serdes_asc *asc = asc_to_max(base_asc);
+	struct max_serdes_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_serdes_asc *asc = asc_to_max(base_asc);
+	struct max_serdes_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_serdes_source *source;
+		struct max_serdes_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_serdes_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->internal_ops = &max_des_internal_ops;
+	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);
+
+	if (des->ops->tpg_patterns) {
+		v4l2_ctrl_handler_init(&priv->ctrl_handler, 1);
+		priv->sd.ctrl_handler = &priv->ctrl_handler;
+
+		v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler,
+					     &max_des_ctrl_ops,
+					     V4L2_CID_TEST_PATTERN,
+					     MAX_SERDES_TPG_PATTERN_MAX,
+					     ~des->ops->tpg_patterns,
+					     __ffs(des->ops->tpg_patterns),
+					     max_serdes_tpg_patterns);
+		if (priv->ctrl_handler.error) {
+			ret = priv->ctrl_handler.error;
+			goto err_free_ctrl;
+		}
+	}
+
+	ret = media_entity_pads_init(&sd->entity, num_pads, priv->pads);
+	if (ret)
+		goto err_free_ctrl;
+
+	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);
+err_free_ctrl:
+	v4l2_ctrl_handler_free(&priv->ctrl_handler);
+
+	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);
+	v4l2_ctrl_handler_free(&priv->ctrl_handler);
+}
+
+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_serdes_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;
+	}
+
+	if (v4l2_ep.nr_of_link_frequencies == 0)
+		link_frequency = MAX_DES_LINK_FREQUENCY_DEFAULT;
+	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_serdes_phys_configs *configs = &des->ops->phys_configs;
+	const struct max_serdes_phys_config *config =
+		&configs->configs[des->phys_config];
+
+	return config->lanes[phy->index];
+}
+EXPORT_SYMBOL_NS_GPL(max_des_phy_hw_data_lanes, "MAX_SERDES");
+
+static int max_des_find_phys_config(struct max_des_priv *priv)
+{
+	struct max_des *des = priv->des;
+	const struct max_serdes_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_serdes_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_serdes_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_NUM_PHYS)
+		return -E2BIG;
+
+	if (des->ops->num_pipes > MAX_DES_NUM_PIPES)
+		return -E2BIG;
+
+	if (des->ops->num_links > MAX_DES_NUM_LINKS)
+		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_NS_GPL(max_des_probe, "MAX_SERDES");
+
+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_NS_GPL(max_des_remove, "MAX_SERDES");
+
+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 0000000000000..dd5d37575710b
--- /dev/null
+++ b/drivers/media/i2c/maxim-serdes/max_des.h
@@ -0,0 +1,153 @@
+/* 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_serdes_gmsl_version version;
+	struct max_serdes_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_serdes_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;
+};
+
+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_serdes_phys_configs phys_configs;
+	struct max_serdes_tpg_entries tpg_entries;
+	enum max_serdes_gmsl_mode tpg_mode;
+	unsigned int tpg_patterns;
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	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);
+#endif
+	int (*log_status)(struct max_des *des);
+	int (*log_pipe_status)(struct max_des *des, struct max_des_pipe *pipe);
+	int (*log_phy_status)(struct max_des *des, struct max_des_phy *phy);
+	int (*set_enable)(struct max_des *des, bool enable);
+	int (*set_tpg)(struct max_des *des, const struct max_serdes_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_enable)(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_serdes_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_serdes_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_serdes_tpg_entry *tpg_entry;
+	enum max_serdes_tpg_pattern tpg_pattern;
+
+	unsigned int phys_config;
+	enum max_serdes_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
-- 
2.50.1


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

* [PATCH v6 18/24] media: i2c: maxim-serdes: add MAX96717 driver
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (16 preceding siblings ...)
  2025-07-16 19:31 ` [PATCH v6 17/24] media: i2c: add Maxim GMSL2/3 deserializer framework Cosmin Tanislav
@ 2025-07-16 19:31 ` Cosmin Tanislav
  2025-07-17 20:29   ` kernel test robot
                     ` (3 more replies)
  2025-07-16 19:31 ` [PATCH v6 19/24] media: i2c: maxim-serdes: add MAX96724 driver Cosmin Tanislav
                   ` (5 subsequent siblings)
  23 siblings, 4 replies; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:31 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

Add a new MAX96717 driver that also supports MAX9295A, MAX96717F and
MAX96793.

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

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
---
 drivers/media/i2c/maxim-serdes/Kconfig    |   16 +
 drivers/media/i2c/maxim-serdes/Makefile   |    1 +
 drivers/media/i2c/maxim-serdes/max96717.c | 1688 +++++++++++++++++++++
 3 files changed, 1705 insertions(+)
 create mode 100644 drivers/media/i2c/maxim-serdes/max96717.c

diff --git a/drivers/media/i2c/maxim-serdes/Kconfig b/drivers/media/i2c/maxim-serdes/Kconfig
index cae1d5a1293ee..648cb891eefef 100644
--- a/drivers/media/i2c/maxim-serdes/Kconfig
+++ b/drivers/media/i2c/maxim-serdes/Kconfig
@@ -14,3 +14,19 @@ config VIDEO_MAXIM_SERDES
 
 	  To compile this driver as a module, choose M here: the module
 	  will be called max_serdes.
+
+config VIDEO_MAX96717
+	tristate "Maxim MAX96717 Serializer support"
+	depends on 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 MAX9295A, MAX96717, MAX96717F,
+	  MAX96793 Serializers, which receive video on a MIPI CSI-2
+	  interface and output it on a GMSL2/3 link.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called max96717.
diff --git a/drivers/media/i2c/maxim-serdes/Makefile b/drivers/media/i2c/maxim-serdes/Makefile
index b54326a5c81b5..04abda6a5437a 100644
--- a/drivers/media/i2c/maxim-serdes/Makefile
+++ b/drivers/media/i2c/maxim-serdes/Makefile
@@ -1,3 +1,4 @@
 # 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
diff --git a/drivers/media/i2c/maxim-serdes/max96717.c b/drivers/media/i2c/maxim-serdes/max96717.c
new file mode 100644
index 0000000000000..d9544e90fd621
--- /dev/null
+++ b/drivers/media/i2c/maxim-serdes/max96717.c
@@ -0,0 +1,1688 @@
+// 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_RCLK_ALT			BIT(2)
+
+#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_CHECKER	0b01
+#define MAX96717_VTX29_PATGEN_MODE_GRADIENT	0b10
+
+#define MAX96717_VTX30_GRAD_INCR(p)		(0x1e6 + (p) * 0x43)
+#define MAX96717_VTX31_CHKR_A_L(p)		(0x1e7 + (p) * 0x43)
+#define MAX96717_VTX34_CHKR_B_L(p)		(0x1ea + (p) * 0x43)
+#define MAX96717_VTX37_CHKR_RPT_A(p)		(0x1ed + (p) * 0x43)
+#define MAX96717_VTX38_CHKR_RPT_B(p)		(0x1ee + (p) * 0x43)
+#define MAX96717_VTX39_CHKR_ALT(p)		(0x1ef + (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_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_RCLK_ALT_MFP			2
+#define MAX96717_RCLK_MFP			4
+#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, "mfp2", "mfp4");
+
+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_JITTER_COMPENSATION_EN	MAX96717_PINCTRL_X(1)
+#define MAX96717_PINCTRL_TX_ID			MAX96717_PINCTRL_X(2)
+#define MAX96717_PINCTRL_RX_ID			MAX96717_PINCTRL_X(3)
+#define MAX96717_PINCTRL_PULL_STRENGTH_HIGH	MAX96717_PINCTRL_X(4)
+#define MAX96717_PINCTRL_INPUT_VALUE		MAX96717_PINCTRL_X(5)
+#define MAX96717_PINCTRL_TX_EN			MAX96717_PINCTRL_X(6)
+#define MAX96717_PINCTRL_RX_EN			MAX96717_PINCTRL_X(7)
+
+static const struct pinconf_generic_params max96717_cfg_params[] = {
+	{ "maxim,jitter-compensation", MAX96717_PINCTRL_JITTER_COMPENSATION_EN, 0 },
+	{ "maxim,tx-id", MAX96717_PINCTRL_TX_ID, 0 },
+	{ "maxim,rx-id", MAX96717_PINCTRL_RX_ID, 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_TX_EN:
+		*mask = MAX96717_GPIO_A_GPIO_TX_EN;
+		*val = 0b1;
+		return 0;
+	case MAX96717_PINCTRL_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_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_RX_ID:
+		*reg = MAX96717_GPIO_C(offset);
+		*mask = MAX96717_GPIO_C_GPIO_RX_ID;
+		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_TX_EN:
+	case MAX96717_PINCTRL_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_TX_ID:
+	case MAX96717_PINCTRL_RX_ID:
+	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;
+	case MAX96717_PINCTRL_TX_ID:
+		*config = pinconf_to_config_packed(MAX96717_PINCTRL_TX_EN, 0);
+
+		ret = max96717_conf_pin_config_get(pctldev, offset, config);
+		if (ret)
+			return ret;
+
+		break;
+	case MAX96717_PINCTRL_RX_ID:
+		*config = pinconf_to_config_packed(MAX96717_PINCTRL_RX_EN, 0);
+
+		ret = max96717_conf_pin_config_get(pctldev, offset, config);
+		if (ret)
+			return ret;
+
+		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_TX_EN:
+	case MAX96717_PINCTRL_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_TX_ID:
+	case MAX96717_PINCTRL_RX_ID:
+	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_RX_EN, 0);
+		return max96717_conf_pin_config_set_one(priv, offset, config);
+	case MAX96717_PINCTRL_TX_ID:
+		config = pinconf_to_config_packed(MAX96717_PINCTRL_TX_EN, 1);
+		return max96717_conf_pin_config_set_one(priv, offset, config);
+	case MAX96717_PINCTRL_RX_ID:
+		config = pinconf_to_config_packed(MAX96717_PINCTRL_RX_EN, 1);
+		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)
+{
+	unsigned long config;
+	int ret;
+
+	config = pinconf_to_config_packed(PIN_CONFIG_SLEW_RATE,
+					  MAX96717_PIO_SLEW_FASTEST);
+	ret = max96717_conf_pin_config_set_one(priv, group, config);
+	if (ret)
+		return ret;
+
+	return regmap_assign_bits(priv->regmap, MAX96717_REG3,
+				  MAX96717_REG3_RCLK_ALT,
+				  group == MAX96717_RCLK_ALT_MFP);
+}
+
+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 __maybe_unused 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 __maybe_unused 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)
+{
+	struct max96717_priv *priv = ser_to_priv(ser);
+	unsigned int val;
+	int ret;
+
+	if (!(priv->info->modes & BIT(MAX_SERDES_GMSL_TUNNEL_MODE)))
+		return 0;
+
+	ret = regmap_read(priv->regmap, MAX96717_EXT23, &val);
+	if (ret)
+		return ret;
+
+	dev_info(priv->dev, "tun_pkt_cnt: %u\n", val);
+
+	return 0;
+}
+
+static int max96717_log_pipe_status(struct max_ser *ser,
+				    struct max_ser_pipe *pipe)
+{
+	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;
+
+	dev_info(priv->dev, "\tpclkdet: %u\n",
+		 !!(val & MAX96717_VIDEO_TX2_PCLKDET));
+
+	return 0;
+}
+
+static int max96717_log_phy_status(struct max_ser *ser,
+				   struct max_ser_phy *phy)
+{
+	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;
+
+	dev_info(priv->dev, "\tphy_pkt_cnt: %u\n", val);
+
+	ret = regmap_read(priv->regmap, MAX96717_EXT22, &val);
+	if (ret)
+		return ret;
+
+	dev_info(priv->dev, "\tcsi_pkt_cnt: %u\n", val);
+
+	ret = regmap_read(priv->regmap, MAX96717_EXT24, &val);
+	if (ret)
+		return ret;
+
+	dev_info(priv->dev, "\tphy_clk_cnt: %u\n", 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. */
+	for (i = 0, val = 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;
+
+	return regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_9,
+				  MAX96717_FRONTTOP_9_START_PORT(index, 1),
+				  phy_id == 1);
+}
+
+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;
+
+	return 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));
+}
+
+static int max96717_set_i2c_xlate(struct max_ser *ser, unsigned int i,
+				  struct max_serdes_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;
+
+	return regmap_update_bits(priv->regmap, MAX96717_I2C_3(i),
+				  MAX96717_I2C_3_DST,
+				  FIELD_PREP(MAX96717_I2C_3_DST, xlate->dst));
+}
+
+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_serdes_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, u32 clock,
+				unsigned int index)
+{
+	u8 pclk_src;
+
+	switch (clock) {
+	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;
+	case 0:
+		return 0;
+	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_mode(struct max96717_priv *priv, bool enable,
+				 unsigned int index)
+{
+	unsigned int patgen_mode;
+
+	switch (priv->ser.tpg_pattern) {
+	case MAX_SERDES_TPG_PATTERN_GRADIENT:
+		patgen_mode = MAX96717_VTX29_PATGEN_MODE_GRADIENT;
+		break;
+	case MAX_SERDES_TPG_PATTERN_CHECKERBOARD:
+		patgen_mode = MAX96717_VTX29_PATGEN_MODE_CHECKER;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return regmap_update_bits(priv->regmap, MAX96717_VTX29(index),
+				  MAX96717_VTX29_PATGEN_MODE,
+				  FIELD_PREP(MAX96717_VTX29_PATGEN_MODE,
+					     enable ? patgen_mode
+						    : MAX96717_VTX29_PATGEN_MODE_DISABLED));
+}
+
+static int max96717_set_tpg(struct max_ser *ser,
+			    const struct max_serdes_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_serdes_tpg_timings timings = { 0 };
+	int ret;
+
+	ret = max_serdes_get_tpg_timings(entry, &timings);
+	if (ret)
+		return ret;
+
+	ret = max96717_set_tpg_timings(priv, &timings, index);
+	if (ret)
+		return ret;
+
+	ret = max96717_set_tpg_clk(priv, timings.clock, index);
+	if (ret)
+		return ret;
+
+	return max96717_set_tpg_mode(priv, entry, index);
+}
+
+static const struct max_serdes_phys_config max96717_phys_configs[] = {
+	{ { 4 } },
+};
+
+static int max96717_init_tpg(struct max_ser *ser)
+{
+	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]);
+
+	const struct reg_sequence regs[] = {
+		{ MAX96717_VTX30_GRAD_INCR(index), MAX_SERDES_GRAD_INCR },
+		REG_SEQUENCE_3_LE(MAX96717_VTX31_CHKR_A_L(index),
+				  MAX_SERDES_CHECKER_COLOR_A),
+		REG_SEQUENCE_3_LE(MAX96717_VTX34_CHKR_B_L(index),
+				  MAX_SERDES_CHECKER_COLOR_B),
+		{ MAX96717_VTX37_CHKR_RPT_A(index), MAX_SERDES_CHECKER_SIZE },
+		{ MAX96717_VTX38_CHKR_RPT_B(index), MAX_SERDES_CHECKER_SIZE },
+		{ MAX96717_VTX39_CHKR_ALT(index), MAX_SERDES_CHECKER_SIZE },
+	};
+
+	return regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs));
+}
+
+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 max96717_init_tpg(ser);
+}
+
+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_serdes_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_SERDES_GMSL_PIXEL_MODE,
+	.tpg_patterns = BIT(MAX_SERDES_TPG_PATTERN_CHECKERBOARD) |
+			BIT(MAX_SERDES_TPG_PATTERN_GRADIENT),
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.reg_read = max96717_reg_read,
+	.reg_write = max96717_reg_write,
+#endif
+	.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_rclk;
+	bool is_alt;
+	u8 val;
+	u8 rclksel;
+};
+
+static const struct max96717_pll_predef_freq max96717_predef_freqs[] = {
+	{  6250000, true,  false, 0, 2 },
+	{ 12500000, true,  false, 0, 1 },
+	{ 13500000, false, true,  0, 3 },
+	{ 19200000, false, false, 0, 3 },
+	{ 24000000, false, true,  1, 3 },
+	{ 25000000, true,  false, 0, 0 },
+	{ 27000000, false, false, 1, 3 },
+	{ 37125000, false, false, 2, 3 },
+	{ 74250000, false, false, 3, 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)
+{
+	const struct max96717_pll_predef_freq *predef_freq;
+	struct max96717_priv *priv = clk_hw_to_priv(hw);
+	unsigned int val, idx;
+	int ret = 0;
+
+	idx = max96717_clk_find_best_index(priv, rate);
+	predef_freq = &max96717_predef_freqs[idx];
+
+	ret = regmap_update_bits(priv->regmap, MAX96717_REG3,
+				 MAX96717_REG3_RCLKSEL,
+				 FIELD_PREP(MAX96717_REG3_RCLKSEL,
+					    predef_freq->rclksel));
+	if (ret)
+		return ret;
+
+	val = FIELD_PREP(MAX96717_REF_VTG0_REFGEN_PREDEF_FREQ,
+			 predef_freq->val);
+
+	if (predef_freq->is_alt)
+		val |= MAX96717_REF_VTG0_REFGEN_PREDEF_FREQ_ALT;
+	if (!predef_freq->is_rclk)
+		val |= MAX96717_REF_VTG0_REFGEN_EN;
+
+	val |= MAX96717_REF_VTG0_REFGEN_RST;
+
+	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 };
+	int ret;
+
+	ret = max96717_mux_set_rclkout(priv, MAX96717_RCLK_MFP);
+	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_SERDES_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_SERDES_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_SERDES_GMSL_PIXEL_MODE) |
+		 BIT(MAX_SERDES_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_IMPORT_NS("MAX_SERDES");
+MODULE_DESCRIPTION("MAX96717 GMSL2 Serializer Driver");
+MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@analog.com>");
+MODULE_LICENSE("GPL");
-- 
2.50.1


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

* [PATCH v6 19/24] media: i2c: maxim-serdes: add MAX96724 driver
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (17 preceding siblings ...)
  2025-07-16 19:31 ` [PATCH v6 18/24] media: i2c: maxim-serdes: add MAX96717 driver Cosmin Tanislav
@ 2025-07-16 19:31 ` Cosmin Tanislav
  2025-07-17 12:44   ` Niklas Söderlund
  2025-07-17 20:39   ` kernel test robot
  2025-07-16 19:31 ` [PATCH v6 20/24] media: i2c: maxim-serdes: add MAX9296A driver Cosmin Tanislav
                   ` (4 subsequent siblings)
  23 siblings, 2 replies; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:31 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

Add a new MAX96724 driver that also supports MAX96712, MAX96724F
and MAX96724R.

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@gmail.com>
---
 drivers/media/i2c/maxim-serdes/Kconfig    |   11 +
 drivers/media/i2c/maxim-serdes/Makefile   |    1 +
 drivers/media/i2c/maxim-serdes/max96724.c | 1183 +++++++++++++++++++++
 3 files changed, 1195 insertions(+)
 create mode 100644 drivers/media/i2c/maxim-serdes/max96724.c

diff --git a/drivers/media/i2c/maxim-serdes/Kconfig b/drivers/media/i2c/maxim-serdes/Kconfig
index 648cb891eefef..2acd96cdbfa44 100644
--- a/drivers/media/i2c/maxim-serdes/Kconfig
+++ b/drivers/media/i2c/maxim-serdes/Kconfig
@@ -30,3 +30,14 @@ config VIDEO_MAX96717
 
 	  To compile this driver as a module, choose M here: the module
 	  will be called max96717.
+
+config VIDEO_MAX96724
+	tristate "Maxim MAX96724 Quad Deserializer support"
+	select VIDEO_MAXIM_SERDES
+	help
+	  This driver supports the Maxim MAX96712, MAX96724, MAX96724F,
+	  MAX96724R Quad Deserializers, which convert from four GMSL2
+	  links to up to four MIPI D-PHY or C-PHY outputs.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called max96724.
diff --git a/drivers/media/i2c/maxim-serdes/Makefile b/drivers/media/i2c/maxim-serdes/Makefile
index 04abda6a5437a..b6d5aebfaee13 100644
--- a/drivers/media/i2c/maxim-serdes/Makefile
+++ b/drivers/media/i2c/maxim-serdes/Makefile
@@ -2,3 +2,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
diff --git a/drivers/media/i2c/maxim-serdes/max96724.c b/drivers/media/i2c/maxim-serdes/max96724.c
new file mode 100644
index 0000000000000..3bc2080b4dc5f
--- /dev/null
+++ b/drivers/media/i2c/maxim-serdes/max96724.c
@@ -0,0 +1,1183 @@
+// 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_CHECKER	0b01
+#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_CHKR_COLOR_A_L			0x106e
+#define MAX96724_CHKR_COLOR_B_L			0x1071
+#define MAX96724_CHKR_RPT_A			0x1074
+#define MAX96724_CHKR_RPT_B			0x1075
+#define MAX96724_CHKR_ALT			0x1076
+
+#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);
+
+	return max96724_wait_for_device(priv);
+}
+
+static int __maybe_unused 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 __maybe_unused 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)
+{
+	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;
+
+	dev_info(priv->dev, "\tvideo_lock: %u\n",
+		 !!(val & MAX96724_VPRBS_VIDEO_LOCK));
+
+	mask = MAX96724_DET(index);
+
+	ret = regmap_read(priv->regmap, MAX96724_DE_DET, &val);
+	if (ret)
+		return ret;
+
+	dev_info(priv->dev, "\tde_det: %u\n", !!(val & mask));
+
+	ret = regmap_read(priv->regmap, MAX96724_HS_DET, &val);
+	if (ret)
+		return ret;
+
+	dev_info(priv->dev, "\ths_det: %u\n", !!(val & mask));
+
+	ret = regmap_read(priv->regmap, MAX96724_VS_DET, &val);
+	if (ret)
+		return ret;
+
+	dev_info(priv->dev, "\tvs_det: %u\n", !!(val & mask));
+
+	ret = regmap_read(priv->regmap, MAX96724_HS_POL, &val);
+	if (ret)
+		return ret;
+
+	dev_info(priv->dev, "\ths_pol: %u\n", !!(val & mask));
+
+	ret = regmap_read(priv->regmap, MAX96724_VS_POL, &val);
+	if (ret)
+		return ret;
+
+	dev_info(priv->dev, "\tvs_pol: %u\n", !!(val & mask));
+
+	return 0;
+}
+
+static int max96724_log_phy_status(struct max_des *des,
+				   struct max_des_phy *phy)
+{
+	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;
+
+	dev_info(priv->dev, "\tcsi2_pkt_cnt: %lu\n",
+		 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;
+
+	dev_info(priv->dev, "\tphy_pkt_cnt: %lu\n",
+		 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_serdes_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_tpg(struct max_des *des)
+{
+	const struct reg_sequence regs[] = {
+		{ MAX96724_GRAD_INCR, MAX_SERDES_GRAD_INCR },
+		REG_SEQUENCE_3_LE(MAX96724_CHKR_COLOR_A_L,
+				  MAX_SERDES_CHECKER_COLOR_A),
+		REG_SEQUENCE_3_LE(MAX96724_CHKR_COLOR_B_L,
+				  MAX_SERDES_CHECKER_COLOR_B),
+		{ MAX96724_CHKR_RPT_A, MAX_SERDES_CHECKER_SIZE },
+		{ MAX96724_CHKR_RPT_B, MAX_SERDES_CHECKER_SIZE },
+		{ MAX96724_CHKR_ALT, MAX_SERDES_CHECKER_SIZE },
+	};
+	struct max96724_priv *priv = des_to_priv(des);
+
+	return regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs));
+}
+
+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;
+
+	return max96724_init_tpg(des);
+}
+
+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. */
+	for (i = 0, val = 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. */
+	return regmap_set_bits(priv->regmap, MAX96724_DPLL_0(index),
+			       MAX96724_DPLL_0_CONFIG_SOFT_RST_N);
+}
+
+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;
+
+	return regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX51(index),
+				  MAX96724_MIPI_TX51_ALT2_MEM_MAP_8,
+				  mode->alt2_mem_map8);
+}
+
+static int max96724_set_phy_enable(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_serdes_gmsl_version version)
+{
+	struct max96724_priv *priv = des_to_priv(des);
+	unsigned int index = link->index;
+	unsigned int val;
+
+	if (version == MAX_SERDES_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_serdes_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, u32 clock)
+{
+	bool patgen_clk_src = 0;
+	u8 pclk_src;
+	int ret;
+
+	switch (clock) {
+	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;
+	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, 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_mode(struct max96724_priv *priv, bool enable)
+{
+	unsigned int patgen_mode;
+
+	switch (priv->des.tpg_pattern) {
+	case MAX_SERDES_TPG_PATTERN_GRADIENT:
+		patgen_mode = MAX96724_PATGEN_1_PATGEN_MODE_GRADIENT;
+		break;
+	case MAX_SERDES_TPG_PATTERN_CHECKERBOARD:
+		patgen_mode = MAX96724_PATGEN_1_PATGEN_MODE_CHECKER;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return regmap_update_bits(priv->regmap, MAX96724_PATGEN_1,
+				  MAX96724_PATGEN_1_PATGEN_MODE,
+				  FIELD_PREP(MAX96724_PATGEN_1_PATGEN_MODE,
+					     enable ? patgen_mode
+						    : MAX96724_PATGEN_1_PATGEN_MODE_DISABLED));
+}
+
+static int max96724_set_tpg(struct max_des *des,
+			    const struct max_serdes_tpg_entry *entry)
+{
+	struct max96724_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 = max96724_set_tpg_timings(priv, &timings);
+	if (ret)
+		return ret;
+
+	ret = max96724_set_tpg_clk(priv, timings.clock);
+	if (ret)
+		return ret;
+
+	ret = max96724_set_tpg_mode(priv, entry);
+	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_serdes_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_SERDES_GMSL_PIXEL_MODE,
+	.tpg_patterns = BIT(MAX_SERDES_TPG_PATTERN_CHECKERBOARD) |
+			BIT(MAX_SERDES_TPG_PATTERN_GRADIENT),
+	.use_atr = true,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.reg_read = max96724_reg_read,
+	.reg_write = max96724_reg_write,
+#endif
+	.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_enable = max96724_set_phy_enable,
+	.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_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_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_SERDES_GMSL_2_3GBPS),
+	.modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) |
+		 BIT(MAX_SERDES_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_SERDES_GMSL_2_3GBPS) |
+		    BIT(MAX_SERDES_GMSL_2_6GBPS),
+	.modes = BIT(MAX_SERDES_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;
+	ops->set_pipe_tunnel_phy = priv->info->set_pipe_tunnel_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	= max96724_of_table,
+	},
+	.probe = max96724_probe,
+	.remove = max96724_remove,
+};
+
+module_i2c_driver(max96724_i2c_driver);
+
+MODULE_IMPORT_NS("MAX_SERDES");
+MODULE_DESCRIPTION("Maxim MAX96724 Quad GMSL2 Deserializer Driver");
+MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@analog.com>");
+MODULE_LICENSE("GPL");
-- 
2.50.1


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

* [PATCH v6 20/24] media: i2c: maxim-serdes: add MAX9296A driver
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (18 preceding siblings ...)
  2025-07-16 19:31 ` [PATCH v6 19/24] media: i2c: maxim-serdes: add MAX96724 driver Cosmin Tanislav
@ 2025-07-16 19:31 ` Cosmin Tanislav
  2025-07-18  5:23   ` kernel test robot
  2025-07-16 19:31 ` [PATCH v6 21/24] arm64: defconfig: disable deprecated MAX96712 driver Cosmin Tanislav
                   ` (3 subsequent siblings)
  23 siblings, 1 reply; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:31 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

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@gmail.com>
---
 drivers/media/i2c/maxim-serdes/Kconfig    |   12 +
 drivers/media/i2c/maxim-serdes/Makefile   |    1 +
 drivers/media/i2c/maxim-serdes/max9296a.c | 1344 +++++++++++++++++++++
 3 files changed, 1357 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 2acd96cdbfa44..05868624f3b63 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 b6d5aebfaee13..ae306bc33bfb9 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 0000000000000..7beaac97b2c60
--- /dev/null
+++ b/drivers/media/i2c/maxim-serdes/max9296a.c
@@ -0,0 +1,1344 @@
+// 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;
+	const struct reg_sequence *rlms_adjust_sequence;
+	unsigned int rlms_adjust_sequence_len;
+	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;
+};
+
+#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 __maybe_unused 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 __maybe_unused 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)
+{
+	struct max9296a_priv *priv = des_to_priv(des);
+	int ret;
+
+	if (priv->info->rlms_adjust_sequence) {
+		ret = regmap_multi_reg_write(priv->regmap,
+					     priv->info->rlms_adjust_sequence,
+					     priv->info->rlms_adjust_sequence_len);
+		if (ret)
+			return ret;
+	}
+
+	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_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_12GBPS;
+	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_12GBPS)
+		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_12GBPS)))
+		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),
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.reg_read = max9296a_reg_read,
+	.reg_write = max9296a_reg_write,
+#endif
+	.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,
+	.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,
+};
+
+/*
+ * 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 max96714_rlms_reg_sequence[] = {
+	{ MAX9296A_RLMS3E(0), 0xfd },
+	{ MAX9296A_RLMS3F(0), 0x3d },
+	{ MAX9296A_RLMS49(0), 0xf5 },
+	{ MAX9296A_RLMS7E(0), 0xa8 },
+	{ MAX9296A_RLMS7F(0), 0x68 },
+	{ MAX9296A_RLMSA3(0), 0x30 },
+	{ MAX9296A_RLMSA5(0), 0x70 },
+	{ MAX9296A_RLMSD8(0), 0x07 },
+};
+
+static const struct max9296a_chip_info max96714_info = {
+	.ops = &max96714_ops,
+	.max_register = 0x5011,
+	.polarity_on_physical_lanes = true,
+	.supports_phy_log = true,
+	.rlms_adjust_sequence = max96714_rlms_reg_sequence,
+	.rlms_adjust_sequence_len = ARRAY_SIZE(max96714_rlms_reg_sequence),
+	.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,
+	.rlms_adjust_sequence = max96714_rlms_reg_sequence,
+	.rlms_adjust_sequence_len = ARRAY_SIZE(max96714_rlms_reg_sequence),
+	.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_12GBPS),
+	.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_IMPORT_NS("MAX_SERDES");
+MODULE_DESCRIPTION("Maxim MAX9296A Quad GMSL2 Deserializer Driver");
+MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@analog.com>");
+MODULE_LICENSE("GPL");
-- 
2.50.1


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

* [PATCH v6 21/24] arm64: defconfig: disable deprecated MAX96712 driver
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (19 preceding siblings ...)
  2025-07-16 19:31 ` [PATCH v6 20/24] media: i2c: maxim-serdes: add MAX9296A driver Cosmin Tanislav
@ 2025-07-16 19:31 ` Cosmin Tanislav
  2025-07-16 19:31 ` [PATCH v6 22/24] staging: media: remove " Cosmin Tanislav
                   ` (2 subsequent siblings)
  23 siblings, 0 replies; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:31 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

The staging MAX96712 driver will be removed as its functionality has
been moved to the MAX96724 driver which makes use of the Maxim
GMSL2/3 deserializer framework.

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
---
 arch/arm64/configs/defconfig | 1 -
 1 file changed, 1 deletion(-)

diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index 897fc686e6a91..6f4254f562da3 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -1300,7 +1300,6 @@ CONFIG_GREYBUS=m
 CONFIG_GREYBUS_BEAGLEPLAY=m
 CONFIG_STAGING=y
 CONFIG_STAGING_MEDIA=y
-CONFIG_VIDEO_MAX96712=m
 CONFIG_VIDEO_MESON_VDEC=m
 CONFIG_SND_BCM2835=m
 CONFIG_CHROME_PLATFORMS=y
-- 
2.50.1


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

* [PATCH v6 22/24] staging: media: remove MAX96712 driver
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (20 preceding siblings ...)
  2025-07-16 19:31 ` [PATCH v6 21/24] arm64: defconfig: disable deprecated MAX96712 driver Cosmin Tanislav
@ 2025-07-16 19:31 ` Cosmin Tanislav
  2025-07-17 12:36   ` Niklas Söderlund
  2025-07-16 19:31 ` [PATCH v6 23/24] media: i2c: remove MAX96717 driver Cosmin Tanislav
  2025-07-16 19:31 ` [PATCH v6 24/24] media: i2c: remove MAX96714 driver Cosmin Tanislav
  23 siblings, 1 reply; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:31 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

Remove the staging MAX96712 driver.
Its functionality has been moved to the MAX96724 driver which makes use
of the Maxim GMSL2/3 deserializer framework.

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
---
 MAINTAINERS                               |   1 -
 drivers/staging/media/Kconfig             |   2 -
 drivers/staging/media/Makefile            |   1 -
 drivers/staging/media/max96712/Kconfig    |  14 -
 drivers/staging/media/max96712/Makefile   |   2 -
 drivers/staging/media/max96712/max96712.c | 487 ----------------------
 6 files changed, 507 deletions(-)
 delete mode 100644 drivers/staging/media/max96712/Kconfig
 delete mode 100644 drivers/staging/media/max96712/Makefile
 delete mode 100644 drivers/staging/media/max96712/max96712.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 0c75a5c195c28..ff0c7fe22ce6e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14751,7 +14751,6 @@ M:	Cosmin Tanislav <cosmin.tanislav@analog.com>
 L:	linux-media@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
-F:	drivers/staging/media/max96712/max96712.c
 
 MAX96714 GMSL2 DESERIALIZER DRIVER
 M:	Julien Massot <julien.massot@collabora.com>
diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig
index ab250c89cd4d4..edcec810433de 100644
--- a/drivers/staging/media/Kconfig
+++ b/drivers/staging/media/Kconfig
@@ -30,8 +30,6 @@ source "drivers/staging/media/ipu3/Kconfig"
 
 source "drivers/staging/media/ipu7/Kconfig"
 
-source "drivers/staging/media/max96712/Kconfig"
-
 source "drivers/staging/media/meson/vdec/Kconfig"
 
 source "drivers/staging/media/starfive/Kconfig"
diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile
index 4a073938b2b2d..e38421c81d0e7 100644
--- a/drivers/staging/media/Makefile
+++ b/drivers/staging/media/Makefile
@@ -2,7 +2,6 @@
 obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE)	+= deprecated/atmel/
 obj-$(CONFIG_INTEL_ATOMISP)     += atomisp/
 obj-$(CONFIG_VIDEO_IMX_MEDIA)	+= imx/
-obj-$(CONFIG_VIDEO_MAX96712)	+= max96712/
 obj-$(CONFIG_VIDEO_MESON_VDEC)	+= meson/vdec/
 obj-$(CONFIG_VIDEO_STARFIVE_CAMSS)	+= starfive/
 obj-$(CONFIG_VIDEO_SUNXI)	+= sunxi/
diff --git a/drivers/staging/media/max96712/Kconfig b/drivers/staging/media/max96712/Kconfig
deleted file mode 100644
index 117fadf81bd08..0000000000000
--- a/drivers/staging/media/max96712/Kconfig
+++ /dev/null
@@ -1,14 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0
-config VIDEO_MAX96712
-	tristate "Maxim MAX96712 Quad GMSL2 Deserializer support"
-	depends on I2C
-	depends on OF_GPIO
-	depends on VIDEO_DEV
-	select V4L2_FWNODE
-	select VIDEO_V4L2_SUBDEV_API
-	select MEDIA_CONTROLLER
-	help
-	  This driver supports the Maxim MAX96712 Quad GMSL2 Deserializer.
-
-	  To compile this driver as a module, choose M here: the
-	  module will be called max96712.
diff --git a/drivers/staging/media/max96712/Makefile b/drivers/staging/media/max96712/Makefile
deleted file mode 100644
index 70c1974ce3f05..0000000000000
--- a/drivers/staging/media/max96712/Makefile
+++ /dev/null
@@ -1,2 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0
-obj-$(CONFIG_VIDEO_MAX96712) += max96712.o
diff --git a/drivers/staging/media/max96712/max96712.c b/drivers/staging/media/max96712/max96712.c
deleted file mode 100644
index 0751b2e048958..0000000000000
--- a/drivers/staging/media/max96712/max96712.c
+++ /dev/null
@@ -1,487 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Maxim MAX96712 Quad GMSL2 Deserializer Driver
- *
- * Copyright (C) 2021 Renesas Electronics Corporation
- * Copyright (C) 2021 Niklas Söderlund
- */
-
-#include <linux/delay.h>
-#include <linux/i2c.h>
-#include <linux/module.h>
-#include <linux/of_graph.h>
-#include <linux/regmap.h>
-
-#include <media/v4l2-ctrls.h>
-#include <media/v4l2-fwnode.h>
-#include <media/v4l2-subdev.h>
-
-#define DEBUG_EXTRA_REG			0x09
-#define DEBUG_EXTRA_PCLK_25MHZ		0x00
-#define DEBUG_EXTRA_PCLK_75MHZ		0x01
-
-enum max96712_pattern {
-	MAX96712_PATTERN_CHECKERBOARD = 0,
-	MAX96712_PATTERN_GRADIENT,
-};
-
-struct max96712_info {
-	unsigned int dpllfreq;
-	bool have_debug_extra;
-};
-
-struct max96712_priv {
-	struct i2c_client *client;
-	struct regmap *regmap;
-	struct gpio_desc *gpiod_pwdn;
-
-	const struct max96712_info *info;
-
-	bool cphy;
-	struct v4l2_mbus_config_mipi_csi2 mipi;
-
-	struct v4l2_subdev sd;
-	struct v4l2_ctrl_handler ctrl_handler;
-	struct media_pad pads[1];
-
-	enum max96712_pattern pattern;
-};
-
-static int max96712_write(struct max96712_priv *priv, unsigned int reg, u8 val)
-{
-	int ret;
-
-	ret = regmap_write(priv->regmap, reg, val);
-	if (ret)
-		dev_err(&priv->client->dev, "write 0x%04x failed\n", reg);
-
-	return ret;
-}
-
-static int max96712_update_bits(struct max96712_priv *priv, unsigned int reg,
-				u8 mask, u8 val)
-{
-	int ret;
-
-	ret = regmap_update_bits(priv->regmap, reg, mask, val);
-	if (ret)
-		dev_err(&priv->client->dev, "update 0x%04x failed\n", reg);
-
-	return ret;
-}
-
-static int max96712_write_bulk(struct max96712_priv *priv, unsigned int reg,
-			       const void *val, size_t val_count)
-{
-	int ret;
-
-	ret = regmap_bulk_write(priv->regmap, reg, val, val_count);
-	if (ret)
-		dev_err(&priv->client->dev, "bulk write 0x%04x failed\n", reg);
-
-	return ret;
-}
-
-static int max96712_write_bulk_value(struct max96712_priv *priv,
-				     unsigned int reg, unsigned int val,
-				     size_t val_count)
-{
-	unsigned int i;
-	u8 values[4];
-
-	for (i = 1; i <= val_count; i++)
-		values[i - 1] = (val >> ((val_count - i) * 8)) & 0xff;
-
-	return max96712_write_bulk(priv, reg, &values, val_count);
-}
-
-static void max96712_reset(struct max96712_priv *priv)
-{
-	max96712_update_bits(priv, 0x13, 0x40, 0x40);
-	msleep(20);
-}
-
-static void max96712_mipi_enable(struct max96712_priv *priv, bool enable)
-{
-	if (enable) {
-		max96712_update_bits(priv, 0x40b, 0x02, 0x02);
-		max96712_update_bits(priv, 0x8a0, 0x80, 0x80);
-	} else {
-		max96712_update_bits(priv, 0x8a0, 0x80, 0x00);
-		max96712_update_bits(priv, 0x40b, 0x02, 0x00);
-	}
-}
-
-static void max96712_mipi_configure(struct max96712_priv *priv)
-{
-	unsigned int i;
-	u8 phy5 = 0;
-
-	max96712_mipi_enable(priv, false);
-
-	/* Select 2x4 mode. */
-	max96712_write(priv, 0x8a0, 0x04);
-
-	/* TODO: Add support for 2-lane and 1-lane configurations. */
-	if (priv->cphy) {
-		/* Configure a 3-lane C-PHY using PHY0 and PHY1. */
-		max96712_write(priv, 0x94a, 0xa0);
-
-		/* Configure C-PHY timings. */
-		max96712_write(priv, 0x8ad, 0x3f);
-		max96712_write(priv, 0x8ae, 0x7d);
-	} else {
-		/* Configure a 4-lane D-PHY using PHY0 and PHY1. */
-		max96712_write(priv, 0x94a, 0xc0);
-	}
-
-	/* Configure lane mapping for PHY0 and PHY1. */
-	/* TODO: Add support for lane swapping. */
-	max96712_write(priv, 0x8a3, 0xe4);
-
-	/* Configure lane polarity for PHY0 and PHY1. */
-	for (i = 0; i < priv->mipi.num_data_lanes + 1; i++)
-		if (priv->mipi.lane_polarities[i])
-			phy5 |= BIT(i == 0 ? 5 : i < 3 ? i - 1 : i);
-	max96712_write(priv, 0x8a5, phy5);
-
-	/* Set link frequency for PHY0 and PHY1. */
-	max96712_update_bits(priv, 0x415, 0x3f,
-			     ((priv->info->dpllfreq / 100) & 0x1f) | BIT(5));
-	max96712_update_bits(priv, 0x418, 0x3f,
-			     ((priv->info->dpllfreq / 100) & 0x1f) | BIT(5));
-
-	/* Enable PHY0 and PHY1 */
-	max96712_update_bits(priv, 0x8a2, 0xf0, 0x30);
-}
-
-static void max96712_pattern_enable(struct max96712_priv *priv, bool enable)
-{
-	const u32 h_active = 1920;
-	const u32 h_fp = 88;
-	const u32 h_sw = 44;
-	const u32 h_bp = 148;
-	const u32 h_tot = h_active + h_fp + h_sw + h_bp;
-
-	const u32 v_active = 1080;
-	const u32 v_fp = 4;
-	const u32 v_sw = 5;
-	const u32 v_bp = 36;
-	const u32 v_tot = v_active + v_fp + v_sw + v_bp;
-
-	if (!enable) {
-		max96712_write(priv, 0x1051, 0x00);
-		return;
-	}
-
-	/* Set PCLK to 75MHz if device have DEBUG_EXTRA register. */
-	if (priv->info->have_debug_extra)
-		max96712_write(priv, DEBUG_EXTRA_REG, DEBUG_EXTRA_PCLK_75MHZ);
-
-	/* Configure Video Timing Generator for 1920x1080 @ 30 fps. */
-	max96712_write_bulk_value(priv, 0x1052, 0, 3);
-	max96712_write_bulk_value(priv, 0x1055, v_sw * h_tot, 3);
-	max96712_write_bulk_value(priv, 0x1058,
-				  (v_active + v_fp + + v_bp) * h_tot, 3);
-	max96712_write_bulk_value(priv, 0x105b, 0, 3);
-	max96712_write_bulk_value(priv, 0x105e, h_sw, 2);
-	max96712_write_bulk_value(priv, 0x1060, h_active + h_fp + h_bp, 2);
-	max96712_write_bulk_value(priv, 0x1062, v_tot, 2);
-	max96712_write_bulk_value(priv, 0x1064,
-				  h_tot * (v_sw + v_bp) + (h_sw + h_bp), 3);
-	max96712_write_bulk_value(priv, 0x1067, h_active, 2);
-	max96712_write_bulk_value(priv, 0x1069, h_fp + h_sw + h_bp, 2);
-	max96712_write_bulk_value(priv, 0x106b, v_active, 2);
-
-	/* Generate VS, HS and DE in free-running mode. */
-	max96712_write(priv, 0x1050, 0xfb);
-
-	/* Configure Video Pattern Generator. */
-	if (priv->pattern == MAX96712_PATTERN_CHECKERBOARD) {
-		/* Set checkerboard pattern size. */
-		max96712_write(priv, 0x1074, 0x3c);
-		max96712_write(priv, 0x1075, 0x3c);
-		max96712_write(priv, 0x1076, 0x3c);
-
-		/* Set checkerboard pattern colors. */
-		max96712_write_bulk_value(priv, 0x106e, 0xfecc00, 3);
-		max96712_write_bulk_value(priv, 0x1071, 0x006aa7, 3);
-
-		/* Generate checkerboard pattern. */
-		max96712_write(priv, 0x1051, 0x10);
-	} else {
-		/* Set gradient increment. */
-		max96712_write(priv, 0x106d, 0x10);
-
-		/* Generate gradient pattern. */
-		max96712_write(priv, 0x1051, 0x20);
-	}
-}
-
-static int max96712_s_stream(struct v4l2_subdev *sd, int enable)
-{
-	struct max96712_priv *priv = v4l2_get_subdevdata(sd);
-
-	if (enable) {
-		max96712_pattern_enable(priv, true);
-		max96712_mipi_enable(priv, true);
-	} else {
-		max96712_mipi_enable(priv, false);
-		max96712_pattern_enable(priv, false);
-	}
-
-	return 0;
-}
-
-static const struct v4l2_subdev_video_ops max96712_video_ops = {
-	.s_stream = max96712_s_stream,
-};
-
-static int max96712_init_state(struct v4l2_subdev *sd,
-			       struct v4l2_subdev_state *state)
-{
-	static const struct v4l2_mbus_framefmt default_fmt = {
-		.width          = 1920,
-		.height         = 1080,
-		.code           = MEDIA_BUS_FMT_RGB888_1X24,
-		.colorspace     = V4L2_COLORSPACE_SRGB,
-		.field          = V4L2_FIELD_NONE,
-		.ycbcr_enc      = V4L2_YCBCR_ENC_DEFAULT,
-		.quantization   = V4L2_QUANTIZATION_DEFAULT,
-		.xfer_func      = V4L2_XFER_FUNC_DEFAULT,
-	};
-	struct v4l2_mbus_framefmt *fmt;
-
-	fmt = v4l2_subdev_state_get_format(state, 0);
-	*fmt = default_fmt;
-
-	return 0;
-}
-
-static const struct v4l2_subdev_internal_ops max96712_internal_ops = {
-	.init_state = max96712_init_state,
-};
-
-static const struct v4l2_subdev_pad_ops max96712_pad_ops = {
-	.get_fmt = v4l2_subdev_get_fmt,
-	.set_fmt = v4l2_subdev_get_fmt,
-};
-
-static const struct v4l2_subdev_ops max96712_subdev_ops = {
-	.video = &max96712_video_ops,
-	.pad = &max96712_pad_ops,
-};
-
-static const char * const max96712_test_pattern[] = {
-	"Checkerboard",
-	"Gradient",
-};
-
-static int max96712_s_ctrl(struct v4l2_ctrl *ctrl)
-{
-	struct max96712_priv *priv =
-		container_of(ctrl->handler, struct max96712_priv, ctrl_handler);
-
-	switch (ctrl->id) {
-	case V4L2_CID_TEST_PATTERN:
-		priv->pattern = ctrl->val ?
-			MAX96712_PATTERN_GRADIENT :
-			MAX96712_PATTERN_CHECKERBOARD;
-		break;
-	}
-	return 0;
-}
-
-static const struct v4l2_ctrl_ops max96712_ctrl_ops = {
-	.s_ctrl = max96712_s_ctrl,
-};
-
-static int max96712_v4l2_register(struct max96712_priv *priv)
-{
-	long pixel_rate;
-	int ret;
-
-	priv->sd.internal_ops = &max96712_internal_ops;
-	v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96712_subdev_ops);
-	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
-	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
-
-	v4l2_ctrl_handler_init(&priv->ctrl_handler, 2);
-
-	/*
-	 * TODO: Once V4L2_CID_LINK_FREQ is changed from a menu control to an
-	 * INT64 control it should be used here instead of V4L2_CID_PIXEL_RATE.
-	 */
-	pixel_rate = priv->info->dpllfreq / priv->mipi.num_data_lanes * 1000000;
-	v4l2_ctrl_new_std(&priv->ctrl_handler, NULL, V4L2_CID_PIXEL_RATE,
-			  pixel_rate, pixel_rate, 1, pixel_rate);
-
-	v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler, &max96712_ctrl_ops,
-				     V4L2_CID_TEST_PATTERN,
-				     ARRAY_SIZE(max96712_test_pattern) - 1,
-				     0, 0, max96712_test_pattern);
-
-	priv->sd.ctrl_handler = &priv->ctrl_handler;
-	ret = priv->ctrl_handler.error;
-	if (ret)
-		goto error;
-
-	priv->pads[0].flags = MEDIA_PAD_FL_SOURCE;
-	ret = media_entity_pads_init(&priv->sd.entity, 1, priv->pads);
-	if (ret)
-		goto error;
-
-	v4l2_set_subdevdata(&priv->sd, priv);
-
-	priv->sd.state_lock = priv->ctrl_handler.lock;
-	ret = v4l2_subdev_init_finalize(&priv->sd);
-	if (ret)
-		goto error;
-
-	ret = v4l2_async_register_subdev(&priv->sd);
-	if (ret < 0) {
-		dev_err(&priv->client->dev, "Unable to register subdevice\n");
-		goto error;
-	}
-
-	return 0;
-error:
-	v4l2_ctrl_handler_free(&priv->ctrl_handler);
-
-	return ret;
-}
-
-static int max96712_parse_dt(struct max96712_priv *priv)
-{
-	struct fwnode_handle *ep;
-	struct v4l2_fwnode_endpoint v4l2_ep = {
-		.bus_type = V4L2_MBUS_UNKNOWN,
-	};
-	unsigned int supported_lanes;
-	int ret;
-
-	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(&priv->client->dev), 4,
-					     0, 0);
-	if (!ep) {
-		dev_err(&priv->client->dev, "Not connected to subdevice\n");
-		return -EINVAL;
-	}
-
-	ret = v4l2_fwnode_endpoint_parse(ep, &v4l2_ep);
-	fwnode_handle_put(ep);
-	if (ret) {
-		dev_err(&priv->client->dev, "Could not parse v4l2 endpoint\n");
-		return -EINVAL;
-	}
-
-	switch (v4l2_ep.bus_type) {
-	case V4L2_MBUS_CSI2_DPHY:
-		supported_lanes = 4;
-		priv->cphy = false;
-		break;
-	case V4L2_MBUS_CSI2_CPHY:
-		supported_lanes = 3;
-		priv->cphy = true;
-		break;
-	default:
-		dev_err(&priv->client->dev, "Unsupported bus-type %u\n",
-			v4l2_ep.bus_type);
-		return -EINVAL;
-	}
-
-	if (v4l2_ep.bus.mipi_csi2.num_data_lanes != supported_lanes) {
-		dev_err(&priv->client->dev, "Only %u data lanes supported\n",
-			supported_lanes);
-		return -EINVAL;
-	}
-
-	priv->mipi = v4l2_ep.bus.mipi_csi2;
-
-	return 0;
-}
-
-static const struct regmap_config max96712_i2c_regmap = {
-	.reg_bits = 16,
-	.val_bits = 8,
-	.max_register = 0x1f00,
-};
-
-static int max96712_probe(struct i2c_client *client)
-{
-	struct max96712_priv *priv;
-	int ret;
-
-	priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
-	if (!priv)
-		return -ENOMEM;
-
-	priv->info = of_device_get_match_data(&client->dev);
-
-	priv->client = client;
-
-	priv->regmap = devm_regmap_init_i2c(client, &max96712_i2c_regmap);
-	if (IS_ERR(priv->regmap))
-		return PTR_ERR(priv->regmap);
-
-	priv->gpiod_pwdn = devm_gpiod_get_optional(&client->dev, "enable",
-						   GPIOD_OUT_HIGH);
-	if (IS_ERR(priv->gpiod_pwdn))
-		return PTR_ERR(priv->gpiod_pwdn);
-
-	gpiod_set_consumer_name(priv->gpiod_pwdn, "max96712-pwdn");
-	gpiod_set_value_cansleep(priv->gpiod_pwdn, 1);
-
-	if (priv->gpiod_pwdn)
-		usleep_range(4000, 5000);
-
-	max96712_reset(priv);
-
-	ret = max96712_parse_dt(priv);
-	if (ret)
-		return ret;
-
-	max96712_mipi_configure(priv);
-
-	return max96712_v4l2_register(priv);
-}
-
-static void max96712_remove(struct i2c_client *client)
-{
-	struct v4l2_subdev *sd = i2c_get_clientdata(client);
-	struct max96712_priv *priv = container_of(sd, struct max96712_priv, sd);
-
-	v4l2_async_unregister_subdev(&priv->sd);
-
-	gpiod_set_value_cansleep(priv->gpiod_pwdn, 0);
-}
-
-static const struct max96712_info max96712_info_max96712 = {
-	.dpllfreq = 1000,
-	.have_debug_extra = true,
-};
-
-static const struct max96712_info max96712_info_max96724 = {
-	.dpllfreq = 1200,
-};
-
-static const struct of_device_id max96712_of_table[] = {
-	{ .compatible = "maxim,max96712", .data = &max96712_info_max96712 },
-	{ .compatible = "maxim,max96724", .data = &max96712_info_max96724 },
-	{ /* sentinel */ }
-};
-MODULE_DEVICE_TABLE(of, max96712_of_table);
-
-static struct i2c_driver max96712_i2c_driver = {
-	.driver	= {
-		.name = "max96712",
-		.of_match_table	= of_match_ptr(max96712_of_table),
-	},
-	.probe = max96712_probe,
-	.remove = max96712_remove,
-};
-
-module_i2c_driver(max96712_i2c_driver);
-
-MODULE_DESCRIPTION("Maxim MAX96712 Quad GMSL2 Deserializer Driver");
-MODULE_AUTHOR("Niklas Söderlund <niklas.soderlund@ragnatech.se>");
-MODULE_LICENSE("GPL");
-- 
2.50.1


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

* [PATCH v6 23/24] media: i2c: remove MAX96717 driver
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (21 preceding siblings ...)
  2025-07-16 19:31 ` [PATCH v6 22/24] staging: media: remove " Cosmin Tanislav
@ 2025-07-16 19:31 ` Cosmin Tanislav
  2025-07-16 19:31 ` [PATCH v6 24/24] media: i2c: remove MAX96714 driver Cosmin Tanislav
  23 siblings, 0 replies; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:31 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

The previous MAX96717 driver has been removed and its functionality has
been moved to a MAX96717 driver which makes use of the Maxim GMSL2/3
serializer framework.

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
---
 MAINTAINERS                  |    1 -
 drivers/media/i2c/Kconfig    |   17 -
 drivers/media/i2c/Makefile   |    1 -
 drivers/media/i2c/max96717.c | 1102 ----------------------------------
 4 files changed, 1121 deletions(-)
 delete mode 100644 drivers/media/i2c/max96717.c

diff --git a/MAINTAINERS b/MAINTAINERS
index ff0c7fe22ce6e..c2e7c677f4652 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14766,7 +14766,6 @@ M:	Cosmin Tanislav <cosmin.tanislav@analog.com>
 L:	linux-media@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
-F:	drivers/media/i2c/max96717.c
 
 MAX9860 MONO AUDIO VOICE CODEC DRIVER
 M:	Peter Rosin <peda@axentia.se>
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 801a712a31808..27fb7524f39bf 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1669,23 +1669,6 @@ config VIDEO_MAX96714
 	  To compile this driver as a module, choose M here: the
 	  module will be called max96714.
 
-config VIDEO_MAX96717
-	tristate "Maxim MAX96717 GMSL2 Serializer support"
-	depends on I2C && VIDEO_DEV && COMMON_CLK
-	select I2C_MUX
-	select MEDIA_CONTROLLER
-	select GPIOLIB
-	select V4L2_CCI_I2C
-	select V4L2_FWNODE
-	select VIDEO_V4L2_SUBDEV_API
-	help
-	  Device driver for the Maxim MAX96717 GMSL2 Serializer.
-	  MAX96717 serializers convert video on a MIPI CSI-2
-	  input to a GMSL2 output.
-
-	  To compile this driver as a module, choose M here: the
-	  module will be called max96717.
-
 source "drivers/media/i2c/maxim-serdes/Kconfig"
 
 endmenu
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 25a0093d40ecf..29ac7ea2bfa0b 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -69,7 +69,6 @@ obj-$(CONFIG_VIDEO_M52790) += m52790.o
 obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o
 obj-$(CONFIG_VIDEO_MAX9286) += max9286.o
 obj-$(CONFIG_VIDEO_MAX96714) += max96714.o
-obj-$(CONFIG_VIDEO_MAX96717) += max96717.o
 obj-$(CONFIG_VIDEO_MAXIM_SERDES) += maxim-serdes/
 obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o
 obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o
diff --git a/drivers/media/i2c/max96717.c b/drivers/media/i2c/max96717.c
deleted file mode 100644
index 015e42fbe2462..0000000000000
--- a/drivers/media/i2c/max96717.c
+++ /dev/null
@@ -1,1102 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Maxim GMSL2 Serializer Driver
- *
- * Copyright (C) 2024 Collabora Ltd.
- */
-
-#include <linux/bitfield.h>
-#include <linux/clk.h>
-#include <linux/clk-provider.h>
-#include <linux/delay.h>
-#include <linux/gpio/driver.h>
-#include <linux/i2c-mux.h>
-#include <linux/i2c.h>
-#include <linux/property.h>
-#include <linux/regmap.h>
-
-#include <media/v4l2-cci.h>
-#include <media/v4l2-ctrls.h>
-#include <media/v4l2-fwnode.h>
-#include <media/v4l2-subdev.h>
-
-#define MAX96717_DEVICE_ID  0xbf
-#define MAX96717F_DEVICE_ID 0xc8
-#define MAX96717_PORTS      2
-#define MAX96717_PAD_SINK   0
-#define MAX96717_PAD_SOURCE 1
-#define MAX96717_CSI_NLANES 4
-
-#define MAX96717_DEFAULT_CLKOUT_RATE	24000000UL
-
-/* DEV */
-#define MAX96717_REG3    CCI_REG8(0x3)
-#define MAX96717_RCLKSEL GENMASK(1, 0)
-#define RCLKSEL_REF_PLL  CCI_REG8(0x3)
-#define MAX96717_REG6    CCI_REG8(0x6)
-#define RCLKEN           BIT(5)
-#define MAX96717_DEV_ID  CCI_REG8(0xd)
-#define MAX96717_DEV_REV CCI_REG8(0xe)
-#define MAX96717_DEV_REV_MASK GENMASK(3, 0)
-
-/* VID_TX Z */
-#define MAX96717_VIDEO_TX0 CCI_REG8(0x110)
-#define MAX96717_VIDEO_AUTO_BPP BIT(3)
-#define MAX96717_VIDEO_TX2 CCI_REG8(0x112)
-#define MAX96717_VIDEO_PCLKDET BIT(7)
-
-/* VTX_Z */
-#define MAX96717_VTX0                  CCI_REG8(0x24e)
-#define MAX96717_VTX1                  CCI_REG8(0x24f)
-#define MAX96717_PATTERN_CLK_FREQ      GENMASK(3, 1)
-#define MAX96717_VTX_VS_DLY            CCI_REG24(0x250)
-#define MAX96717_VTX_VS_HIGH           CCI_REG24(0x253)
-#define MAX96717_VTX_VS_LOW            CCI_REG24(0x256)
-#define MAX96717_VTX_V2H               CCI_REG24(0x259)
-#define MAX96717_VTX_HS_HIGH           CCI_REG16(0x25c)
-#define MAX96717_VTX_HS_LOW            CCI_REG16(0x25e)
-#define MAX96717_VTX_HS_CNT            CCI_REG16(0x260)
-#define MAX96717_VTX_V2D               CCI_REG24(0x262)
-#define MAX96717_VTX_DE_HIGH           CCI_REG16(0x265)
-#define MAX96717_VTX_DE_LOW            CCI_REG16(0x267)
-#define MAX96717_VTX_DE_CNT            CCI_REG16(0x269)
-#define MAX96717_VTX29                 CCI_REG8(0x26b)
-#define MAX96717_VTX_MODE              GENMASK(1, 0)
-#define MAX96717_VTX_GRAD_INC          CCI_REG8(0x26c)
-#define MAX96717_VTX_CHKB_COLOR_A      CCI_REG24(0x26d)
-#define MAX96717_VTX_CHKB_COLOR_B      CCI_REG24(0x270)
-#define MAX96717_VTX_CHKB_RPT_CNT_A    CCI_REG8(0x273)
-#define MAX96717_VTX_CHKB_RPT_CNT_B    CCI_REG8(0x274)
-#define MAX96717_VTX_CHKB_ALT          CCI_REG8(0x275)
-
-/* GPIO */
-#define MAX96717_NUM_GPIO         11
-#define MAX96717_GPIO_REG_A(gpio) CCI_REG8(0x2be + (gpio) * 3)
-#define MAX96717_GPIO_OUT         BIT(4)
-#define MAX96717_GPIO_IN          BIT(3)
-#define MAX96717_GPIO_RX_EN       BIT(2)
-#define MAX96717_GPIO_TX_EN       BIT(1)
-#define MAX96717_GPIO_OUT_DIS     BIT(0)
-
-/* FRONTTOP */
-/* MAX96717 only have CSI port 'B' */
-#define MAX96717_FRONTOP0     CCI_REG8(0x308)
-#define MAX96717_START_PORT_B BIT(5)
-
-/* MIPI_RX */
-#define MAX96717_MIPI_RX1       CCI_REG8(0x331)
-#define MAX96717_MIPI_LANES_CNT GENMASK(5, 4)
-#define MAX96717_MIPI_RX2       CCI_REG8(0x332) /* phy1 Lanes map */
-#define MAX96717_PHY2_LANES_MAP GENMASK(7, 4)
-#define MAX96717_MIPI_RX3       CCI_REG8(0x333) /* phy2 Lanes map */
-#define MAX96717_PHY1_LANES_MAP GENMASK(3, 0)
-#define MAX96717_MIPI_RX4       CCI_REG8(0x334) /* phy1 lane polarities */
-#define MAX96717_PHY1_LANES_POL GENMASK(6, 4)
-#define MAX96717_MIPI_RX5       CCI_REG8(0x335) /* phy2 lane polarities */
-#define MAX96717_PHY2_LANES_POL GENMASK(2, 0)
-
-/* MIPI_RX_EXT */
-#define MAX96717_MIPI_RX_EXT11 CCI_REG8(0x383)
-#define MAX96717_TUN_MODE      BIT(7)
-
-/* REF_VTG */
-#define REF_VTG0                CCI_REG8(0x3f0)
-#define REFGEN_PREDEF_EN        BIT(6)
-#define REFGEN_PREDEF_FREQ_MASK GENMASK(5, 4)
-#define REFGEN_PREDEF_FREQ_ALT  BIT(3)
-#define REFGEN_RST              BIT(1)
-#define REFGEN_EN               BIT(0)
-
-/* MISC */
-#define PIO_SLEW_1 CCI_REG8(0x570)
-
-enum max96717_vpg_mode {
-	MAX96717_VPG_DISABLED = 0,
-	MAX96717_VPG_CHECKERBOARD = 1,
-	MAX96717_VPG_GRADIENT = 2,
-};
-
-struct max96717_priv {
-	struct i2c_client		  *client;
-	struct regmap			  *regmap;
-	struct i2c_mux_core		  *mux;
-	struct v4l2_mbus_config_mipi_csi2 mipi_csi2;
-	struct v4l2_subdev                sd;
-	struct media_pad                  pads[MAX96717_PORTS];
-	struct v4l2_ctrl_handler          ctrl_handler;
-	struct v4l2_async_notifier        notifier;
-	struct v4l2_subdev                *source_sd;
-	u16                               source_sd_pad;
-	u64			          enabled_source_streams;
-	u8                                pll_predef_index;
-	struct clk_hw                     clk_hw;
-	struct gpio_chip                  gpio_chip;
-	enum max96717_vpg_mode            pattern;
-};
-
-static inline struct max96717_priv *sd_to_max96717(struct v4l2_subdev *sd)
-{
-	return container_of(sd, struct max96717_priv, sd);
-}
-
-static inline struct max96717_priv *clk_hw_to_max96717(struct clk_hw *hw)
-{
-	return container_of(hw, struct max96717_priv, clk_hw);
-}
-
-static int max96717_i2c_mux_select(struct i2c_mux_core *mux, u32 chan)
-{
-	return 0;
-}
-
-static int max96717_i2c_mux_init(struct max96717_priv *priv)
-{
-	priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev,
-				  1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE,
-				  max96717_i2c_mux_select, NULL);
-	if (!priv->mux)
-		return -ENOMEM;
-
-	return i2c_mux_add_adapter(priv->mux, 0, 0);
-}
-
-static inline int max96717_start_csi(struct max96717_priv *priv, bool start)
-{
-	return cci_update_bits(priv->regmap, MAX96717_FRONTOP0,
-			       MAX96717_START_PORT_B,
-			       start ? MAX96717_START_PORT_B : 0, NULL);
-}
-
-static int max96717_apply_patgen_timing(struct max96717_priv *priv,
-					struct v4l2_subdev_state *state)
-{
-	struct v4l2_mbus_framefmt *fmt =
-		v4l2_subdev_state_get_format(state, MAX96717_PAD_SOURCE);
-	const u32 h_active = fmt->width;
-	const u32 h_fp = 88;
-	const u32 h_sw = 44;
-	const u32 h_bp = 148;
-	u32 h_tot;
-	const u32 v_active = fmt->height;
-	const u32 v_fp = 4;
-	const u32 v_sw = 5;
-	const u32 v_bp = 36;
-	u32 v_tot;
-	int ret = 0;
-
-	h_tot = h_active + h_fp + h_sw + h_bp;
-	v_tot = v_active + v_fp + v_sw + v_bp;
-
-	/* 75 Mhz pixel clock */
-	cci_update_bits(priv->regmap, MAX96717_VTX1,
-			MAX96717_PATTERN_CLK_FREQ, 0xa, &ret);
-
-	dev_info(&priv->client->dev, "height: %d width: %d\n", fmt->height,
-		 fmt->width);
-
-	cci_write(priv->regmap, MAX96717_VTX_VS_DLY, 0, &ret);
-	cci_write(priv->regmap, MAX96717_VTX_VS_HIGH, v_sw * h_tot, &ret);
-	cci_write(priv->regmap, MAX96717_VTX_VS_LOW,
-		  (v_active + v_fp + v_bp) * h_tot, &ret);
-	cci_write(priv->regmap, MAX96717_VTX_HS_HIGH, h_sw, &ret);
-	cci_write(priv->regmap, MAX96717_VTX_HS_LOW, h_active + h_fp + h_bp,
-		  &ret);
-	cci_write(priv->regmap, MAX96717_VTX_V2D,
-		  h_tot * (v_sw + v_bp) + (h_sw + h_bp), &ret);
-	cci_write(priv->regmap, MAX96717_VTX_HS_CNT, v_tot, &ret);
-	cci_write(priv->regmap, MAX96717_VTX_DE_HIGH, h_active, &ret);
-	cci_write(priv->regmap, MAX96717_VTX_DE_LOW, h_fp + h_sw + h_bp,
-		  &ret);
-	cci_write(priv->regmap, MAX96717_VTX_DE_CNT, v_active, &ret);
-	/* B G R */
-	cci_write(priv->regmap, MAX96717_VTX_CHKB_COLOR_A, 0xfecc00, &ret);
-	/* B G R */
-	cci_write(priv->regmap, MAX96717_VTX_CHKB_COLOR_B, 0x006aa7, &ret);
-	cci_write(priv->regmap, MAX96717_VTX_CHKB_RPT_CNT_A, 0x3c, &ret);
-	cci_write(priv->regmap, MAX96717_VTX_CHKB_RPT_CNT_B, 0x3c, &ret);
-	cci_write(priv->regmap, MAX96717_VTX_CHKB_ALT, 0x3c, &ret);
-	cci_write(priv->regmap, MAX96717_VTX_GRAD_INC, 0x10, &ret);
-
-	return ret;
-}
-
-static int max96717_apply_patgen(struct max96717_priv *priv,
-				 struct v4l2_subdev_state *state)
-{
-	unsigned int val;
-	int ret = 0;
-
-	if (priv->pattern)
-		ret = max96717_apply_patgen_timing(priv, state);
-
-	cci_write(priv->regmap, MAX96717_VTX0, priv->pattern ? 0xfb : 0,
-		  &ret);
-
-	val = FIELD_PREP(MAX96717_VTX_MODE, priv->pattern);
-	cci_update_bits(priv->regmap, MAX96717_VTX29, MAX96717_VTX_MODE,
-			val, &ret);
-	return ret;
-}
-
-static int max96717_s_ctrl(struct v4l2_ctrl *ctrl)
-{
-	struct max96717_priv *priv =
-		container_of(ctrl->handler, struct max96717_priv, ctrl_handler);
-	int ret;
-
-	switch (ctrl->id) {
-	case V4L2_CID_TEST_PATTERN:
-		if (priv->enabled_source_streams)
-			return -EBUSY;
-		priv->pattern = ctrl->val;
-		break;
-	default:
-		return -EINVAL;
-	}
-
-	/* Use bpp from bpp register */
-	ret = cci_update_bits(priv->regmap, MAX96717_VIDEO_TX0,
-			      MAX96717_VIDEO_AUTO_BPP,
-			      priv->pattern ? 0 : MAX96717_VIDEO_AUTO_BPP,
-			      NULL);
-
-	/*
-	 * Pattern generator doesn't work with tunnel mode.
-	 * Needs RGB color format and deserializer tunnel mode must be disabled.
-	 */
-	return cci_update_bits(priv->regmap, MAX96717_MIPI_RX_EXT11,
-			       MAX96717_TUN_MODE,
-			       priv->pattern ? 0 : MAX96717_TUN_MODE, &ret);
-}
-
-static const char * const max96717_test_pattern[] = {
-	"Disabled",
-	"Checkerboard",
-	"Gradient"
-};
-
-static const struct v4l2_ctrl_ops max96717_ctrl_ops = {
-	.s_ctrl = max96717_s_ctrl,
-};
-
-static int max96717_gpiochip_get(struct gpio_chip *gpiochip,
-				 unsigned int offset)
-{
-	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
-	u64 val;
-	int ret;
-
-	ret = cci_read(priv->regmap, MAX96717_GPIO_REG_A(offset),
-		       &val, NULL);
-	if (ret)
-		return ret;
-
-	if (val & MAX96717_GPIO_OUT_DIS)
-		return !!(val & MAX96717_GPIO_IN);
-	else
-		return !!(val & MAX96717_GPIO_OUT);
-}
-
-static int max96717_gpiochip_set(struct gpio_chip *gpiochip,
-				 unsigned int offset, int value)
-{
-	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
-
-	return cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset),
-			       MAX96717_GPIO_OUT, MAX96717_GPIO_OUT, NULL);
-}
-
-static int max96717_gpio_get_direction(struct gpio_chip *gpiochip,
-				       unsigned int offset)
-{
-	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
-	u64 val;
-	int ret;
-
-	ret = cci_read(priv->regmap, MAX96717_GPIO_REG_A(offset), &val, NULL);
-	if (ret < 0)
-		return ret;
-
-	return !!(val & MAX96717_GPIO_OUT_DIS);
-}
-
-static int max96717_gpio_direction_out(struct gpio_chip *gpiochip,
-				       unsigned int offset, int value)
-{
-	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
-
-	return cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset),
-			       MAX96717_GPIO_OUT_DIS | MAX96717_GPIO_OUT,
-			       value ? MAX96717_GPIO_OUT : 0, NULL);
-}
-
-static int max96717_gpio_direction_in(struct gpio_chip *gpiochip,
-				      unsigned int offset)
-{
-	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
-
-	return cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset),
-			       MAX96717_GPIO_OUT_DIS, MAX96717_GPIO_OUT_DIS,
-			       NULL);
-}
-
-static int max96717_gpiochip_probe(struct max96717_priv *priv)
-{
-	struct device *dev = &priv->client->dev;
-	struct gpio_chip *gc = &priv->gpio_chip;
-	int i, ret = 0;
-
-	gc->label = dev_name(dev);
-	gc->parent = dev;
-	gc->owner = THIS_MODULE;
-	gc->ngpio = MAX96717_NUM_GPIO;
-	gc->base = -1;
-	gc->can_sleep = true;
-	gc->get_direction = max96717_gpio_get_direction;
-	gc->direction_input = max96717_gpio_direction_in;
-	gc->direction_output = max96717_gpio_direction_out;
-	gc->set_rv = max96717_gpiochip_set;
-	gc->get = max96717_gpiochip_get;
-
-	/* Disable GPIO forwarding */
-	for (i = 0; i < gc->ngpio; i++)
-		cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(i),
-				MAX96717_GPIO_RX_EN | MAX96717_GPIO_TX_EN,
-				0, &ret);
-
-	if (ret)
-		return ret;
-
-	ret = devm_gpiochip_add_data(dev, gc, priv);
-	if (ret) {
-		dev_err(dev, "Unable to create gpio_chip\n");
-		return ret;
-	}
-
-	return 0;
-}
-
-static int _max96717_set_routing(struct v4l2_subdev *sd,
-				 struct v4l2_subdev_state *state,
-				 struct v4l2_subdev_krouting *routing)
-{
-	static const struct v4l2_mbus_framefmt format = {
-		.width = 1280,
-		.height = 1080,
-		.code = MEDIA_BUS_FMT_Y8_1X8,
-		.field = V4L2_FIELD_NONE,
-	};
-	int ret;
-
-	ret = v4l2_subdev_routing_validate(sd, routing,
-					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
-	if (ret)
-		return ret;
-
-	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
-	if (ret)
-		return ret;
-
-	return 0;
-}
-
-static int max96717_set_routing(struct v4l2_subdev *sd,
-				struct v4l2_subdev_state *state,
-				enum v4l2_subdev_format_whence which,
-				struct v4l2_subdev_krouting *routing)
-{
-	struct max96717_priv *priv = sd_to_max96717(sd);
-
-	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams)
-		return -EBUSY;
-
-	return _max96717_set_routing(sd, state, routing);
-}
-
-static int max96717_set_fmt(struct v4l2_subdev *sd,
-			    struct v4l2_subdev_state *state,
-			    struct v4l2_subdev_format *format)
-{
-	struct max96717_priv *priv = sd_to_max96717(sd);
-	struct v4l2_mbus_framefmt *fmt;
-	u64 stream_source_mask;
-
-	if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
-	    priv->enabled_source_streams)
-		return -EBUSY;
-
-	/* No transcoding, source and sink formats must match. */
-	if (format->pad == MAX96717_PAD_SOURCE)
-		return v4l2_subdev_get_fmt(sd, state, format);
-
-	/* Set sink format */
-	fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
-	if (!fmt)
-		return -EINVAL;
-
-	*fmt = format->format;
-
-	/* Propagate to source format */
-	fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
-							   format->stream);
-	if (!fmt)
-		return -EINVAL;
-	*fmt = format->format;
-
-	stream_source_mask = BIT(format->stream);
-
-	return v4l2_subdev_state_xlate_streams(state, MAX96717_PAD_SOURCE,
-					       MAX96717_PAD_SINK,
-					       &stream_source_mask);
-}
-
-static int max96717_init_state(struct v4l2_subdev *sd,
-			       struct v4l2_subdev_state *state)
-{
-	struct v4l2_subdev_route routes[] = {
-		{
-			.sink_pad = MAX96717_PAD_SINK,
-			.sink_stream = 0,
-			.source_pad = MAX96717_PAD_SOURCE,
-			.source_stream = 0,
-			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
-		},
-	};
-	struct v4l2_subdev_krouting routing = {
-		.num_routes = ARRAY_SIZE(routes),
-		.routes = routes,
-	};
-
-	return _max96717_set_routing(sd, state, &routing);
-}
-
-static bool max96717_pipe_pclkdet(struct max96717_priv *priv)
-{
-	u64 val = 0;
-
-	cci_read(priv->regmap, MAX96717_VIDEO_TX2, &val, NULL);
-
-	return val & MAX96717_VIDEO_PCLKDET;
-}
-
-static int max96717_log_status(struct v4l2_subdev *sd)
-{
-	struct max96717_priv *priv = sd_to_max96717(sd);
-	struct device *dev = &priv->client->dev;
-
-	dev_info(dev, "Serializer: max96717\n");
-	dev_info(dev, "Pipe: pclkdet:%d\n", max96717_pipe_pclkdet(priv));
-
-	return 0;
-}
-
-static int max96717_enable_streams(struct v4l2_subdev *sd,
-				   struct v4l2_subdev_state *state, u32 pad,
-				   u64 streams_mask)
-{
-	struct max96717_priv *priv = sd_to_max96717(sd);
-	u64 sink_streams;
-	int ret;
-
-	if (!priv->enabled_source_streams)
-		max96717_start_csi(priv, true);
-
-	ret = max96717_apply_patgen(priv, state);
-	if (ret)
-		goto stop_csi;
-
-	if (!priv->pattern) {
-		sink_streams =
-			v4l2_subdev_state_xlate_streams(state,
-							MAX96717_PAD_SOURCE,
-							MAX96717_PAD_SINK,
-							&streams_mask);
-
-		ret = v4l2_subdev_enable_streams(priv->source_sd,
-						 priv->source_sd_pad,
-						 sink_streams);
-		if (ret)
-			goto stop_csi;
-	}
-
-	priv->enabled_source_streams |= streams_mask;
-
-	return 0;
-
-stop_csi:
-	if (!priv->enabled_source_streams)
-		max96717_start_csi(priv, false);
-
-	return ret;
-}
-
-static int max96717_disable_streams(struct v4l2_subdev *sd,
-				    struct v4l2_subdev_state *state, u32 pad,
-				    u64 streams_mask)
-{
-	struct max96717_priv *priv = sd_to_max96717(sd);
-	u64 sink_streams;
-
-	/*
-	 * Stop the CSI receiver first then the source,
-	 * otherwise the device may become unresponsive
-	 * while holding the I2C bus low.
-	 */
-	priv->enabled_source_streams &= ~streams_mask;
-	if (!priv->enabled_source_streams)
-		max96717_start_csi(priv, false);
-
-	if (!priv->pattern) {
-		int ret;
-
-		sink_streams =
-			v4l2_subdev_state_xlate_streams(state,
-							MAX96717_PAD_SOURCE,
-							MAX96717_PAD_SINK,
-							&streams_mask);
-
-		ret = v4l2_subdev_disable_streams(priv->source_sd,
-						  priv->source_sd_pad,
-						  sink_streams);
-		if (ret)
-			return ret;
-	}
-
-	return 0;
-}
-
-static const struct v4l2_subdev_pad_ops max96717_pad_ops = {
-	.enable_streams = max96717_enable_streams,
-	.disable_streams = max96717_disable_streams,
-	.set_routing = max96717_set_routing,
-	.get_fmt = v4l2_subdev_get_fmt,
-	.set_fmt = max96717_set_fmt,
-};
-
-static const struct v4l2_subdev_core_ops max96717_subdev_core_ops = {
-	.log_status = max96717_log_status,
-};
-
-static const struct v4l2_subdev_internal_ops max96717_internal_ops = {
-	.init_state = max96717_init_state,
-};
-
-static const struct v4l2_subdev_ops max96717_subdev_ops = {
-	.core = &max96717_subdev_core_ops,
-	.pad = &max96717_pad_ops,
-};
-
-static const struct media_entity_operations max96717_entity_ops = {
-	.link_validate = v4l2_subdev_link_validate,
-};
-
-static int max96717_notify_bound(struct v4l2_async_notifier *notifier,
-				 struct v4l2_subdev *source_subdev,
-				 struct v4l2_async_connection *asd)
-{
-	struct max96717_priv *priv = sd_to_max96717(notifier->sd);
-	struct device *dev = &priv->client->dev;
-	int ret;
-
-	ret = media_entity_get_fwnode_pad(&source_subdev->entity,
-					  source_subdev->fwnode,
-					  MEDIA_PAD_FL_SOURCE);
-	if (ret < 0) {
-		dev_err(dev, "Failed to find pad for %s\n",
-			source_subdev->name);
-		return ret;
-	}
-
-	priv->source_sd = source_subdev;
-	priv->source_sd_pad = ret;
-
-	ret = media_create_pad_link(&source_subdev->entity, priv->source_sd_pad,
-				    &priv->sd.entity, 0,
-				    MEDIA_LNK_FL_ENABLED |
-				    MEDIA_LNK_FL_IMMUTABLE);
-	if (ret) {
-		dev_err(dev, "Unable to link %s:%u -> %s:0\n",
-			source_subdev->name, priv->source_sd_pad,
-			priv->sd.name);
-		return ret;
-	}
-
-	return 0;
-}
-
-static const struct v4l2_async_notifier_operations max96717_notify_ops = {
-	.bound = max96717_notify_bound,
-};
-
-static int max96717_v4l2_notifier_register(struct max96717_priv *priv)
-{
-	struct device *dev = &priv->client->dev;
-	struct v4l2_async_connection *asd;
-	struct fwnode_handle *ep_fwnode;
-	int ret;
-
-	ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
-						    MAX96717_PAD_SINK, 0, 0);
-	if (!ep_fwnode) {
-		dev_err(dev, "No graph endpoint\n");
-		return -ENODEV;
-	}
-
-	v4l2_async_subdev_nf_init(&priv->notifier, &priv->sd);
-
-	asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, ep_fwnode,
-					      struct v4l2_async_connection);
-
-	fwnode_handle_put(ep_fwnode);
-
-	if (IS_ERR(asd)) {
-		dev_err(dev, "Failed to add subdev: %ld", PTR_ERR(asd));
-		v4l2_async_nf_cleanup(&priv->notifier);
-		return PTR_ERR(asd);
-	}
-
-	priv->notifier.ops = &max96717_notify_ops;
-
-	ret = v4l2_async_nf_register(&priv->notifier);
-	if (ret) {
-		dev_err(dev, "Failed to register subdev_notifier");
-		v4l2_async_nf_cleanup(&priv->notifier);
-		return ret;
-	}
-
-	return 0;
-}
-
-static int max96717_subdev_init(struct max96717_priv *priv)
-{
-	struct device *dev = &priv->client->dev;
-	int ret;
-
-	v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96717_subdev_ops);
-	priv->sd.internal_ops = &max96717_internal_ops;
-
-	v4l2_ctrl_handler_init(&priv->ctrl_handler, 1);
-	priv->sd.ctrl_handler = &priv->ctrl_handler;
-
-	v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler,
-				     &max96717_ctrl_ops,
-				     V4L2_CID_TEST_PATTERN,
-				     ARRAY_SIZE(max96717_test_pattern) - 1,
-				     0, 0, max96717_test_pattern);
-	if (priv->ctrl_handler.error) {
-		ret = priv->ctrl_handler.error;
-		goto err_free_ctrl;
-	}
-
-	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
-	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
-	priv->sd.entity.ops = &max96717_entity_ops;
-
-	priv->pads[MAX96717_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
-	priv->pads[MAX96717_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
-
-	ret = media_entity_pads_init(&priv->sd.entity, 2, priv->pads);
-	if (ret) {
-		dev_err_probe(dev, ret, "Failed to init pads\n");
-		goto err_free_ctrl;
-	}
-
-	ret = v4l2_subdev_init_finalize(&priv->sd);
-	if (ret) {
-		dev_err_probe(dev, ret,
-			      "v4l2 subdev init finalized failed\n");
-		goto err_entity_cleanup;
-	}
-	ret = max96717_v4l2_notifier_register(priv);
-	if (ret) {
-		dev_err_probe(dev, ret,
-			      "v4l2 subdev notifier register failed\n");
-		goto err_free_state;
-	}
-
-	ret = v4l2_async_register_subdev(&priv->sd);
-	if (ret) {
-		dev_err_probe(dev, ret, "v4l2_async_register_subdev error\n");
-		goto err_unreg_notif;
-	}
-
-	return 0;
-
-err_unreg_notif:
-	v4l2_async_nf_unregister(&priv->notifier);
-	v4l2_async_nf_cleanup(&priv->notifier);
-err_free_state:
-	v4l2_subdev_cleanup(&priv->sd);
-err_entity_cleanup:
-	media_entity_cleanup(&priv->sd.entity);
-err_free_ctrl:
-	v4l2_ctrl_handler_free(&priv->ctrl_handler);
-
-	return ret;
-}
-
-static void max96717_subdev_uninit(struct max96717_priv *priv)
-{
-	v4l2_async_unregister_subdev(&priv->sd);
-	v4l2_async_nf_unregister(&priv->notifier);
-	v4l2_async_nf_cleanup(&priv->notifier);
-	v4l2_subdev_cleanup(&priv->sd);
-	media_entity_cleanup(&priv->sd.entity);
-	v4l2_ctrl_handler_free(&priv->ctrl_handler);
-}
-
-struct max96717_pll_predef_freq {
-	unsigned long freq;
-	bool is_alt;
-	u8 val;
-};
-
-static const struct max96717_pll_predef_freq max96717_predef_freqs[] = {
-	{ 13500000, true,  0 }, { 19200000, false, 0 },
-	{ 24000000, true,  1 }, { 27000000, false, 1 },
-	{ 37125000, false, 2 }, { 74250000, false, 3 },
-};
-
-static unsigned long
-max96717_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
-{
-	struct max96717_priv *priv = clk_hw_to_max96717(hw);
-
-	return max96717_predef_freqs[priv->pll_predef_index].freq;
-}
-
-static unsigned int max96717_clk_find_best_index(struct max96717_priv *priv,
-						 unsigned long rate)
-{
-	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_max96717(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_max96717(hw);
-	unsigned int val, idx;
-	int ret = 0;
-
-	idx = max96717_clk_find_best_index(priv, rate);
-
-	val = FIELD_PREP(REFGEN_PREDEF_FREQ_MASK,
-			 max96717_predef_freqs[idx].val);
-
-	if (max96717_predef_freqs[idx].is_alt)
-		val |= REFGEN_PREDEF_FREQ_ALT;
-
-	val |= REFGEN_RST | REFGEN_PREDEF_EN;
-
-	cci_write(priv->regmap, REF_VTG0, val, &ret);
-	cci_update_bits(priv->regmap, REF_VTG0, REFGEN_RST | REFGEN_EN,
-			REFGEN_EN, &ret);
-	if (ret)
-		return ret;
-
-	priv->pll_predef_index = idx;
-
-	return 0;
-}
-
-static int max96717_clk_prepare(struct clk_hw *hw)
-{
-	struct max96717_priv *priv = clk_hw_to_max96717(hw);
-
-	return cci_update_bits(priv->regmap, MAX96717_REG6, RCLKEN,
-			       RCLKEN, NULL);
-}
-
-static void max96717_clk_unprepare(struct clk_hw *hw)
-{
-	struct max96717_priv *priv = clk_hw_to_max96717(hw);
-
-	cci_update_bits(priv->regmap, MAX96717_REG6, RCLKEN, 0, NULL);
-}
-
-static const struct clk_ops max96717_clk_ops = {
-	.prepare     = max96717_clk_prepare,
-	.unprepare   = max96717_clk_unprepare,
-	.set_rate    = max96717_clk_set_rate,
-	.recalc_rate = max96717_clk_recalc_rate,
-	.round_rate  = max96717_clk_round_rate,
-};
-
-static int max96717_register_clkout(struct max96717_priv *priv)
-{
-	struct device *dev = &priv->client->dev;
-	struct clk_init_data init = { .ops = &max96717_clk_ops };
-	int ret;
-
-	init.name = kasprintf(GFP_KERNEL, "max96717.%s.clk_out", dev_name(dev));
-	if (!init.name)
-		return -ENOMEM;
-
-	/* RCLKSEL Reference PLL output */
-	ret = cci_update_bits(priv->regmap, MAX96717_REG3, MAX96717_RCLKSEL,
-			      MAX96717_RCLKSEL, NULL);
-	/* MFP4 fastest slew rate */
-	cci_update_bits(priv->regmap, PIO_SLEW_1, BIT(5) | BIT(4), 0, &ret);
-	if (ret)
-		goto free_init_name;
-
-	priv->clk_hw.init = &init;
-
-	/* Initialize to 24 MHz */
-	ret = max96717_clk_set_rate(&priv->clk_hw,
-				    MAX96717_DEFAULT_CLKOUT_RATE, 0);
-	if (ret < 0)
-		goto free_init_name;
-
-	ret = devm_clk_hw_register(dev, &priv->clk_hw);
-	kfree(init.name);
-	if (ret)
-		return dev_err_probe(dev, ret, "Cannot register clock HW\n");
-
-	ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
-					  &priv->clk_hw);
-	if (ret)
-		return dev_err_probe(dev, ret,
-				     "Cannot add OF clock provider\n");
-
-	return 0;
-
-free_init_name:
-	kfree(init.name);
-	return ret;
-}
-
-static int max96717_init_csi_lanes(struct max96717_priv *priv)
-{
-	struct v4l2_mbus_config_mipi_csi2 *mipi = &priv->mipi_csi2;
-	unsigned long lanes_used = 0;
-	unsigned int nlanes, lane, val = 0;
-	int ret;
-
-	nlanes = mipi->num_data_lanes;
-
-	ret = cci_update_bits(priv->regmap, MAX96717_MIPI_RX1,
-			      MAX96717_MIPI_LANES_CNT,
-			      FIELD_PREP(MAX96717_MIPI_LANES_CNT,
-					 nlanes - 1), NULL);
-
-	/* lanes polarity */
-	for (lane = 0; lane < nlanes + 1; lane++) {
-		if (!mipi->lane_polarities[lane])
-			continue;
-		/* Clock lane */
-		if (lane == 0)
-			val |= BIT(2);
-		else if (lane < 3)
-			val |= BIT(lane - 1);
-		else
-			val |= BIT(lane);
-	}
-
-	cci_update_bits(priv->regmap, MAX96717_MIPI_RX5,
-			MAX96717_PHY2_LANES_POL,
-			FIELD_PREP(MAX96717_PHY2_LANES_POL, val), &ret);
-
-	cci_update_bits(priv->regmap, MAX96717_MIPI_RX4,
-			MAX96717_PHY1_LANES_POL,
-			FIELD_PREP(MAX96717_PHY1_LANES_POL,
-				   val >> 3), &ret);
-	/* lanes mapping */
-	for (lane = 0, val = 0; lane < nlanes; lane++) {
-		val |= (mipi->data_lanes[lane] - 1) << (lane * 2);
-		lanes_used |= BIT(mipi->data_lanes[lane] - 1);
-	}
-
-	/*
-	 * Unused lanes need to be mapped as well to not have
-	 * the same lanes mapped twice.
-	 */
-	for (; lane < MAX96717_CSI_NLANES; lane++) {
-		unsigned int idx = find_first_zero_bit(&lanes_used,
-						       MAX96717_CSI_NLANES);
-
-		val |= idx << (lane * 2);
-		lanes_used |= BIT(idx);
-	}
-
-	cci_update_bits(priv->regmap, MAX96717_MIPI_RX3,
-			MAX96717_PHY1_LANES_MAP,
-			FIELD_PREP(MAX96717_PHY1_LANES_MAP, val), &ret);
-
-	return cci_update_bits(priv->regmap, MAX96717_MIPI_RX2,
-			       MAX96717_PHY2_LANES_MAP,
-			       FIELD_PREP(MAX96717_PHY2_LANES_MAP, val >> 4),
-			       &ret);
-}
-
-static int max96717_hw_init(struct max96717_priv *priv)
-{
-	struct device *dev = &priv->client->dev;
-	u64 dev_id, val;
-	int ret;
-
-	ret = cci_read(priv->regmap, MAX96717_DEV_ID, &dev_id, NULL);
-	if (ret)
-		return dev_err_probe(dev, ret,
-				     "Fail to read the device id\n");
-
-	if (dev_id != MAX96717_DEVICE_ID && dev_id != MAX96717F_DEVICE_ID)
-		return dev_err_probe(dev, -EOPNOTSUPP,
-				     "Unsupported device id got %x\n", (u8)dev_id);
-
-	ret = cci_read(priv->regmap, MAX96717_DEV_REV, &val, NULL);
-	if (ret)
-		return dev_err_probe(dev, ret,
-				     "Fail to read device revision");
-
-	dev_dbg(dev, "Found %x (rev %lx)\n", (u8)dev_id,
-		(u8)val & MAX96717_DEV_REV_MASK);
-
-	ret = cci_read(priv->regmap, MAX96717_MIPI_RX_EXT11, &val, NULL);
-	if (ret)
-		return dev_err_probe(dev, ret,
-				     "Fail to read mipi rx extension");
-
-	if (!(val & MAX96717_TUN_MODE))
-		return dev_err_probe(dev, -EOPNOTSUPP,
-				     "Only supporting tunnel mode");
-
-	return max96717_init_csi_lanes(priv);
-}
-
-static int max96717_parse_dt(struct max96717_priv *priv)
-{
-	struct device *dev = &priv->client->dev;
-	struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
-	struct fwnode_handle *ep_fwnode;
-	unsigned char num_data_lanes;
-	int ret;
-
-	ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
-						    MAX96717_PAD_SINK, 0, 0);
-	if (!ep_fwnode)
-		return dev_err_probe(dev, -ENOENT, "no endpoint found\n");
-
-	ret = v4l2_fwnode_endpoint_parse(ep_fwnode, &vep);
-
-	fwnode_handle_put(ep_fwnode);
-
-	if (ret < 0)
-		return dev_err_probe(dev, ret, "Failed to parse sink endpoint");
-
-	num_data_lanes = vep.bus.mipi_csi2.num_data_lanes;
-	if (num_data_lanes < 1 || num_data_lanes > MAX96717_CSI_NLANES)
-		return dev_err_probe(dev, -EINVAL,
-				     "Invalid data lanes must be 1 to 4\n");
-
-	priv->mipi_csi2 = vep.bus.mipi_csi2;
-
-	return 0;
-}
-
-static int max96717_probe(struct i2c_client *client)
-{
-	struct device *dev = &client->dev;
-	struct max96717_priv *priv;
-	int ret;
-
-	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
-	if (!priv)
-		return -ENOMEM;
-
-	priv->client = client;
-	priv->regmap = devm_cci_regmap_init_i2c(client, 16);
-	if (IS_ERR(priv->regmap)) {
-		ret = PTR_ERR(priv->regmap);
-		return dev_err_probe(dev, ret, "Failed to init regmap\n");
-	}
-
-	ret = max96717_parse_dt(priv);
-	if (ret)
-		return dev_err_probe(dev, ret, "Failed to parse the dt\n");
-
-	ret = max96717_hw_init(priv);
-	if (ret)
-		return dev_err_probe(dev, ret,
-				     "Failed to initialize the hardware\n");
-
-	ret = max96717_gpiochip_probe(priv);
-	if (ret)
-		return dev_err_probe(&client->dev, ret,
-				     "Failed to init gpiochip\n");
-
-	ret = max96717_register_clkout(priv);
-	if (ret)
-		return dev_err_probe(dev, ret, "Failed to register clkout\n");
-
-	ret = max96717_subdev_init(priv);
-	if (ret)
-		return dev_err_probe(dev, ret,
-				     "Failed to initialize v4l2 subdev\n");
-
-	ret = max96717_i2c_mux_init(priv);
-	if (ret) {
-		dev_err_probe(dev, ret, "failed to add remote i2c adapter\n");
-		max96717_subdev_uninit(priv);
-	}
-
-	return ret;
-}
-
-static void max96717_remove(struct i2c_client *client)
-{
-	struct v4l2_subdev *sd = i2c_get_clientdata(client);
-	struct max96717_priv *priv = sd_to_max96717(sd);
-
-	max96717_subdev_uninit(priv);
-	i2c_mux_del_adapters(priv->mux);
-}
-
-static const struct of_device_id max96717_of_ids[] = {
-	{ .compatible = "maxim,max96717f" },
-	{ }
-};
-MODULE_DEVICE_TABLE(of, max96717_of_ids);
-
-static struct i2c_driver max96717_i2c_driver = {
-	.driver	= {
-		.name		= "max96717",
-		.of_match_table	= max96717_of_ids,
-	},
-	.probe		= max96717_probe,
-	.remove		= max96717_remove,
-};
-
-module_i2c_driver(max96717_i2c_driver);
-
-MODULE_DESCRIPTION("Maxim GMSL2 MAX96717 Serializer Driver");
-MODULE_AUTHOR("Julien Massot <julien.massot@collabora.com>");
-MODULE_LICENSE("GPL");
-- 
2.50.1


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

* [PATCH v6 24/24] media: i2c: remove MAX96714 driver
  2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
                   ` (22 preceding siblings ...)
  2025-07-16 19:31 ` [PATCH v6 23/24] media: i2c: remove MAX96717 driver Cosmin Tanislav
@ 2025-07-16 19:31 ` Cosmin Tanislav
  23 siblings, 0 replies; 44+ messages in thread
From: Cosmin Tanislav @ 2025-07-16 19:31 UTC (permalink / raw)
  To: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Niklas Söderlund, Julien Massot, Sakari Ailus,
	Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio, Cosmin Tanislav

The previous MAX96714 driver has been removed and its functionality has
been moved to the MAX9296A driver which makes use of the Maxim GMSL2/3
serializer framework.

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
---
 MAINTAINERS                  |    1 -
 drivers/media/i2c/Kconfig    |   17 -
 drivers/media/i2c/Makefile   |    1 -
 drivers/media/i2c/max96714.c | 1017 ----------------------------------
 4 files changed, 1036 deletions(-)
 delete mode 100644 drivers/media/i2c/max96714.c

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


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

* Re: [PATCH v6 14/24] dt-bindings: media: i2c: add MAX9296A, MAX96716A, MAX96792A
  2025-07-16 19:30 ` [PATCH v6 14/24] dt-bindings: media: i2c: add MAX9296A, MAX96716A, MAX96792A Cosmin Tanislav
@ 2025-07-16 21:35   ` Rob Herring (Arm)
  0 siblings, 0 replies; 44+ messages in thread
From: Rob Herring (Arm) @ 2025-07-16 21:35 UTC (permalink / raw)
  To: Cosmin Tanislav
  Cc: Tomi Valkeinen, Mauro Carvalho Chehab, devicetree, linux-gpio,
	Niklas Söderlund, linux-media, Laurent Pinchart,
	Julien Massot, linux-staging, Greg Kroah-Hartman, Sakari Ailus,
	linux-arm-kernel, Cosmin Tanislav, Linus Walleij, linux-kernel


On Wed, 16 Jul 2025 22:30:59 +0300, Cosmin Tanislav wrote:
> The MAX9296A deserializer converts single or dual serial inputs to MIPI
> CSI-2 outputs. The GMSL2 links operate at a fixed rate of 3Gbps or 6Gbps
> in the forward direction and 187.5Mbps in the reverse direction.
> In GMSL1 mode, each serial link can be paired with 3.12Gbps or 1.5Gbps
> GMSL1 serializers or operate up to 4.5Gbps with GMSL2 serializers with
> GMSL1 backward compatibility. The MAX9296A supports mixed GMSL2 and
> GMSL1 links. The serial inputs operate independently, allowing videos
> with different timings and resolutions to be received on each input.
> 
> MAX96716A supports both tunnel and pixel mode.
> MAX96792A supports both tunnel and pixel mode, and has two GMSL3 links.
> 
> Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
> Acked-by: Rob Herring (Arm) <robh@kernel.org>
> ---
>  .../bindings/media/i2c/maxim,max9296a.yaml    | 242 ++++++++++++++++++
>  MAINTAINERS                                   |   6 +
>  2 files changed, 248 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/i2c/maxim,max9296a.yaml
> 

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/media/i2c/maxim,max9296a.example.dtb: serializer@40 (maxim,max96717): compatible: 'oneOf' conditional failed, one must be fixed:
	['maxim,max96717'] is too short
	'maxim,max96717' is not one of ['maxim,max9295a', 'maxim,max96717f']
	from schema $id: http://devicetree.org/schemas/media/i2c/maxim,max96717.yaml#
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/media/i2c/maxim,max9296a.example.dtb: serializer@40 (maxim,max96717): compatible: 'oneOf' conditional failed, one must be fixed:
	['maxim,max96717'] is too short
	'maxim,max96717' is not one of ['maxim,max9295a', 'maxim,max96717f']
	from schema $id: http://devicetree.org/schemas/media/i2c/maxim,max96717.yaml#

doc reference errors (make refcheckdocs):

See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20250716193111.942217-15-demonsingur@gmail.com

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.


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

* Re: [PATCH v6 22/24] staging: media: remove MAX96712 driver
  2025-07-16 19:31 ` [PATCH v6 22/24] staging: media: remove " Cosmin Tanislav
@ 2025-07-17 12:36   ` Niklas Söderlund
  0 siblings, 0 replies; 44+ messages in thread
From: Niklas Söderlund @ 2025-07-17 12:36 UTC (permalink / raw)
  To: Cosmin Tanislav
  Cc: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Julien Massot, Sakari Ailus, Laurent Pinchart,
	Greg Kroah-Hartman, Linus Walleij, linux-media, devicetree,
	linux-kernel, linux-arm-kernel, linux-staging, linux-gpio

Hi Cosmin,

Thanks for your work.

On 2025-07-16 22:31:07 +0300, Cosmin Tanislav wrote:
> Remove the staging MAX96712 driver.
> Its functionality has been moved to the MAX96724 driver which makes use
> of the Maxim GMSL2/3 deserializer framework.
> 
> Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>

With the new driver merged before this one being removed,

Acked-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>

> ---
>  MAINTAINERS                               |   1 -
>  drivers/staging/media/Kconfig             |   2 -
>  drivers/staging/media/Makefile            |   1 -
>  drivers/staging/media/max96712/Kconfig    |  14 -
>  drivers/staging/media/max96712/Makefile   |   2 -
>  drivers/staging/media/max96712/max96712.c | 487 ----------------------
>  6 files changed, 507 deletions(-)
>  delete mode 100644 drivers/staging/media/max96712/Kconfig
>  delete mode 100644 drivers/staging/media/max96712/Makefile
>  delete mode 100644 drivers/staging/media/max96712/max96712.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 0c75a5c195c28..ff0c7fe22ce6e 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14751,7 +14751,6 @@ M:	Cosmin Tanislav <cosmin.tanislav@analog.com>
>  L:	linux-media@vger.kernel.org
>  S:	Maintained
>  F:	Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
> -F:	drivers/staging/media/max96712/max96712.c
>  
>  MAX96714 GMSL2 DESERIALIZER DRIVER
>  M:	Julien Massot <julien.massot@collabora.com>
> diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig
> index ab250c89cd4d4..edcec810433de 100644
> --- a/drivers/staging/media/Kconfig
> +++ b/drivers/staging/media/Kconfig
> @@ -30,8 +30,6 @@ source "drivers/staging/media/ipu3/Kconfig"
>  
>  source "drivers/staging/media/ipu7/Kconfig"
>  
> -source "drivers/staging/media/max96712/Kconfig"
> -
>  source "drivers/staging/media/meson/vdec/Kconfig"
>  
>  source "drivers/staging/media/starfive/Kconfig"
> diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile
> index 4a073938b2b2d..e38421c81d0e7 100644
> --- a/drivers/staging/media/Makefile
> +++ b/drivers/staging/media/Makefile
> @@ -2,7 +2,6 @@
>  obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE)	+= deprecated/atmel/
>  obj-$(CONFIG_INTEL_ATOMISP)     += atomisp/
>  obj-$(CONFIG_VIDEO_IMX_MEDIA)	+= imx/
> -obj-$(CONFIG_VIDEO_MAX96712)	+= max96712/
>  obj-$(CONFIG_VIDEO_MESON_VDEC)	+= meson/vdec/
>  obj-$(CONFIG_VIDEO_STARFIVE_CAMSS)	+= starfive/
>  obj-$(CONFIG_VIDEO_SUNXI)	+= sunxi/
> diff --git a/drivers/staging/media/max96712/Kconfig b/drivers/staging/media/max96712/Kconfig
> deleted file mode 100644
> index 117fadf81bd08..0000000000000
> --- a/drivers/staging/media/max96712/Kconfig
> +++ /dev/null
> @@ -1,14 +0,0 @@
> -# SPDX-License-Identifier: GPL-2.0
> -config VIDEO_MAX96712
> -	tristate "Maxim MAX96712 Quad GMSL2 Deserializer support"
> -	depends on I2C
> -	depends on OF_GPIO
> -	depends on VIDEO_DEV
> -	select V4L2_FWNODE
> -	select VIDEO_V4L2_SUBDEV_API
> -	select MEDIA_CONTROLLER
> -	help
> -	  This driver supports the Maxim MAX96712 Quad GMSL2 Deserializer.
> -
> -	  To compile this driver as a module, choose M here: the
> -	  module will be called max96712.
> diff --git a/drivers/staging/media/max96712/Makefile b/drivers/staging/media/max96712/Makefile
> deleted file mode 100644
> index 70c1974ce3f05..0000000000000
> --- a/drivers/staging/media/max96712/Makefile
> +++ /dev/null
> @@ -1,2 +0,0 @@
> -# SPDX-License-Identifier: GPL-2.0
> -obj-$(CONFIG_VIDEO_MAX96712) += max96712.o
> diff --git a/drivers/staging/media/max96712/max96712.c b/drivers/staging/media/max96712/max96712.c
> deleted file mode 100644
> index 0751b2e048958..0000000000000
> --- a/drivers/staging/media/max96712/max96712.c
> +++ /dev/null
> @@ -1,487 +0,0 @@
> -// SPDX-License-Identifier: GPL-2.0
> -/*
> - * Maxim MAX96712 Quad GMSL2 Deserializer Driver
> - *
> - * Copyright (C) 2021 Renesas Electronics Corporation
> - * Copyright (C) 2021 Niklas Söderlund
> - */
> -
> -#include <linux/delay.h>
> -#include <linux/i2c.h>
> -#include <linux/module.h>
> -#include <linux/of_graph.h>
> -#include <linux/regmap.h>
> -
> -#include <media/v4l2-ctrls.h>
> -#include <media/v4l2-fwnode.h>
> -#include <media/v4l2-subdev.h>
> -
> -#define DEBUG_EXTRA_REG			0x09
> -#define DEBUG_EXTRA_PCLK_25MHZ		0x00
> -#define DEBUG_EXTRA_PCLK_75MHZ		0x01
> -
> -enum max96712_pattern {
> -	MAX96712_PATTERN_CHECKERBOARD = 0,
> -	MAX96712_PATTERN_GRADIENT,
> -};
> -
> -struct max96712_info {
> -	unsigned int dpllfreq;
> -	bool have_debug_extra;
> -};
> -
> -struct max96712_priv {
> -	struct i2c_client *client;
> -	struct regmap *regmap;
> -	struct gpio_desc *gpiod_pwdn;
> -
> -	const struct max96712_info *info;
> -
> -	bool cphy;
> -	struct v4l2_mbus_config_mipi_csi2 mipi;
> -
> -	struct v4l2_subdev sd;
> -	struct v4l2_ctrl_handler ctrl_handler;
> -	struct media_pad pads[1];
> -
> -	enum max96712_pattern pattern;
> -};
> -
> -static int max96712_write(struct max96712_priv *priv, unsigned int reg, u8 val)
> -{
> -	int ret;
> -
> -	ret = regmap_write(priv->regmap, reg, val);
> -	if (ret)
> -		dev_err(&priv->client->dev, "write 0x%04x failed\n", reg);
> -
> -	return ret;
> -}
> -
> -static int max96712_update_bits(struct max96712_priv *priv, unsigned int reg,
> -				u8 mask, u8 val)
> -{
> -	int ret;
> -
> -	ret = regmap_update_bits(priv->regmap, reg, mask, val);
> -	if (ret)
> -		dev_err(&priv->client->dev, "update 0x%04x failed\n", reg);
> -
> -	return ret;
> -}
> -
> -static int max96712_write_bulk(struct max96712_priv *priv, unsigned int reg,
> -			       const void *val, size_t val_count)
> -{
> -	int ret;
> -
> -	ret = regmap_bulk_write(priv->regmap, reg, val, val_count);
> -	if (ret)
> -		dev_err(&priv->client->dev, "bulk write 0x%04x failed\n", reg);
> -
> -	return ret;
> -}
> -
> -static int max96712_write_bulk_value(struct max96712_priv *priv,
> -				     unsigned int reg, unsigned int val,
> -				     size_t val_count)
> -{
> -	unsigned int i;
> -	u8 values[4];
> -
> -	for (i = 1; i <= val_count; i++)
> -		values[i - 1] = (val >> ((val_count - i) * 8)) & 0xff;
> -
> -	return max96712_write_bulk(priv, reg, &values, val_count);
> -}
> -
> -static void max96712_reset(struct max96712_priv *priv)
> -{
> -	max96712_update_bits(priv, 0x13, 0x40, 0x40);
> -	msleep(20);
> -}
> -
> -static void max96712_mipi_enable(struct max96712_priv *priv, bool enable)
> -{
> -	if (enable) {
> -		max96712_update_bits(priv, 0x40b, 0x02, 0x02);
> -		max96712_update_bits(priv, 0x8a0, 0x80, 0x80);
> -	} else {
> -		max96712_update_bits(priv, 0x8a0, 0x80, 0x00);
> -		max96712_update_bits(priv, 0x40b, 0x02, 0x00);
> -	}
> -}
> -
> -static void max96712_mipi_configure(struct max96712_priv *priv)
> -{
> -	unsigned int i;
> -	u8 phy5 = 0;
> -
> -	max96712_mipi_enable(priv, false);
> -
> -	/* Select 2x4 mode. */
> -	max96712_write(priv, 0x8a0, 0x04);
> -
> -	/* TODO: Add support for 2-lane and 1-lane configurations. */
> -	if (priv->cphy) {
> -		/* Configure a 3-lane C-PHY using PHY0 and PHY1. */
> -		max96712_write(priv, 0x94a, 0xa0);
> -
> -		/* Configure C-PHY timings. */
> -		max96712_write(priv, 0x8ad, 0x3f);
> -		max96712_write(priv, 0x8ae, 0x7d);
> -	} else {
> -		/* Configure a 4-lane D-PHY using PHY0 and PHY1. */
> -		max96712_write(priv, 0x94a, 0xc0);
> -	}
> -
> -	/* Configure lane mapping for PHY0 and PHY1. */
> -	/* TODO: Add support for lane swapping. */
> -	max96712_write(priv, 0x8a3, 0xe4);
> -
> -	/* Configure lane polarity for PHY0 and PHY1. */
> -	for (i = 0; i < priv->mipi.num_data_lanes + 1; i++)
> -		if (priv->mipi.lane_polarities[i])
> -			phy5 |= BIT(i == 0 ? 5 : i < 3 ? i - 1 : i);
> -	max96712_write(priv, 0x8a5, phy5);
> -
> -	/* Set link frequency for PHY0 and PHY1. */
> -	max96712_update_bits(priv, 0x415, 0x3f,
> -			     ((priv->info->dpllfreq / 100) & 0x1f) | BIT(5));
> -	max96712_update_bits(priv, 0x418, 0x3f,
> -			     ((priv->info->dpllfreq / 100) & 0x1f) | BIT(5));
> -
> -	/* Enable PHY0 and PHY1 */
> -	max96712_update_bits(priv, 0x8a2, 0xf0, 0x30);
> -}
> -
> -static void max96712_pattern_enable(struct max96712_priv *priv, bool enable)
> -{
> -	const u32 h_active = 1920;
> -	const u32 h_fp = 88;
> -	const u32 h_sw = 44;
> -	const u32 h_bp = 148;
> -	const u32 h_tot = h_active + h_fp + h_sw + h_bp;
> -
> -	const u32 v_active = 1080;
> -	const u32 v_fp = 4;
> -	const u32 v_sw = 5;
> -	const u32 v_bp = 36;
> -	const u32 v_tot = v_active + v_fp + v_sw + v_bp;
> -
> -	if (!enable) {
> -		max96712_write(priv, 0x1051, 0x00);
> -		return;
> -	}
> -
> -	/* Set PCLK to 75MHz if device have DEBUG_EXTRA register. */
> -	if (priv->info->have_debug_extra)
> -		max96712_write(priv, DEBUG_EXTRA_REG, DEBUG_EXTRA_PCLK_75MHZ);
> -
> -	/* Configure Video Timing Generator for 1920x1080 @ 30 fps. */
> -	max96712_write_bulk_value(priv, 0x1052, 0, 3);
> -	max96712_write_bulk_value(priv, 0x1055, v_sw * h_tot, 3);
> -	max96712_write_bulk_value(priv, 0x1058,
> -				  (v_active + v_fp + + v_bp) * h_tot, 3);
> -	max96712_write_bulk_value(priv, 0x105b, 0, 3);
> -	max96712_write_bulk_value(priv, 0x105e, h_sw, 2);
> -	max96712_write_bulk_value(priv, 0x1060, h_active + h_fp + h_bp, 2);
> -	max96712_write_bulk_value(priv, 0x1062, v_tot, 2);
> -	max96712_write_bulk_value(priv, 0x1064,
> -				  h_tot * (v_sw + v_bp) + (h_sw + h_bp), 3);
> -	max96712_write_bulk_value(priv, 0x1067, h_active, 2);
> -	max96712_write_bulk_value(priv, 0x1069, h_fp + h_sw + h_bp, 2);
> -	max96712_write_bulk_value(priv, 0x106b, v_active, 2);
> -
> -	/* Generate VS, HS and DE in free-running mode. */
> -	max96712_write(priv, 0x1050, 0xfb);
> -
> -	/* Configure Video Pattern Generator. */
> -	if (priv->pattern == MAX96712_PATTERN_CHECKERBOARD) {
> -		/* Set checkerboard pattern size. */
> -		max96712_write(priv, 0x1074, 0x3c);
> -		max96712_write(priv, 0x1075, 0x3c);
> -		max96712_write(priv, 0x1076, 0x3c);
> -
> -		/* Set checkerboard pattern colors. */
> -		max96712_write_bulk_value(priv, 0x106e, 0xfecc00, 3);
> -		max96712_write_bulk_value(priv, 0x1071, 0x006aa7, 3);
> -
> -		/* Generate checkerboard pattern. */
> -		max96712_write(priv, 0x1051, 0x10);
> -	} else {
> -		/* Set gradient increment. */
> -		max96712_write(priv, 0x106d, 0x10);
> -
> -		/* Generate gradient pattern. */
> -		max96712_write(priv, 0x1051, 0x20);
> -	}
> -}
> -
> -static int max96712_s_stream(struct v4l2_subdev *sd, int enable)
> -{
> -	struct max96712_priv *priv = v4l2_get_subdevdata(sd);
> -
> -	if (enable) {
> -		max96712_pattern_enable(priv, true);
> -		max96712_mipi_enable(priv, true);
> -	} else {
> -		max96712_mipi_enable(priv, false);
> -		max96712_pattern_enable(priv, false);
> -	}
> -
> -	return 0;
> -}
> -
> -static const struct v4l2_subdev_video_ops max96712_video_ops = {
> -	.s_stream = max96712_s_stream,
> -};
> -
> -static int max96712_init_state(struct v4l2_subdev *sd,
> -			       struct v4l2_subdev_state *state)
> -{
> -	static const struct v4l2_mbus_framefmt default_fmt = {
> -		.width          = 1920,
> -		.height         = 1080,
> -		.code           = MEDIA_BUS_FMT_RGB888_1X24,
> -		.colorspace     = V4L2_COLORSPACE_SRGB,
> -		.field          = V4L2_FIELD_NONE,
> -		.ycbcr_enc      = V4L2_YCBCR_ENC_DEFAULT,
> -		.quantization   = V4L2_QUANTIZATION_DEFAULT,
> -		.xfer_func      = V4L2_XFER_FUNC_DEFAULT,
> -	};
> -	struct v4l2_mbus_framefmt *fmt;
> -
> -	fmt = v4l2_subdev_state_get_format(state, 0);
> -	*fmt = default_fmt;
> -
> -	return 0;
> -}
> -
> -static const struct v4l2_subdev_internal_ops max96712_internal_ops = {
> -	.init_state = max96712_init_state,
> -};
> -
> -static const struct v4l2_subdev_pad_ops max96712_pad_ops = {
> -	.get_fmt = v4l2_subdev_get_fmt,
> -	.set_fmt = v4l2_subdev_get_fmt,
> -};
> -
> -static const struct v4l2_subdev_ops max96712_subdev_ops = {
> -	.video = &max96712_video_ops,
> -	.pad = &max96712_pad_ops,
> -};
> -
> -static const char * const max96712_test_pattern[] = {
> -	"Checkerboard",
> -	"Gradient",
> -};
> -
> -static int max96712_s_ctrl(struct v4l2_ctrl *ctrl)
> -{
> -	struct max96712_priv *priv =
> -		container_of(ctrl->handler, struct max96712_priv, ctrl_handler);
> -
> -	switch (ctrl->id) {
> -	case V4L2_CID_TEST_PATTERN:
> -		priv->pattern = ctrl->val ?
> -			MAX96712_PATTERN_GRADIENT :
> -			MAX96712_PATTERN_CHECKERBOARD;
> -		break;
> -	}
> -	return 0;
> -}
> -
> -static const struct v4l2_ctrl_ops max96712_ctrl_ops = {
> -	.s_ctrl = max96712_s_ctrl,
> -};
> -
> -static int max96712_v4l2_register(struct max96712_priv *priv)
> -{
> -	long pixel_rate;
> -	int ret;
> -
> -	priv->sd.internal_ops = &max96712_internal_ops;
> -	v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96712_subdev_ops);
> -	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> -	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> -
> -	v4l2_ctrl_handler_init(&priv->ctrl_handler, 2);
> -
> -	/*
> -	 * TODO: Once V4L2_CID_LINK_FREQ is changed from a menu control to an
> -	 * INT64 control it should be used here instead of V4L2_CID_PIXEL_RATE.
> -	 */
> -	pixel_rate = priv->info->dpllfreq / priv->mipi.num_data_lanes * 1000000;
> -	v4l2_ctrl_new_std(&priv->ctrl_handler, NULL, V4L2_CID_PIXEL_RATE,
> -			  pixel_rate, pixel_rate, 1, pixel_rate);
> -
> -	v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler, &max96712_ctrl_ops,
> -				     V4L2_CID_TEST_PATTERN,
> -				     ARRAY_SIZE(max96712_test_pattern) - 1,
> -				     0, 0, max96712_test_pattern);
> -
> -	priv->sd.ctrl_handler = &priv->ctrl_handler;
> -	ret = priv->ctrl_handler.error;
> -	if (ret)
> -		goto error;
> -
> -	priv->pads[0].flags = MEDIA_PAD_FL_SOURCE;
> -	ret = media_entity_pads_init(&priv->sd.entity, 1, priv->pads);
> -	if (ret)
> -		goto error;
> -
> -	v4l2_set_subdevdata(&priv->sd, priv);
> -
> -	priv->sd.state_lock = priv->ctrl_handler.lock;
> -	ret = v4l2_subdev_init_finalize(&priv->sd);
> -	if (ret)
> -		goto error;
> -
> -	ret = v4l2_async_register_subdev(&priv->sd);
> -	if (ret < 0) {
> -		dev_err(&priv->client->dev, "Unable to register subdevice\n");
> -		goto error;
> -	}
> -
> -	return 0;
> -error:
> -	v4l2_ctrl_handler_free(&priv->ctrl_handler);
> -
> -	return ret;
> -}
> -
> -static int max96712_parse_dt(struct max96712_priv *priv)
> -{
> -	struct fwnode_handle *ep;
> -	struct v4l2_fwnode_endpoint v4l2_ep = {
> -		.bus_type = V4L2_MBUS_UNKNOWN,
> -	};
> -	unsigned int supported_lanes;
> -	int ret;
> -
> -	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(&priv->client->dev), 4,
> -					     0, 0);
> -	if (!ep) {
> -		dev_err(&priv->client->dev, "Not connected to subdevice\n");
> -		return -EINVAL;
> -	}
> -
> -	ret = v4l2_fwnode_endpoint_parse(ep, &v4l2_ep);
> -	fwnode_handle_put(ep);
> -	if (ret) {
> -		dev_err(&priv->client->dev, "Could not parse v4l2 endpoint\n");
> -		return -EINVAL;
> -	}
> -
> -	switch (v4l2_ep.bus_type) {
> -	case V4L2_MBUS_CSI2_DPHY:
> -		supported_lanes = 4;
> -		priv->cphy = false;
> -		break;
> -	case V4L2_MBUS_CSI2_CPHY:
> -		supported_lanes = 3;
> -		priv->cphy = true;
> -		break;
> -	default:
> -		dev_err(&priv->client->dev, "Unsupported bus-type %u\n",
> -			v4l2_ep.bus_type);
> -		return -EINVAL;
> -	}
> -
> -	if (v4l2_ep.bus.mipi_csi2.num_data_lanes != supported_lanes) {
> -		dev_err(&priv->client->dev, "Only %u data lanes supported\n",
> -			supported_lanes);
> -		return -EINVAL;
> -	}
> -
> -	priv->mipi = v4l2_ep.bus.mipi_csi2;
> -
> -	return 0;
> -}
> -
> -static const struct regmap_config max96712_i2c_regmap = {
> -	.reg_bits = 16,
> -	.val_bits = 8,
> -	.max_register = 0x1f00,
> -};
> -
> -static int max96712_probe(struct i2c_client *client)
> -{
> -	struct max96712_priv *priv;
> -	int ret;
> -
> -	priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
> -	if (!priv)
> -		return -ENOMEM;
> -
> -	priv->info = of_device_get_match_data(&client->dev);
> -
> -	priv->client = client;
> -
> -	priv->regmap = devm_regmap_init_i2c(client, &max96712_i2c_regmap);
> -	if (IS_ERR(priv->regmap))
> -		return PTR_ERR(priv->regmap);
> -
> -	priv->gpiod_pwdn = devm_gpiod_get_optional(&client->dev, "enable",
> -						   GPIOD_OUT_HIGH);
> -	if (IS_ERR(priv->gpiod_pwdn))
> -		return PTR_ERR(priv->gpiod_pwdn);
> -
> -	gpiod_set_consumer_name(priv->gpiod_pwdn, "max96712-pwdn");
> -	gpiod_set_value_cansleep(priv->gpiod_pwdn, 1);
> -
> -	if (priv->gpiod_pwdn)
> -		usleep_range(4000, 5000);
> -
> -	max96712_reset(priv);
> -
> -	ret = max96712_parse_dt(priv);
> -	if (ret)
> -		return ret;
> -
> -	max96712_mipi_configure(priv);
> -
> -	return max96712_v4l2_register(priv);
> -}
> -
> -static void max96712_remove(struct i2c_client *client)
> -{
> -	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> -	struct max96712_priv *priv = container_of(sd, struct max96712_priv, sd);
> -
> -	v4l2_async_unregister_subdev(&priv->sd);
> -
> -	gpiod_set_value_cansleep(priv->gpiod_pwdn, 0);
> -}
> -
> -static const struct max96712_info max96712_info_max96712 = {
> -	.dpllfreq = 1000,
> -	.have_debug_extra = true,
> -};
> -
> -static const struct max96712_info max96712_info_max96724 = {
> -	.dpllfreq = 1200,
> -};
> -
> -static const struct of_device_id max96712_of_table[] = {
> -	{ .compatible = "maxim,max96712", .data = &max96712_info_max96712 },
> -	{ .compatible = "maxim,max96724", .data = &max96712_info_max96724 },
> -	{ /* sentinel */ }
> -};
> -MODULE_DEVICE_TABLE(of, max96712_of_table);
> -
> -static struct i2c_driver max96712_i2c_driver = {
> -	.driver	= {
> -		.name = "max96712",
> -		.of_match_table	= of_match_ptr(max96712_of_table),
> -	},
> -	.probe = max96712_probe,
> -	.remove = max96712_remove,
> -};
> -
> -module_i2c_driver(max96712_i2c_driver);
> -
> -MODULE_DESCRIPTION("Maxim MAX96712 Quad GMSL2 Deserializer Driver");
> -MODULE_AUTHOR("Niklas Söderlund <niklas.soderlund@ragnatech.se>");
> -MODULE_LICENSE("GPL");
> -- 
> 2.50.1
> 

-- 
Kind Regards,
Niklas Söderlund

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

* Re: [PATCH v6 19/24] media: i2c: maxim-serdes: add MAX96724 driver
  2025-07-16 19:31 ` [PATCH v6 19/24] media: i2c: maxim-serdes: add MAX96724 driver Cosmin Tanislav
@ 2025-07-17 12:44   ` Niklas Söderlund
  2025-07-17 20:39   ` kernel test robot
  1 sibling, 0 replies; 44+ messages in thread
From: Niklas Söderlund @ 2025-07-17 12:44 UTC (permalink / raw)
  To: Cosmin Tanislav
  Cc: Cosmin Tanislav, Tomi Valkeinen, Mauro Carvalho Chehab,
	Rob Herring, Julien Massot, Sakari Ailus, Laurent Pinchart,
	Greg Kroah-Hartman, Linus Walleij, linux-media, devicetree,
	linux-kernel, linux-arm-kernel, linux-staging, linux-gpio

Hi Cosmin,

Thanks for this work!

On 2025-07-16 22:31:04 +0300, Cosmin Tanislav wrote:
> Add a new MAX96724 driver that also supports MAX96712, MAX96724F
> and MAX96724R.
> 
> 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@gmail.com>

I have tested this on R-Car V3U that uses MAX96712 in D-PHY mode, V4H
that uses MAX96712 in C-PHY mode and V4M that uses MAX96724 in D-PHY
mode. And all my test-cases using the TPG (both patterns) pass just as
before with the driver in staging!

For the TPG usage to replace the driver in staging,

Tested-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>

Compared to my testing on v5 all dependencies for the R-Car VIN pipeline 
to use the new drivers have now been merged in media/next. I still 
needed to enable the Streams API is an experimental feature, but that is 
expected of course.

> ---
>  drivers/media/i2c/maxim-serdes/Kconfig    |   11 +
>  drivers/media/i2c/maxim-serdes/Makefile   |    1 +
>  drivers/media/i2c/maxim-serdes/max96724.c | 1183 +++++++++++++++++++++
>  3 files changed, 1195 insertions(+)
>  create mode 100644 drivers/media/i2c/maxim-serdes/max96724.c
> 
> diff --git a/drivers/media/i2c/maxim-serdes/Kconfig b/drivers/media/i2c/maxim-serdes/Kconfig
> index 648cb891eefef..2acd96cdbfa44 100644
> --- a/drivers/media/i2c/maxim-serdes/Kconfig
> +++ b/drivers/media/i2c/maxim-serdes/Kconfig
> @@ -30,3 +30,14 @@ config VIDEO_MAX96717
>  
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called max96717.
> +
> +config VIDEO_MAX96724
> +	tristate "Maxim MAX96724 Quad Deserializer support"
> +	select VIDEO_MAXIM_SERDES
> +	help
> +	  This driver supports the Maxim MAX96712, MAX96724, MAX96724F,
> +	  MAX96724R Quad Deserializers, which convert from four GMSL2
> +	  links to up to four MIPI D-PHY or C-PHY outputs.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called max96724.
> diff --git a/drivers/media/i2c/maxim-serdes/Makefile b/drivers/media/i2c/maxim-serdes/Makefile
> index 04abda6a5437a..b6d5aebfaee13 100644
> --- a/drivers/media/i2c/maxim-serdes/Makefile
> +++ b/drivers/media/i2c/maxim-serdes/Makefile
> @@ -2,3 +2,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
> diff --git a/drivers/media/i2c/maxim-serdes/max96724.c b/drivers/media/i2c/maxim-serdes/max96724.c
> new file mode 100644
> index 0000000000000..3bc2080b4dc5f
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max96724.c
> @@ -0,0 +1,1183 @@
> +// 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_CHECKER	0b01
> +#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_CHKR_COLOR_A_L			0x106e
> +#define MAX96724_CHKR_COLOR_B_L			0x1071
> +#define MAX96724_CHKR_RPT_A			0x1074
> +#define MAX96724_CHKR_RPT_B			0x1075
> +#define MAX96724_CHKR_ALT			0x1076
> +
> +#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);
> +
> +	return max96724_wait_for_device(priv);
> +}
> +
> +static int __maybe_unused 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 __maybe_unused 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)
> +{
> +	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;
> +
> +	dev_info(priv->dev, "\tvideo_lock: %u\n",
> +		 !!(val & MAX96724_VPRBS_VIDEO_LOCK));
> +
> +	mask = MAX96724_DET(index);
> +
> +	ret = regmap_read(priv->regmap, MAX96724_DE_DET, &val);
> +	if (ret)
> +		return ret;
> +
> +	dev_info(priv->dev, "\tde_det: %u\n", !!(val & mask));
> +
> +	ret = regmap_read(priv->regmap, MAX96724_HS_DET, &val);
> +	if (ret)
> +		return ret;
> +
> +	dev_info(priv->dev, "\ths_det: %u\n", !!(val & mask));
> +
> +	ret = regmap_read(priv->regmap, MAX96724_VS_DET, &val);
> +	if (ret)
> +		return ret;
> +
> +	dev_info(priv->dev, "\tvs_det: %u\n", !!(val & mask));
> +
> +	ret = regmap_read(priv->regmap, MAX96724_HS_POL, &val);
> +	if (ret)
> +		return ret;
> +
> +	dev_info(priv->dev, "\ths_pol: %u\n", !!(val & mask));
> +
> +	ret = regmap_read(priv->regmap, MAX96724_VS_POL, &val);
> +	if (ret)
> +		return ret;
> +
> +	dev_info(priv->dev, "\tvs_pol: %u\n", !!(val & mask));
> +
> +	return 0;
> +}
> +
> +static int max96724_log_phy_status(struct max_des *des,
> +				   struct max_des_phy *phy)
> +{
> +	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;
> +
> +	dev_info(priv->dev, "\tcsi2_pkt_cnt: %lu\n",
> +		 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;
> +
> +	dev_info(priv->dev, "\tphy_pkt_cnt: %lu\n",
> +		 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_serdes_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_tpg(struct max_des *des)
> +{
> +	const struct reg_sequence regs[] = {
> +		{ MAX96724_GRAD_INCR, MAX_SERDES_GRAD_INCR },
> +		REG_SEQUENCE_3_LE(MAX96724_CHKR_COLOR_A_L,
> +				  MAX_SERDES_CHECKER_COLOR_A),
> +		REG_SEQUENCE_3_LE(MAX96724_CHKR_COLOR_B_L,
> +				  MAX_SERDES_CHECKER_COLOR_B),
> +		{ MAX96724_CHKR_RPT_A, MAX_SERDES_CHECKER_SIZE },
> +		{ MAX96724_CHKR_RPT_B, MAX_SERDES_CHECKER_SIZE },
> +		{ MAX96724_CHKR_ALT, MAX_SERDES_CHECKER_SIZE },
> +	};
> +	struct max96724_priv *priv = des_to_priv(des);
> +
> +	return regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs));
> +}
> +
> +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;
> +
> +	return max96724_init_tpg(des);
> +}
> +
> +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. */
> +	for (i = 0, val = 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. */
> +	return regmap_set_bits(priv->regmap, MAX96724_DPLL_0(index),
> +			       MAX96724_DPLL_0_CONFIG_SOFT_RST_N);
> +}
> +
> +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;
> +
> +	return regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX51(index),
> +				  MAX96724_MIPI_TX51_ALT2_MEM_MAP_8,
> +				  mode->alt2_mem_map8);
> +}
> +
> +static int max96724_set_phy_enable(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_serdes_gmsl_version version)
> +{
> +	struct max96724_priv *priv = des_to_priv(des);
> +	unsigned int index = link->index;
> +	unsigned int val;
> +
> +	if (version == MAX_SERDES_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_serdes_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, u32 clock)
> +{
> +	bool patgen_clk_src = 0;
> +	u8 pclk_src;
> +	int ret;
> +
> +	switch (clock) {
> +	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;
> +	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, 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_mode(struct max96724_priv *priv, bool enable)
> +{
> +	unsigned int patgen_mode;
> +
> +	switch (priv->des.tpg_pattern) {
> +	case MAX_SERDES_TPG_PATTERN_GRADIENT:
> +		patgen_mode = MAX96724_PATGEN_1_PATGEN_MODE_GRADIENT;
> +		break;
> +	case MAX_SERDES_TPG_PATTERN_CHECKERBOARD:
> +		patgen_mode = MAX96724_PATGEN_1_PATGEN_MODE_CHECKER;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return regmap_update_bits(priv->regmap, MAX96724_PATGEN_1,
> +				  MAX96724_PATGEN_1_PATGEN_MODE,
> +				  FIELD_PREP(MAX96724_PATGEN_1_PATGEN_MODE,
> +					     enable ? patgen_mode
> +						    : MAX96724_PATGEN_1_PATGEN_MODE_DISABLED));
> +}
> +
> +static int max96724_set_tpg(struct max_des *des,
> +			    const struct max_serdes_tpg_entry *entry)
> +{
> +	struct max96724_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 = max96724_set_tpg_timings(priv, &timings);
> +	if (ret)
> +		return ret;
> +
> +	ret = max96724_set_tpg_clk(priv, timings.clock);
> +	if (ret)
> +		return ret;
> +
> +	ret = max96724_set_tpg_mode(priv, entry);
> +	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_serdes_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_SERDES_GMSL_PIXEL_MODE,
> +	.tpg_patterns = BIT(MAX_SERDES_TPG_PATTERN_CHECKERBOARD) |
> +			BIT(MAX_SERDES_TPG_PATTERN_GRADIENT),
> +	.use_atr = true,
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> +	.reg_read = max96724_reg_read,
> +	.reg_write = max96724_reg_write,
> +#endif
> +	.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_enable = max96724_set_phy_enable,
> +	.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_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_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_SERDES_GMSL_2_3GBPS),
> +	.modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) |
> +		 BIT(MAX_SERDES_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_SERDES_GMSL_2_3GBPS) |
> +		    BIT(MAX_SERDES_GMSL_2_6GBPS),
> +	.modes = BIT(MAX_SERDES_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;
> +	ops->set_pipe_tunnel_phy = priv->info->set_pipe_tunnel_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	= max96724_of_table,
> +	},
> +	.probe = max96724_probe,
> +	.remove = max96724_remove,
> +};
> +
> +module_i2c_driver(max96724_i2c_driver);
> +
> +MODULE_IMPORT_NS("MAX_SERDES");
> +MODULE_DESCRIPTION("Maxim MAX96724 Quad GMSL2 Deserializer Driver");
> +MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@analog.com>");
> +MODULE_LICENSE("GPL");
> -- 
> 2.50.1
> 

-- 
Kind Regards,
Niklas Söderlund

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

* Re: [PATCH v6 18/24] media: i2c: maxim-serdes: add MAX96717 driver
  2025-07-16 19:31 ` [PATCH v6 18/24] media: i2c: maxim-serdes: add MAX96717 driver Cosmin Tanislav
@ 2025-07-17 20:29   ` kernel test robot
  2025-07-17 21:52   ` kernel test robot
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 44+ messages in thread
From: kernel test robot @ 2025-07-17 20:29 UTC (permalink / raw)
  To: Cosmin Tanislav, Cosmin Tanislav, Tomi Valkeinen,
	Mauro Carvalho Chehab, Rob Herring, Niklas Söderlund,
	Julien Massot, Sakari Ailus, Laurent Pinchart, Greg Kroah-Hartman,
	Linus Walleij
  Cc: Paul Gazzillo, Necip Fazil Yildiran, oe-kbuild-all, linux-media,
	devicetree, linux-kernel, linux-arm-kernel, linux-staging,
	linux-gpio

Hi Cosmin,

kernel test robot noticed the following build warnings:

[auto build test WARNING on next-20250716]
[also build test WARNING on v6.16-rc6]
[cannot apply to robh/for-next staging/staging-testing staging/staging-next staging/staging-linus arm64/for-next/core linus/master v6.16-rc6 v6.16-rc5 v6.16-rc4]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Cosmin-Tanislav/media-mc-Add-INTERNAL-pad-flag/20250717-033901
base:   next-20250716
patch link:    https://lore.kernel.org/r/20250716193111.942217-19-demonsingur%40gmail.com
patch subject: [PATCH v6 18/24] media: i2c: maxim-serdes: add MAX96717 driver
config: nios2-kismet-CONFIG_GENERIC_PINCONF-CONFIG_VIDEO_MAX96717-0-0 (https://download.01.org/0day-ci/archive/20250718/202507180404.Rvs0GsdN-lkp@intel.com/config)
reproduce: (https://download.01.org/0day-ci/archive/20250718/202507180404.Rvs0GsdN-lkp@intel.com/reproduce)

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

kismet warnings: (new ones prefixed by >>)
>> kismet: WARNING: unmet direct dependencies detected for GENERIC_PINCONF when selected by VIDEO_MAX96717
   WARNING: unmet direct dependencies detected for I2C_MUX
     Depends on [n]: I2C [=n]
     Selected by [y]:
     - VIDEO_MAXIM_SERDES [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y]
   
   WARNING: unmet direct dependencies detected for GENERIC_PINCONF
     Depends on [n]: PINCTRL [=n]
     Selected by [y]:
     - VIDEO_MAX96717 [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y] && COMMON_CLK [=y]
   
   WARNING: unmet direct dependencies detected for GENERIC_PINMUX_FUNCTIONS
     Depends on [n]: PINCTRL [=n]
     Selected by [y]:
     - VIDEO_MAX96717 [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y] && COMMON_CLK [=y]
   
   WARNING: unmet direct dependencies detected for GENERIC_PINCTRL_GROUPS
     Depends on [n]: PINCTRL [=n]
     Selected by [y]:
     - VIDEO_MAX96717 [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y] && COMMON_CLK [=y]
   
   WARNING: unmet direct dependencies detected for I2C_ATR
     Depends on [n]: I2C [=n]
     Selected by [y]:
     - VIDEO_MAXIM_SERDES [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y]

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

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

* Re: [PATCH v6 19/24] media: i2c: maxim-serdes: add MAX96724 driver
  2025-07-16 19:31 ` [PATCH v6 19/24] media: i2c: maxim-serdes: add MAX96724 driver Cosmin Tanislav
  2025-07-17 12:44   ` Niklas Söderlund
@ 2025-07-17 20:39   ` kernel test robot
  1 sibling, 0 replies; 44+ messages in thread
From: kernel test robot @ 2025-07-17 20:39 UTC (permalink / raw)
  To: Cosmin Tanislav, Cosmin Tanislav, Tomi Valkeinen,
	Mauro Carvalho Chehab, Rob Herring, Niklas Söderlund,
	Julien Massot, Sakari Ailus, Laurent Pinchart, Greg Kroah-Hartman,
	Linus Walleij
  Cc: llvm, oe-kbuild-all, linux-media, devicetree, linux-kernel,
	linux-arm-kernel, linux-staging, linux-gpio

Hi Cosmin,

kernel test robot noticed the following build errors:

[auto build test ERROR on next-20250716]
[also build test ERROR on v6.16-rc6]
[cannot apply to robh/for-next staging/staging-testing staging/staging-next staging/staging-linus arm64/for-next/core linus/master v6.16-rc6 v6.16-rc5 v6.16-rc4]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Cosmin-Tanislav/media-mc-Add-INTERNAL-pad-flag/20250717-033901
base:   next-20250716
patch link:    https://lore.kernel.org/r/20250716193111.942217-20-demonsingur%40gmail.com
patch subject: [PATCH v6 19/24] media: i2c: maxim-serdes: add MAX96724 driver
config: hexagon-allmodconfig (https://download.01.org/0day-ci/archive/20250718/202507180433.nXvvwmFy-lkp@intel.com/config)
compiler: clang version 17.0.6 (https://github.com/llvm/llvm-project 6009708b4367171ccdbf4b5905cb6a803753fe18)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250718/202507180433.nXvvwmFy-lkp@intel.com/reproduce)

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

All errors (new ones prefixed by >>):

>> drivers/media/i2c/maxim-serdes/max96724.c:273:6: error: call to undeclared function 'FIELD_PREP'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     273 |                                  FIELD_PREP(MAX96724_PWR1_RESET_ALL, 1));
         |                                  ^
   drivers/media/i2c/maxim-serdes/max96724.c:494:6: error: call to undeclared function 'FIELD_PREP'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     494 |                                  FIELD_PREP(MAX96724_MIPI_TX10_CSI2_LANE_CNT,
         |                                  ^
   drivers/media/i2c/maxim-serdes/max96724.c:693:7: error: call to undeclared function 'FIELD_PREP'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     693 |                            FIELD_PREP(MAX96724_MIPI_TX13_MAP_SRC_DT,
         |                            ^
   drivers/media/i2c/maxim-serdes/max96724.c:741:7: error: call to undeclared function 'FIELD_PREP'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     741 |                                   FIELD_PREP(MAX96724_MIPI_TX57_TUN_DEST,
         |                                   ^
   drivers/media/i2c/maxim-serdes/max96724.c:914:8: error: call to undeclared function 'FIELD_PREP'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     914 |                             FIELD_PREP(MAX96724_PATGEN_0_VTG_MODE,
         |                             ^
   drivers/media/i2c/maxim-serdes/max96724.c:957:6: error: call to undeclared function 'FIELD_PREP'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     957 |                                  FIELD_PREP(MAX96724_VPRBS_PATGEN_CLK_SRC,
         |                                  ^
   drivers/media/i2c/maxim-serdes/max96724.c:985:7: error: call to undeclared function 'FIELD_PREP'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     985 |                                   FIELD_PREP(MAX96724_PATGEN_1_PATGEN_MODE,
         |                                   ^
   7 errors generated.


vim +/FIELD_PREP +273 drivers/media/i2c/maxim-serdes/max96724.c

   262	
   263	static int max96724_reset(struct max96724_priv *priv)
   264	{
   265		int ret;
   266	
   267		ret = max96724_wait_for_device(priv);
   268		if (ret)
   269			return ret;
   270	
   271		ret = regmap_update_bits(priv->regmap, MAX96724_PWR1,
   272					 MAX96724_PWR1_RESET_ALL,
 > 273					 FIELD_PREP(MAX96724_PWR1_RESET_ALL, 1));
   274		if (ret)
   275			return ret;
   276	
   277		fsleep(10000);
   278	
   279		return max96724_wait_for_device(priv);
   280	}
   281	

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

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

* Re: [PATCH v6 18/24] media: i2c: maxim-serdes: add MAX96717 driver
  2025-07-16 19:31 ` [PATCH v6 18/24] media: i2c: maxim-serdes: add MAX96717 driver Cosmin Tanislav
  2025-07-17 20:29   ` kernel test robot
@ 2025-07-17 21:52   ` kernel test robot
  2025-07-17 23:13   ` kernel test robot
  2025-07-18  1:37   ` kernel test robot
  3 siblings, 0 replies; 44+ messages in thread
From: kernel test robot @ 2025-07-17 21:52 UTC (permalink / raw)
  To: Cosmin Tanislav, Cosmin Tanislav, Tomi Valkeinen,
	Mauro Carvalho Chehab, Rob Herring, Niklas Söderlund,
	Julien Massot, Sakari Ailus, Laurent Pinchart, Greg Kroah-Hartman,
	Linus Walleij
  Cc: Paul Gazzillo, Necip Fazil Yildiran, oe-kbuild-all, linux-media,
	devicetree, linux-kernel, linux-arm-kernel, linux-staging,
	linux-gpio

Hi Cosmin,

kernel test robot noticed the following build warnings:

[auto build test WARNING on next-20250716]
[also build test WARNING on v6.16-rc6]
[cannot apply to robh/for-next staging/staging-testing staging/staging-next staging/staging-linus arm64/for-next/core linus/master v6.16-rc6 v6.16-rc5 v6.16-rc4]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Cosmin-Tanislav/media-mc-Add-INTERNAL-pad-flag/20250717-033901
base:   next-20250716
patch link:    https://lore.kernel.org/r/20250716193111.942217-19-demonsingur%40gmail.com
patch subject: [PATCH v6 18/24] media: i2c: maxim-serdes: add MAX96717 driver
config: nios2-kismet-CONFIG_GENERIC_PINCTRL_GROUPS-CONFIG_VIDEO_MAX96717-0-0 (https://download.01.org/0day-ci/archive/20250718/202507180543.aUfFOLJo-lkp@intel.com/config)
reproduce: (https://download.01.org/0day-ci/archive/20250718/202507180543.aUfFOLJo-lkp@intel.com/reproduce)

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

kismet warnings: (new ones prefixed by >>)
>> kismet: WARNING: unmet direct dependencies detected for GENERIC_PINCTRL_GROUPS when selected by VIDEO_MAX96717
   WARNING: unmet direct dependencies detected for GENERIC_PINCONF
     Depends on [n]: PINCTRL [=n]
     Selected by [y]:
     - VIDEO_MAX96717 [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y] && COMMON_CLK [=y]
   
   WARNING: unmet direct dependencies detected for GENERIC_PINMUX_FUNCTIONS
     Depends on [n]: PINCTRL [=n]
     Selected by [y]:
     - VIDEO_MAX96717 [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y] && COMMON_CLK [=y]
   
   WARNING: unmet direct dependencies detected for GENERIC_PINCTRL_GROUPS
     Depends on [n]: PINCTRL [=n]
     Selected by [y]:
     - VIDEO_MAX96717 [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y] && COMMON_CLK [=y]

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

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

* Re: [PATCH v6 18/24] media: i2c: maxim-serdes: add MAX96717 driver
  2025-07-16 19:31 ` [PATCH v6 18/24] media: i2c: maxim-serdes: add MAX96717 driver Cosmin Tanislav
  2025-07-17 20:29   ` kernel test robot
  2025-07-17 21:52   ` kernel test robot
@ 2025-07-17 23:13   ` kernel test robot
  2025-07-18  1:37   ` kernel test robot
  3 siblings, 0 replies; 44+ messages in thread
From: kernel test robot @ 2025-07-17 23:13 UTC (permalink / raw)
  To: Cosmin Tanislav, Cosmin Tanislav, Tomi Valkeinen,
	Mauro Carvalho Chehab, Rob Herring, Niklas Söderlund,
	Julien Massot, Sakari Ailus, Laurent Pinchart, Greg Kroah-Hartman,
	Linus Walleij
  Cc: Paul Gazzillo, Necip Fazil Yildiran, oe-kbuild-all, linux-media,
	devicetree, linux-kernel, linux-arm-kernel, linux-staging,
	linux-gpio

Hi Cosmin,

kernel test robot noticed the following build warnings:

[auto build test WARNING on next-20250716]
[also build test WARNING on v6.16-rc6]
[cannot apply to robh/for-next staging/staging-testing staging/staging-next staging/staging-linus arm64/for-next/core linus/master v6.16-rc6 v6.16-rc5 v6.16-rc4]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Cosmin-Tanislav/media-mc-Add-INTERNAL-pad-flag/20250717-033901
base:   next-20250716
patch link:    https://lore.kernel.org/r/20250716193111.942217-19-demonsingur%40gmail.com
patch subject: [PATCH v6 18/24] media: i2c: maxim-serdes: add MAX96717 driver
config: nios2-kismet-CONFIG_GENERIC_PINMUX_FUNCTIONS-CONFIG_VIDEO_MAX96717-0-0 (https://download.01.org/0day-ci/archive/20250718/202507180700.ACATL5wj-lkp@intel.com/config)
reproduce: (https://download.01.org/0day-ci/archive/20250718/202507180700.ACATL5wj-lkp@intel.com/reproduce)

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

kismet warnings: (new ones prefixed by >>)
>> kismet: WARNING: unmet direct dependencies detected for GENERIC_PINMUX_FUNCTIONS when selected by VIDEO_MAX96717
   WARNING: unmet direct dependencies detected for I2C_MUX
     Depends on [n]: I2C [=n]
     Selected by [y]:
     - VIDEO_MAXIM_SERDES [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y]
   
   WARNING: unmet direct dependencies detected for GENERIC_PINCONF
     Depends on [n]: PINCTRL [=n]
     Selected by [y]:
     - VIDEO_MAX96717 [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y] && COMMON_CLK [=y]
   
   WARNING: unmet direct dependencies detected for GENERIC_PINMUX_FUNCTIONS
     Depends on [n]: PINCTRL [=n]
     Selected by [y]:
     - VIDEO_MAX96717 [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y] && COMMON_CLK [=y]
   
   WARNING: unmet direct dependencies detected for GENERIC_PINCTRL_GROUPS
     Depends on [n]: PINCTRL [=n]
     Selected by [y]:
     - VIDEO_MAX96717 [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y] && COMMON_CLK [=y]
   
   WARNING: unmet direct dependencies detected for I2C_ATR
     Depends on [n]: I2C [=n]
     Selected by [y]:
     - VIDEO_MAXIM_SERDES [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y]

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

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

* Re: [PATCH v6 15/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer framework
  2025-07-16 19:31 ` [PATCH v6 15/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer framework Cosmin Tanislav
@ 2025-07-18  0:25   ` kernel test robot
  2025-07-18  1:37   ` kernel test robot
  1 sibling, 0 replies; 44+ messages in thread
From: kernel test robot @ 2025-07-18  0:25 UTC (permalink / raw)
  To: Cosmin Tanislav, Cosmin Tanislav, Tomi Valkeinen,
	Mauro Carvalho Chehab, Rob Herring, Niklas Söderlund,
	Julien Massot, Sakari Ailus, Laurent Pinchart, Greg Kroah-Hartman,
	Linus Walleij
  Cc: Paul Gazzillo, Necip Fazil Yildiran, oe-kbuild-all, linux-media,
	devicetree, linux-kernel, linux-arm-kernel, linux-staging,
	linux-gpio

Hi Cosmin,

kernel test robot noticed the following build warnings:

[auto build test WARNING on next-20250716]
[also build test WARNING on v6.16-rc6]
[cannot apply to robh/for-next staging/staging-testing staging/staging-next staging/staging-linus arm64/for-next/core linus/master v6.16-rc6 v6.16-rc5 v6.16-rc4]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Cosmin-Tanislav/media-mc-Add-INTERNAL-pad-flag/20250717-033901
base:   next-20250716
patch link:    https://lore.kernel.org/r/20250716193111.942217-16-demonsingur%40gmail.com
patch subject: [PATCH v6 15/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer framework
config: nios2-kismet-CONFIG_I2C_ATR-CONFIG_VIDEO_MAXIM_SERDES-0-0 (https://download.01.org/0day-ci/archive/20250718/202507180835.07NwogM6-lkp@intel.com/config)
reproduce: (https://download.01.org/0day-ci/archive/20250718/202507180835.07NwogM6-lkp@intel.com/reproduce)

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

kismet warnings: (new ones prefixed by >>)
>> kismet: WARNING: unmet direct dependencies detected for I2C_ATR when selected by VIDEO_MAXIM_SERDES
   WARNING: unmet direct dependencies detected for I2C_MUX
     Depends on [n]: I2C [=n]
     Selected by [y]:
     - VIDEO_MAXIM_SERDES [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y]
   
   WARNING: unmet direct dependencies detected for I2C_ATR
     Depends on [n]: I2C [=n]
     Selected by [y]:
     - VIDEO_MAXIM_SERDES [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y]

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

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

* Re: [PATCH v6 18/24] media: i2c: maxim-serdes: add MAX96717 driver
  2025-07-16 19:31 ` [PATCH v6 18/24] media: i2c: maxim-serdes: add MAX96717 driver Cosmin Tanislav
                     ` (2 preceding siblings ...)
  2025-07-17 23:13   ` kernel test robot
@ 2025-07-18  1:37   ` kernel test robot
  3 siblings, 0 replies; 44+ messages in thread
From: kernel test robot @ 2025-07-18  1:37 UTC (permalink / raw)
  To: Cosmin Tanislav, Cosmin Tanislav, Tomi Valkeinen,
	Mauro Carvalho Chehab, Rob Herring, Niklas Söderlund,
	Julien Massot, Sakari Ailus, Laurent Pinchart, Greg Kroah-Hartman,
	Linus Walleij
  Cc: oe-kbuild-all, linux-media, devicetree, linux-kernel,
	linux-arm-kernel, linux-staging, linux-gpio

Hi Cosmin,

kernel test robot noticed the following build errors:

[auto build test ERROR on next-20250716]
[cannot apply to robh/for-next staging/staging-testing staging/staging-next staging/staging-linus arm64/for-next/core linus/master v6.16-rc6 v6.16-rc5 v6.16-rc4]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Cosmin-Tanislav/media-mc-Add-INTERNAL-pad-flag/20250717-033901
base:   next-20250716
patch link:    https://lore.kernel.org/r/20250716193111.942217-19-demonsingur%40gmail.com
patch subject: [PATCH v6 18/24] media: i2c: maxim-serdes: add MAX96717 driver
config: x86_64-randconfig-003-20250717 (https://download.01.org/0day-ci/archive/20250718/202507180929.p1tVAhva-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14+deb12u1) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250718/202507180929.p1tVAhva-lkp@intel.com/reproduce)

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

All errors (new ones prefixed by >>):

   ld: drivers/media/i2c/maxim-serdes/max96717.o: in function `max96717_conf_pin_config_get':
>> drivers/media/i2c/maxim-serdes/max96717.c:485: undefined reference to `pinctrl_dev_get_drvdata'
   ld: drivers/media/i2c/maxim-serdes/max96717.o: in function `max96717_conf_pin_config_set':
   drivers/media/i2c/maxim-serdes/max96717.c:655: undefined reference to `pinctrl_dev_get_drvdata'
   ld: drivers/media/i2c/maxim-serdes/max96717.o: in function `max96717_mux_set':
   drivers/media/i2c/maxim-serdes/max96717.c:712: undefined reference to `pinctrl_dev_get_drvdata'
   ld: drivers/media/i2c/maxim-serdes/max96717.o: in function `max96717_gpiochip_probe':
>> drivers/media/i2c/maxim-serdes/max96717.c:1553: undefined reference to `devm_pinctrl_register_and_init'
>> ld: drivers/media/i2c/maxim-serdes/max96717.c:1557: undefined reference to `pinctrl_enable'
   ld: drivers/media/i2c/maxim-serdes/max96717.o: in function `pinconf_generic_dt_node_to_map_pin':
>> include/linux/pinctrl/pinconf-generic.h:219: undefined reference to `pinconf_generic_dt_node_to_map'
>> ld: drivers/media/i2c/maxim-serdes/max96717.o:(.rodata+0xa68): undefined reference to `pinconf_generic_dt_free_map'

Kconfig warnings: (for reference only)
   WARNING: unmet direct dependencies detected for GENERIC_PINCONF
   Depends on [n]: PINCTRL [=n]
   Selected by [y]:
   - VIDEO_MAX96717 [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y] && COMMON_CLK [=y]
   WARNING: unmet direct dependencies detected for GENERIC_PINMUX_FUNCTIONS
   Depends on [n]: PINCTRL [=n]
   Selected by [y]:
   - VIDEO_MAX96717 [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y] && COMMON_CLK [=y]
   WARNING: unmet direct dependencies detected for GENERIC_PINCTRL_GROUPS
   Depends on [n]: PINCTRL [=n]
   Selected by [y]:
   - VIDEO_MAX96717 [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y] && COMMON_CLK [=y]


vim +485 drivers/media/i2c/maxim-serdes/max96717.c

   480	
   481	static int max96717_conf_pin_config_get(struct pinctrl_dev *pctldev,
   482						unsigned int offset,
   483						unsigned long *config)
   484	{
 > 485		struct max96717_priv *priv = pinctrl_dev_get_drvdata(pctldev);
   486		u32 param = pinconf_to_config_param(*config);
   487		unsigned int reg, mask, val, en_val;
   488		int ret;
   489	
   490		ret = max96717_get_pin_config_reg(offset, param, &reg, &mask, &en_val);
   491		if (ret)
   492			return ret;
   493	
   494		switch (param) {
   495		case PIN_CONFIG_DRIVE_OPEN_DRAIN:
   496		case PIN_CONFIG_DRIVE_PUSH_PULL:
   497		case PIN_CONFIG_BIAS_DISABLE:
   498		case PIN_CONFIG_BIAS_PULL_DOWN:
   499		case PIN_CONFIG_BIAS_PULL_UP:
   500		case MAX96717_PINCTRL_JITTER_COMPENSATION_EN:
   501		case MAX96717_PINCTRL_TX_EN:
   502		case MAX96717_PINCTRL_RX_EN:
   503		case PIN_CONFIG_OUTPUT_ENABLE:
   504		case PIN_CONFIG_INPUT_ENABLE:
   505			ret = regmap_read(priv->regmap, reg, &val);
   506			if (ret)
   507				return ret;
   508	
   509			val = field_get(mask, val) == en_val;
   510			if (!val)
   511				return -EINVAL;
   512	
   513			break;
   514		case MAX96717_PINCTRL_PULL_STRENGTH_HIGH:
   515		case MAX96717_PINCTRL_INPUT_VALUE:
   516		case PIN_CONFIG_OUTPUT:
   517			ret = regmap_read(priv->regmap, reg, &val);
   518			if (ret)
   519				return ret;
   520	
   521			val = field_get(mask, val) == en_val;
   522			break;
   523		case MAX96717_PINCTRL_TX_ID:
   524		case MAX96717_PINCTRL_RX_ID:
   525		case PIN_CONFIG_SLEW_RATE:
   526			ret = regmap_read(priv->regmap, reg, &val);
   527			if (ret)
   528				return ret;
   529	
   530			val = field_get(mask, val);
   531			break;
   532		default:
   533			return -ENOTSUPP;
   534		}
   535	
   536		switch (param) {
   537		case PIN_CONFIG_BIAS_PULL_DOWN:
   538		case PIN_CONFIG_BIAS_PULL_UP:
   539			*config = pinconf_to_config_packed(MAX96717_PINCTRL_PULL_STRENGTH_HIGH, 0);
   540	
   541			ret = max96717_conf_pin_config_get(pctldev, offset, config);
   542			if (ret)
   543				return ret;
   544	
   545			val = pinconf_to_config_argument(*config);
   546			if (val)
   547				val = MAX96717_BIAS_PULL_STRENGTH_1000000_OHM;
   548			else
   549				val = MAX96717_BIAS_PULL_STRENGTH_40000_OHM;
   550	
   551			break;
   552		case MAX96717_PINCTRL_TX_ID:
   553			*config = pinconf_to_config_packed(MAX96717_PINCTRL_TX_EN, 0);
   554	
   555			ret = max96717_conf_pin_config_get(pctldev, offset, config);
   556			if (ret)
   557				return ret;
   558	
   559			break;
   560		case MAX96717_PINCTRL_RX_ID:
   561			*config = pinconf_to_config_packed(MAX96717_PINCTRL_RX_EN, 0);
   562	
   563			ret = max96717_conf_pin_config_get(pctldev, offset, config);
   564			if (ret)
   565				return ret;
   566	
   567			break;
   568		default:
   569			break;
   570		}
   571	
   572		*config = pinconf_to_config_packed(param, val);
   573	
   574		return 0;
   575	}
   576	

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

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

* Re: [PATCH v6 15/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer framework
  2025-07-16 19:31 ` [PATCH v6 15/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer framework Cosmin Tanislav
  2025-07-18  0:25   ` kernel test robot
@ 2025-07-18  1:37   ` kernel test robot
  1 sibling, 0 replies; 44+ messages in thread
From: kernel test robot @ 2025-07-18  1:37 UTC (permalink / raw)
  To: Cosmin Tanislav, Cosmin Tanislav, Tomi Valkeinen,
	Mauro Carvalho Chehab, Rob Herring, Niklas Söderlund,
	Julien Massot, Sakari Ailus, Laurent Pinchart, Greg Kroah-Hartman,
	Linus Walleij
  Cc: Paul Gazzillo, Necip Fazil Yildiran, oe-kbuild-all, linux-media,
	devicetree, linux-kernel, linux-arm-kernel, linux-staging,
	linux-gpio

Hi Cosmin,

kernel test robot noticed the following build warnings:

[auto build test WARNING on next-20250716]
[also build test WARNING on v6.16-rc6]
[cannot apply to robh/for-next staging/staging-testing staging/staging-next staging/staging-linus arm64/for-next/core linus/master v6.16-rc6 v6.16-rc5 v6.16-rc4]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Cosmin-Tanislav/media-mc-Add-INTERNAL-pad-flag/20250717-033901
base:   next-20250716
patch link:    https://lore.kernel.org/r/20250716193111.942217-16-demonsingur%40gmail.com
patch subject: [PATCH v6 15/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer framework
config: nios2-kismet-CONFIG_I2C_MUX-CONFIG_VIDEO_MAXIM_SERDES-0-0 (https://download.01.org/0day-ci/archive/20250718/202507180909.8Mnk3jkp-lkp@intel.com/config)
reproduce: (https://download.01.org/0day-ci/archive/20250718/202507180909.8Mnk3jkp-lkp@intel.com/reproduce)

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

kismet warnings: (new ones prefixed by >>)
>> kismet: WARNING: unmet direct dependencies detected for I2C_MUX when selected by VIDEO_MAXIM_SERDES
   WARNING: unmet direct dependencies detected for I2C_MUX
     Depends on [n]: I2C [=n]
     Selected by [y]:
     - VIDEO_MAXIM_SERDES [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y]
   
   WARNING: unmet direct dependencies detected for I2C_ATR
     Depends on [n]: I2C [=n]
     Selected by [y]:
     - VIDEO_MAXIM_SERDES [=y] && MEDIA_SUPPORT [=y] && VIDEO_DEV [=y]

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

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

* Re: [PATCH v6 20/24] media: i2c: maxim-serdes: add MAX9296A driver
  2025-07-16 19:31 ` [PATCH v6 20/24] media: i2c: maxim-serdes: add MAX9296A driver Cosmin Tanislav
@ 2025-07-18  5:23   ` kernel test robot
  0 siblings, 0 replies; 44+ messages in thread
From: kernel test robot @ 2025-07-18  5:23 UTC (permalink / raw)
  To: Cosmin Tanislav, Cosmin Tanislav, Tomi Valkeinen,
	Mauro Carvalho Chehab, Rob Herring, Niklas Söderlund,
	Julien Massot, Sakari Ailus, Laurent Pinchart, Greg Kroah-Hartman,
	Linus Walleij
  Cc: oe-kbuild-all, linux-media, devicetree, linux-kernel,
	linux-arm-kernel, linux-staging, linux-gpio

Hi Cosmin,

kernel test robot noticed the following build warnings:

[auto build test WARNING on next-20250716]
[also build test WARNING on v6.16-rc6]
[cannot apply to robh/for-next staging/staging-testing staging/staging-next staging/staging-linus arm64/for-next/core linus/master v6.16-rc6 v6.16-rc5 v6.16-rc4]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Cosmin-Tanislav/media-mc-Add-INTERNAL-pad-flag/20250717-033901
base:   next-20250716
patch link:    https://lore.kernel.org/r/20250716193111.942217-21-demonsingur%40gmail.com
patch subject: [PATCH v6 20/24] media: i2c: maxim-serdes: add MAX9296A driver
config: i386-randconfig-063-20250718 (https://download.01.org/0day-ci/archive/20250718/202507181514.fwoTNxka-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14+deb12u1) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250718/202507181514.fwoTNxka-lkp@intel.com/reproduce)

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

sparse warnings: (new ones prefixed by >>)
>> drivers/media/i2c/maxim-serdes/max9296a.c:1205:27: sparse: sparse: symbol 'max96714_rlms_reg_sequence' was not declared. Should it be static?

vim +/max96714_rlms_reg_sequence +1205 drivers/media/i2c/maxim-serdes/max9296a.c

  1199	
  1200	/*
  1201	 * These register writes are described as required in MAX96714 datasheet
  1202	 * Page 53, Section Register Map, to optimize link performance in 6Gbps
  1203	 * and 3Gbps links for all cable lengths.
  1204	 */
> 1205	const struct reg_sequence max96714_rlms_reg_sequence[] = {
  1206		{ MAX9296A_RLMS3E(0), 0xfd },
  1207		{ MAX9296A_RLMS3F(0), 0x3d },
  1208		{ MAX9296A_RLMS49(0), 0xf5 },
  1209		{ MAX9296A_RLMS7E(0), 0xa8 },
  1210		{ MAX9296A_RLMS7F(0), 0x68 },
  1211		{ MAX9296A_RLMSA3(0), 0x30 },
  1212		{ MAX9296A_RLMSA5(0), 0x70 },
  1213		{ MAX9296A_RLMSD8(0), 0x07 },
  1214	};
  1215	

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

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

* Re: [PATCH v6 02/24] dt-bindings: media: i2c: max96717: add myself as maintainer
  2025-07-16 19:30 ` [PATCH v6 02/24] dt-bindings: media: i2c: max96717: add myself as maintainer Cosmin Tanislav
@ 2025-08-01 12:17   ` Julien Massot
  0 siblings, 0 replies; 44+ messages in thread
From: Julien Massot @ 2025-08-01 12:17 UTC (permalink / raw)
  To: Cosmin Tanislav, Cosmin Tanislav, Tomi Valkeinen,
	Mauro Carvalho Chehab, Rob Herring, Niklas Söderlund,
	Sakari Ailus, Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio

Hi Cosmin,

On Wed, 2025-07-16 at 22:30 +0300, Cosmin Tanislav wrote:
> Analog Devices is taking responsability for the maintenance of the Maxim
> GMSL2/3 devices.
> Add myself to the maintainers list and to the device tree bindings.
> 
> Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
> Acked-by: Rob Herring (Arm) <robh@kernel.org>
> ---
>  Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml | 1 +
>  MAINTAINERS                                                     | 1 +
>  2 files changed, 2 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
> b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
> index d1e8ba6e368ec..15ab37702a927 100644
> --- a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
> +++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
> @@ -9,6 +9,7 @@ title: MAX96717 CSI-2 to GMSL2 Serializer
>  
>  maintainers:
>    - Julien Massot <julien.massot@collabora.com>
> +  - Cosmin Tanislav <cosmin.tanislav@analog.com>
>  
>  description:
>    The MAX96717 serializer converts MIPI CSI-2 D-PHY formatted input
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 24c557ee091d7..e973b0a985815 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14761,6 +14761,7 @@ F:	drivers/media/i2c/max96714.c
>  
>  MAX96717 GMSL2 SERIALIZER DRIVER
>  M:	Julien Massot <julien.massot@collabora.com>
> +M:	Cosmin Tanislav <cosmin.tanislav@analog.com>
>  L:	linux-media@vger.kernel.org
>  S:	Maintained
>  F:	Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml

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

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

* Re: [PATCH v6 03/24] dt-bindings: media: i2c: max96717: add support for I2C ATR
  2025-07-16 19:30 ` [PATCH v6 03/24] dt-bindings: media: i2c: max96717: add support for I2C ATR Cosmin Tanislav
@ 2025-08-01 12:19   ` Julien Massot
  0 siblings, 0 replies; 44+ messages in thread
From: Julien Massot @ 2025-08-01 12:19 UTC (permalink / raw)
  To: Cosmin Tanislav, Cosmin Tanislav, Tomi Valkeinen,
	Mauro Carvalho Chehab, Rob Herring, Niklas Söderlund,
	Sakari Ailus, Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio

On Wed, 2025-07-16 at 22:30 +0300, Cosmin Tanislav wrote:
> MAX96717 is capable of address translation for the connected I2C slaves.
> 
> Add support for I2C ATR while keeping I2C gate for compatibility to
> support this usecase.
> 
> Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
> Acked-by: Rob Herring (Arm) <robh@kernel.org>
> ---
>  .../bindings/media/i2c/maxim,max96717.yaml    | 39 +++++++++++++++++++
>  1 file changed, 39 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
> b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
> index 15ab37702a927..167c3dd50683c 100644
> --- a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
> +++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
> @@ -92,6 +92,30 @@ properties:
>        incoming GMSL2 link. Therefore, it supports an i2c-gate
>        subnode to configure a sensor.
>  
> +  i2c-alias-pool:
> +    maxItems: 2
> +
> +  i2c-atr:
> +    type: object
> +    additionalProperties: false
> +
> +    properties:
> +      '#address-cells':
> +        const: 1
> +
> +      '#size-cells':
> +        const: 0
> +
> +    patternProperties:
> +      '^i2c@[01]$':
> +        $ref: /schemas/i2c/i2c-controller.yaml#
> +        unevaluatedProperties: false
> +        properties:
> +          reg:
> +            items:
> +              minimum: 0
> +              maximum: 1
> +
>  required:
>    - compatible
>    - reg
> @@ -99,6 +123,21 @@ required:
>  
>  additionalProperties: false
>  
> +allOf:
> +  - $ref: /schemas/i2c/i2c-atr.yaml#
> +
> +  - anyOf:
> +      - oneOf:
> +          - required: [i2c-atr]
> +          - required: [i2c-gate]
> +
> +      - not:
> +          required: [i2c-atr, i2c-gate]
> +
> +dependentRequired:
> +  i2c-atr: [i2c-alias-pool]
> +  i2c-alias-pool: [i2c-atr]
> +
>  examples:
>    - |
>      #include <dt-bindings/gpio/gpio.h>

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

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

* Re: [PATCH v6 04/24] dt-bindings: media: i2c: max96717: add support for pinctrl/pinconf
  2025-07-16 19:30 ` [PATCH v6 04/24] dt-bindings: media: i2c: max96717: add support for pinctrl/pinconf Cosmin Tanislav
@ 2025-08-01 12:20   ` Julien Massot
  0 siblings, 0 replies; 44+ messages in thread
From: Julien Massot @ 2025-08-01 12:20 UTC (permalink / raw)
  To: Cosmin Tanislav, Cosmin Tanislav, Tomi Valkeinen,
	Mauro Carvalho Chehab, Rob Herring, Niklas Söderlund,
	Sakari Ailus, Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio

On Wed, 2025-07-16 at 22:30 +0300, Cosmin Tanislav wrote:
> MAX96717 is capable of configuring various pin properties.
> 
> Add pinctrl/pinconf properties to support this usecase.
> 
> Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
> Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
> 

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

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

* Re: [PATCH v6 05/24] dt-bindings: media: i2c: max96717: add support for MAX9295A
  2025-07-16 19:30 ` [PATCH v6 05/24] dt-bindings: media: i2c: max96717: add support for MAX9295A Cosmin Tanislav
@ 2025-08-01 12:21   ` Julien Massot
  2025-08-01 12:34   ` Julien Massot
  1 sibling, 0 replies; 44+ messages in thread
From: Julien Massot @ 2025-08-01 12:21 UTC (permalink / raw)
  To: Cosmin Tanislav, Cosmin Tanislav, Tomi Valkeinen,
	Mauro Carvalho Chehab, Rob Herring, Niklas Söderlund,
	Sakari Ailus, Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio

On Wed, 2025-07-16 at 22:30 +0300, Cosmin Tanislav wrote:
> MAX9295A is an older variant of the MAX96717 which does not support
> tunnel mode.
> 
> Document the compatibility.
> 
> Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
> Acked-by: Rob Herring (Arm) <robh@kernel.org>

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

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

* Re: [PATCH v6 12/24] dt-bindings: media: i2c: max96714: add myself as maintainer
  2025-07-16 19:30 ` [PATCH v6 12/24] dt-bindings: media: i2c: max96714: add myself as maintainer Cosmin Tanislav
@ 2025-08-01 12:22   ` Julien Massot
  0 siblings, 0 replies; 44+ messages in thread
From: Julien Massot @ 2025-08-01 12:22 UTC (permalink / raw)
  To: Cosmin Tanislav, Cosmin Tanislav, Tomi Valkeinen,
	Mauro Carvalho Chehab, Rob Herring, Niklas Söderlund,
	Sakari Ailus, Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio

On Wed, 2025-07-16 at 22:30 +0300, Cosmin Tanislav wrote:
> Analog Devices is taking responsability for the maintenance of the Maxim
> GMSL2/3 devices.
> Add myself to the maintainers list and to the device tree bindings.
> 
> Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
> Acked-by: Rob Herring (Arm) <robh@kernel.org>
> 
> 
Reviewed-by: Julien Massot <julien.massot@collabora.com>

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

* Re: [PATCH v6 13/24] dt-bindings: media: i2c: max96714: add support for MAX96714R
  2025-07-16 19:30 ` [PATCH v6 13/24] dt-bindings: media: i2c: max96714: add support for MAX96714R Cosmin Tanislav
@ 2025-08-01 12:23   ` Julien Massot
  0 siblings, 0 replies; 44+ messages in thread
From: Julien Massot @ 2025-08-01 12:23 UTC (permalink / raw)
  To: Cosmin Tanislav, Cosmin Tanislav, Tomi Valkeinen,
	Mauro Carvalho Chehab, Rob Herring, Niklas Söderlund,
	Sakari Ailus, Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio

On Wed, 2025-07-16 at 22:30 +0300, Cosmin Tanislav wrote:
> MAX96714R is a lower capability variant of the MAX96714 which only
> supports a fixed rate of 3Gbps in the forward direction.
> 
> Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
> Acked-by: Rob Herring (Arm) <robh@kernel.org>
> 
> 
Reviewed-by: Julien Massot <julien.massot@collabora.com>

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

* Re: [PATCH v6 05/24] dt-bindings: media: i2c: max96717: add support for MAX9295A
  2025-07-16 19:30 ` [PATCH v6 05/24] dt-bindings: media: i2c: max96717: add support for MAX9295A Cosmin Tanislav
  2025-08-01 12:21   ` Julien Massot
@ 2025-08-01 12:34   ` Julien Massot
  1 sibling, 0 replies; 44+ messages in thread
From: Julien Massot @ 2025-08-01 12:34 UTC (permalink / raw)
  To: Cosmin Tanislav, Cosmin Tanislav, Tomi Valkeinen,
	Mauro Carvalho Chehab, Rob Herring, Niklas Söderlund,
	Sakari Ailus, Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio

On Wed, 2025-07-16 at 22:30 +0300, Cosmin Tanislav wrote:
> MAX9295A is an older variant of the MAX96717 which does not support
> tunnel mode.
> 
> Document the compatibility.
> 
> Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
> Acked-by: Rob Herring (Arm) <robh@kernel.org>
> ---
>  .../devicetree/bindings/media/i2c/maxim,max96717.yaml      | 7 ++++++-
>  1 file changed, 6 insertions(+), 1 deletion(-)
> 
> diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
> b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
> index 9afaa8a7a3f52..78ecbab8205a5 100644
> --- a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
> +++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
> @@ -25,12 +25,17 @@ description:
>  
>    The GMSL2 serial link operates at a fixed rate of 3Gbps or 6Gbps in the
>    forward direction and 187.5Mbps in the reverse direction.
> +
>    MAX96717F only supports a fixed rate of 3Gbps in the forward direction.
>  
> +  MAX9295A only supports pixel mode.
> +
>  properties:
>    compatible:
>      oneOf:
> -      - const: maxim,max96717f
> +      - enum:
> +          - maxim,max9295a
> +          - maxim,max96717f
>        - items:
>            - enum:
>                - maxim,max96717

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

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

* Re: [PATCH v6 06/24] dt-bindings: media: i2c: max96717: add support for MAX96793
  2025-07-16 19:30 ` [PATCH v6 06/24] dt-bindings: media: i2c: max96717: add support for MAX96793 Cosmin Tanislav
@ 2025-08-01 12:35   ` Julien Massot
  0 siblings, 0 replies; 44+ messages in thread
From: Julien Massot @ 2025-08-01 12:35 UTC (permalink / raw)
  To: Cosmin Tanislav, Cosmin Tanislav, Tomi Valkeinen,
	Mauro Carvalho Chehab, Rob Herring, Niklas Söderlund,
	Sakari Ailus, Laurent Pinchart, Greg Kroah-Hartman, Linus Walleij
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-staging, linux-gpio

On Wed, 2025-07-16 at 22:30 +0300, Cosmin Tanislav wrote:
> MAX96793 is a newer variant of the MAX96717 which also supports GMSL3
> links.
> 
> Document this compatibility.
> 
> Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
> Acked-by: Rob Herring (Arm) <robh@kernel.org>
> ---
>  .../devicetree/bindings/media/i2c/maxim,max96717.yaml          | 3 +++
>  1 file changed, 3 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
> b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
> index 78ecbab8205a5..02a44db982852 100644
> --- a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
> +++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml
> @@ -30,6 +30,8 @@ description:
>  
>    MAX9295A only supports pixel mode.
>  
> +  MAX96793 also supports GMSL3 mode.
> +
>  properties:
>    compatible:
>      oneOf:
> @@ -39,6 +41,7 @@ properties:
>        - items:
>            - enum:
>                - maxim,max96717
> +              - maxim,max96793
>            - const: maxim,max96717f
>  
>    '#gpio-cells':

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

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

end of thread, other threads:[~2025-08-01 12:35 UTC | newest]

Thread overview: 44+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-16 19:30 [PATCH v6 00/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Cosmin Tanislav
2025-07-16 19:30 ` [PATCH v6 01/24] media: mc: Add INTERNAL pad flag Cosmin Tanislav
2025-07-16 19:30 ` [PATCH v6 02/24] dt-bindings: media: i2c: max96717: add myself as maintainer Cosmin Tanislav
2025-08-01 12:17   ` Julien Massot
2025-07-16 19:30 ` [PATCH v6 03/24] dt-bindings: media: i2c: max96717: add support for I2C ATR Cosmin Tanislav
2025-08-01 12:19   ` Julien Massot
2025-07-16 19:30 ` [PATCH v6 04/24] dt-bindings: media: i2c: max96717: add support for pinctrl/pinconf Cosmin Tanislav
2025-08-01 12:20   ` Julien Massot
2025-07-16 19:30 ` [PATCH v6 05/24] dt-bindings: media: i2c: max96717: add support for MAX9295A Cosmin Tanislav
2025-08-01 12:21   ` Julien Massot
2025-08-01 12:34   ` Julien Massot
2025-07-16 19:30 ` [PATCH v6 06/24] dt-bindings: media: i2c: max96717: add support for MAX96793 Cosmin Tanislav
2025-08-01 12:35   ` Julien Massot
2025-07-16 19:30 ` [PATCH v6 07/24] dt-bindings: media: i2c: max96712: add myself as maintainer Cosmin Tanislav
2025-07-16 19:30 ` [PATCH v6 08/24] dt-bindings: media: i2c: max96712: use pattern properties for ports Cosmin Tanislav
2025-07-16 19:30 ` [PATCH v6 09/24] dt-bindings: media: i2c: max96712: add support for I2C ATR Cosmin Tanislav
2025-07-16 19:30 ` [PATCH v6 10/24] dt-bindings: media: i2c: max96712: add support for POC supplies Cosmin Tanislav
2025-07-16 19:30 ` [PATCH v6 11/24] dt-bindings: media: i2c: max96712: add support for MAX96724F/R Cosmin Tanislav
2025-07-16 19:30 ` [PATCH v6 12/24] dt-bindings: media: i2c: max96714: add myself as maintainer Cosmin Tanislav
2025-08-01 12:22   ` Julien Massot
2025-07-16 19:30 ` [PATCH v6 13/24] dt-bindings: media: i2c: max96714: add support for MAX96714R Cosmin Tanislav
2025-08-01 12:23   ` Julien Massot
2025-07-16 19:30 ` [PATCH v6 14/24] dt-bindings: media: i2c: add MAX9296A, MAX96716A, MAX96792A Cosmin Tanislav
2025-07-16 21:35   ` Rob Herring (Arm)
2025-07-16 19:31 ` [PATCH v6 15/24] media: i2c: add Maxim GMSL2/3 serializer and deserializer framework Cosmin Tanislav
2025-07-18  0:25   ` kernel test robot
2025-07-18  1:37   ` kernel test robot
2025-07-16 19:31 ` [PATCH v6 16/24] media: i2c: add Maxim GMSL2/3 serializer framework Cosmin Tanislav
2025-07-16 19:31 ` [PATCH v6 17/24] media: i2c: add Maxim GMSL2/3 deserializer framework Cosmin Tanislav
2025-07-16 19:31 ` [PATCH v6 18/24] media: i2c: maxim-serdes: add MAX96717 driver Cosmin Tanislav
2025-07-17 20:29   ` kernel test robot
2025-07-17 21:52   ` kernel test robot
2025-07-17 23:13   ` kernel test robot
2025-07-18  1:37   ` kernel test robot
2025-07-16 19:31 ` [PATCH v6 19/24] media: i2c: maxim-serdes: add MAX96724 driver Cosmin Tanislav
2025-07-17 12:44   ` Niklas Söderlund
2025-07-17 20:39   ` kernel test robot
2025-07-16 19:31 ` [PATCH v6 20/24] media: i2c: maxim-serdes: add MAX9296A driver Cosmin Tanislav
2025-07-18  5:23   ` kernel test robot
2025-07-16 19:31 ` [PATCH v6 21/24] arm64: defconfig: disable deprecated MAX96712 driver Cosmin Tanislav
2025-07-16 19:31 ` [PATCH v6 22/24] staging: media: remove " Cosmin Tanislav
2025-07-17 12:36   ` Niklas Söderlund
2025-07-16 19:31 ` [PATCH v6 23/24] media: i2c: remove MAX96717 driver Cosmin Tanislav
2025-07-16 19:31 ` [PATCH v6 24/24] media: i2c: remove MAX96714 driver Cosmin Tanislav

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