From: "Niklas Söderlund" <niklas.soderlund@ragnatech.se>
To: dumitru.ceclan@analog.com
Cc: Tomi Valkeinen <tomi.valkeinen+renesas@ideasonboard.com>,
Mauro Carvalho Chehab <mchehab@kernel.org>,
Sakari Ailus <sakari.ailus@linux.intel.com>,
Laurent Pinchart <laurent.pinchart@ideasonboard.com>,
Julien Massot <julien.massot@collabora.com>,
Rob Herring <robh@kernel.org>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
mitrutzceclan@gmail.com, linux-media@vger.kernel.org,
linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
linux-staging@lists.linux.dev, linux-gpio@vger.kernel.org,
Martin Hecht <Martin.Hecht@avnet.eu>,
Cosmin Tanislav <demonsingur@gmail.com>
Subject: Re: [PATCH v13 14/22] media: i2c: add Maxim GMSL2/3 serializer framework
Date: Wed, 10 Jun 2026 16:32:24 +0200 [thread overview]
Message-ID: <20260610143224.GC2948@ragnatech.se> (raw)
In-Reply-To: <20260604-gmsl2-3_serdes-v13-14-9d8a4919983b@analog.com>
Hello,
Small nit which I'm not sure is correct "fixing", but running
checkpatch,
total: 0 errors, 187 warnings, 0 checks, 2335 lines checked
A quick look at the warnings suggest all are of this type,
WARNING: 'ser' may be misspelled - perhaps 'set'?
#72: FILE: drivers/media/i2c/maxim-serdes/max_ser.c:25:
+ struct max_ser *ser;
^^^
So no need to change anything, but maybe worth doing to make life easier
for the future? I'm OK with it just thought I mention it.
On 2026-06-04 17:14:01 +0300, Dumitru Ceclan via B4 Relay wrote:
> From: Cosmin Tanislav <demonsingur@gmail.com>
>
> 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()
> * 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 | 2184 ++++++++++++++++++++++++++++++
> drivers/media/i2c/maxim-serdes/max_ser.h | 147 ++
> 3 files changed, 2332 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/media/i2c/maxim-serdes/Makefile b/drivers/media/i2c/maxim-serdes/Makefile
> index 630fbb486bab..17511cb03369 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 000000000000..a193381435e6
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max_ser.c
> @@ -0,0 +1,2184 @@
> +// 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;
> +
> + if (WARN_ON(i == ser->ops->num_i2c_xlates))
> + return;
> +
> + 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,
> + };
> + 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_ser_i2c_atr_ops, 1, 0);
> + if (IS_ERR(priv->atr))
> + return PTR_ERR(priv->atr);
> +
> + i2c_atr_set_driver_data(priv->atr, priv);
> +
> + ret = i2c_atr_add_adapter(priv->atr, &desc);
> + if (ret) {
> + i2c_atr_delete(priv->atr);
> + priv->atr = NULL;
> + }
> +
> + return ret;
> +}
> +
> +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)
> +{
> + struct fwnode_handle *fwnode;
> +
> + fwnode = device_get_named_child_node(priv->dev, "i2c-gate");
> + if (fwnode) {
> + fwnode_handle_put(fwnode);
> + return max_ser_i2c_mux_init(priv);
> + }
> +
> + 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;
> + }
> +
> + if (format->format.code == ~0U)
> + format->format.code = MEDIA_BUS_FMT_UYVY8_1X16;
> +
> + 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;
> + struct v4l2_subdev_state *state;
> + unsigned int i, j;
> + int ret;
> +
> + state = v4l2_subdev_lock_and_get_active_state(&priv->sd);
> +
> + 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)
> + goto out_unlock;
> + }
> + 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)
> + goto out_unlock;
> + }
> + v4l2_info(sd, "\n");
> + }
> +
> + ret = 0;
> +
> +out_unlock:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +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 -ENOTTY;
> +
> + 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 -ENOTTY;
> +
> + 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_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;
> +
> + if (!max_ser_pad_is_tpg(ser, fi->pad) ||
> + fi->stream != MAX_SERDES_TPG_STREAM)
> + return -ENOTTY;
> +
> + return v4l2_subdev_get_frame_interval(sd, state, fi);
> +}
> +
> +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);
> +
> + static const struct v4l2_mbus_framefmt format = {
> + .code = MEDIA_BUS_FMT_Y8_1X8,
> + .field = V4L2_FIELD_NONE,
> + .width = 640,
> + .height = 480,
> + };
> +
> + return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> +}
> +
> +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;
> +
> + if (*num_dts >= ser->ops->num_dts_per_pipe)
> + return -EINVAL;
> +
> + 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 = 0;
> + 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);
> + }
> +
> + if (!bpps)
> + return 0;
> +
> + 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 = max_ser_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 = {
> + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
> + .has_pad_interdep = v4l2_subdev_has_pad_interdep,
> + .link_validate = v4l2_subdev_link_validate,
> +};
> +
> +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);
> + v4l2_ctrl_handler_free(&priv->ctrl_handler);
> +}
> +
> +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) {
> + fwnode_handle_put(ep);
> + 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, ¤t_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 000000000000..a9365be5e62d
> --- /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.53.0
>
>
--
Kind Regards,
Niklas Söderlund
next prev parent reply other threads:[~2026-06-10 14:32 UTC|newest]
Thread overview: 61+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-04 14:13 [PATCH v13 00/22] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Dumitru Ceclan via B4 Relay
2026-06-04 14:13 ` Dumitru Ceclan
2026-06-04 14:13 ` [PATCH v13 01/22] media: mc: Add INTERNAL pad flag Dumitru Ceclan via B4 Relay
2026-06-04 14:13 ` Dumitru Ceclan
2026-06-04 14:29 ` sashiko-bot
2026-06-04 14:13 ` [PATCH v13 02/22] dt-bindings: media: i2c: max96717: add support for I2C ATR Dumitru Ceclan via B4 Relay
2026-06-04 14:13 ` Dumitru Ceclan
2026-06-04 14:31 ` sashiko-bot
2026-06-04 14:13 ` [PATCH v13 03/22] dt-bindings: media: i2c: max96717: add support for pinctrl/pinconf Dumitru Ceclan via B4 Relay
2026-06-04 14:13 ` Dumitru Ceclan
2026-06-04 14:13 ` [PATCH v13 04/22] dt-bindings: media: i2c: max96717: add support for MAX9295A Dumitru Ceclan via B4 Relay
2026-06-04 14:13 ` Dumitru Ceclan
2026-06-04 14:13 ` [PATCH v13 05/22] dt-bindings: media: i2c: max96717: add support for MAX96793 Dumitru Ceclan via B4 Relay
2026-06-04 14:13 ` Dumitru Ceclan
2026-06-04 14:13 ` [PATCH v13 06/22] dt-bindings: media: i2c: max96712: use pattern properties for ports Dumitru Ceclan via B4 Relay
2026-06-04 14:13 ` Dumitru Ceclan
2026-06-04 14:13 ` [PATCH v13 07/22] dt-bindings: media: i2c: max96712: add support for I2C ATR Dumitru Ceclan via B4 Relay
2026-06-04 14:13 ` Dumitru Ceclan
2026-06-04 14:30 ` sashiko-bot
2026-06-04 14:13 ` [PATCH v13 08/22] dt-bindings: media: i2c: max96712: add support for POC supplies Dumitru Ceclan via B4 Relay
2026-06-04 14:13 ` Dumitru Ceclan
2026-06-04 14:13 ` [PATCH v13 09/22] dt-bindings: media: i2c: max96712: add support for MAX96724F/R Dumitru Ceclan via B4 Relay
2026-06-04 14:13 ` Dumitru Ceclan
2026-06-04 14:13 ` [PATCH v13 10/22] dt-bindings: media: i2c: max96712: add control-channel-port property Dumitru Ceclan via B4 Relay
2026-06-04 14:13 ` Dumitru Ceclan
2026-06-04 14:29 ` sashiko-bot
2026-06-04 14:13 ` [PATCH v13 11/22] dt-bindings: media: i2c: max96714: add support for MAX96714R Dumitru Ceclan via B4 Relay
2026-06-04 14:13 ` Dumitru Ceclan
2026-06-04 14:13 ` [PATCH v13 12/22] dt-bindings: media: i2c: add MAX9296A, MAX96716A, MAX96792A Dumitru Ceclan via B4 Relay
2026-06-04 14:13 ` Dumitru Ceclan
2026-06-04 14:14 ` [PATCH v13 13/22] media: i2c: add Maxim GMSL2/3 serializer and deserializer framework Dumitru Ceclan via B4 Relay
2026-06-04 14:14 ` Dumitru Ceclan
2026-06-04 14:14 ` [PATCH v13 14/22] media: i2c: add Maxim GMSL2/3 serializer framework Dumitru Ceclan via B4 Relay
2026-06-04 14:14 ` Dumitru Ceclan
2026-06-04 14:33 ` sashiko-bot
2026-06-10 14:32 ` Niklas Söderlund [this message]
2026-06-04 14:14 ` [PATCH v13 15/22] media: i2c: add Maxim GMSL2/3 deserializer framework Dumitru Ceclan via B4 Relay
2026-06-04 14:14 ` Dumitru Ceclan
2026-06-04 14:40 ` sashiko-bot
2026-06-10 14:35 ` Niklas Söderlund
2026-06-04 14:14 ` [PATCH v13 16/22] media: i2c: maxim-serdes: add MAX96717 driver Dumitru Ceclan via B4 Relay
2026-06-04 14:14 ` Dumitru Ceclan
2026-06-04 14:41 ` sashiko-bot
2026-06-10 14:39 ` Niklas Söderlund
2026-06-04 14:14 ` [PATCH v13 17/22] media: i2c: maxim-serdes: add MAX96724 driver Dumitru Ceclan via B4 Relay
2026-06-04 14:14 ` Dumitru Ceclan
2026-06-04 14:38 ` sashiko-bot
2026-06-10 14:42 ` Niklas Söderlund
2026-06-10 15:06 ` Dan Carpenter
2026-06-04 14:14 ` [PATCH v13 18/22] media: i2c: maxim-serdes: add MAX9296A driver Dumitru Ceclan via B4 Relay
2026-06-04 14:14 ` Dumitru Ceclan
2026-06-04 14:40 ` sashiko-bot
2026-06-04 14:14 ` [PATCH v13 19/22] arm64: defconfig: disable deprecated MAX96712 driver Dumitru Ceclan via B4 Relay
2026-06-04 14:14 ` Dumitru Ceclan
2026-06-04 14:33 ` sashiko-bot
2026-06-04 14:14 ` [PATCH v13 20/22] staging: media: remove " Dumitru Ceclan via B4 Relay
2026-06-04 14:14 ` Dumitru Ceclan
2026-06-04 14:14 ` [PATCH v13 21/22] media: i2c: remove MAX96717 driver Dumitru Ceclan via B4 Relay
2026-06-04 14:14 ` Dumitru Ceclan
2026-06-04 14:14 ` [PATCH v13 22/22] media: i2c: remove MAX96714 driver Dumitru Ceclan via B4 Relay
2026-06-04 14:14 ` Dumitru Ceclan
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260610143224.GC2948@ragnatech.se \
--to=niklas.soderlund@ragnatech.se \
--cc=Martin.Hecht@avnet.eu \
--cc=demonsingur@gmail.com \
--cc=devicetree@vger.kernel.org \
--cc=dumitru.ceclan@analog.com \
--cc=gregkh@linuxfoundation.org \
--cc=julien.massot@collabora.com \
--cc=laurent.pinchart@ideasonboard.com \
--cc=linux-gpio@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-media@vger.kernel.org \
--cc=linux-staging@lists.linux.dev \
--cc=mchehab@kernel.org \
--cc=mitrutzceclan@gmail.com \
--cc=robh@kernel.org \
--cc=sakari.ailus@linux.intel.com \
--cc=tomi.valkeinen+renesas@ideasonboard.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.