Linux kernel staging patches
 help / color / mirror / Atom feed
* Re: [PATCH v13 15/22] media: i2c: add Maxim GMSL2/3 deserializer framework
From: Niklas Söderlund @ 2026-06-10 14:35 UTC (permalink / raw)
  To: dumitru.ceclan
  Cc: Tomi Valkeinen, Mauro Carvalho Chehab, Sakari Ailus,
	Laurent Pinchart, Julien Massot, Rob Herring, Greg Kroah-Hartman,
	mitrutzceclan, linux-media, linux-kernel, devicetree,
	linux-staging, linux-gpio, Martin Hecht, Cosmin Tanislav
In-Reply-To: <20260604-gmsl2-3_serdes-v13-15-9d8a4919983b@analog.com>

Hello,

Thanks for your work.

On 2026-06-04 17:14:02 +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 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 | 3243 ++++++++++++++++++++++++++++++
>  drivers/media/i2c/maxim-serdes/max_des.h |  157 ++
>  3 files changed, 3401 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/media/i2c/maxim-serdes/Makefile b/drivers/media/i2c/maxim-serdes/Makefile
> index 17511cb03369..b54326a5c81b 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 000000000000..732e3ab83664
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max_des.c
> @@ -0,0 +1,3243 @@
> +// 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 <linux/units.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		(100  * HZ_PER_MHZ)
> +#define MAX_DES_LINK_FREQUENCY_DEFAULT		(750  * HZ_PER_MHZ)
> +#define MAX_DES_LINK_FREQUENCY_MAX		(1250 * HZ_PER_MHZ)
> +
> +#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->info->num_links;
> +}
> +
> +static inline bool max_des_pad_is_source(struct max_des *des, u32 pad)
> +{
> +	return pad >= des->info->num_links &&
> +	       pad < des->info->num_links + des->info->num_phys;
> +}
> +
> +static inline bool max_des_pad_is_tpg(struct max_des *des, u32 pad)
> +{
> +	return pad == des->info->num_links + des->info->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->info->num_links;
> +}
> +
> +static inline unsigned int max_des_num_pads(struct max_des *des)
> +{
> +	return des->info->num_links + des->info->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->info->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->info->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->info->tpg_entries.num_entries; i++) {
> +		entry = &des->info->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->info->tpg_entries.num_entries)
> +		return NULL;
> +
> +	return &des->info->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->info->modes;
> +
> +	if (context->tpg)
> +		*modes = BIT(des->info->tpg_mode);
> +
> +	for (i = 0; i < des->info->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->info->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->info->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");

WARNING: quoted string split across lines
#747: FILE: drivers/media/i2c/maxim-serdes/max_des.c:699:
+				"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->info->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->info->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->info->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->info->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->info->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->info->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->info->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->info->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 >= MAX_SERDES_STREAMS_NUM) {
> +			dev_err(priv->dev, "Invalid stream id %u\n", stream_id);
> +			return -EINVAL;
> +		}
> +
> +		if (stream_id_usage[stream_id] && des->info->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->info->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->info->num_phys);
> +
> +		if (priv->unused_phy &&
> +		    (context->mode != MAX_SERDES_GMSL_TUNNEL_MODE ||
> +		     phy_id == des->info->num_phys))
> +			phy_id = priv->unused_phy->index;
> +
> +		if (phy_id != des->info->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->info->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->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->info->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->info->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->info->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->info->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->info->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->info->needs_single_link_version)
> +		return;
> +
> +	for (i = 0; i < des->info->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->info->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->info->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->info->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->info->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->info->num_links; i++) {
> +		struct max_des_link *link = &des->links[i];
> +
> +		if (!link->enabled)
> +			continue;
> +
> +		mask |= BIT(link->index);
> +	}
> +
> +	ret = des->ops->select_links(des, mask);
> +	if (ret)
> +		goto err_add_adapters;
> +
> +	return 0;
> +
> +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 max_des *des = priv->des;
> +	struct device *dev = device;
> +	struct i2c_client *client;
> +	unsigned int i;
> +
> +	/*
> +	 * 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 (i = 0; i < des->info->num_links; i++) {
> +		if (des->links[i].enabled &&
> +		    client->adapter == des->links[i].adapter)
> +			break;
> +	}
> +
> +	if (i == des->info->num_links)
> +		return NOTIFY_DONE;
> +
> +	max_des_ser_attach_addr(priv, i, 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->info->num_links == 1)
> +		flags |= I2C_MUX_GATE;
> +
> +	priv->mux = i2c_mux_alloc(priv->client->adapter, priv->dev,
> +				  des->info->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->info->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;
> +
> +		link->adapter = priv->mux->adapter[priv->mux->num_adapters - 1];
> +	}
> +
> +	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->info->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->info->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;
> +	}
> +
> +	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_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 -ENOTTY;
> +
> +	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_get_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;
> +
> +	if (!max_des_pad_is_tpg(des, fi->pad) ||
> +	    fi->stream != MAX_SERDES_TPG_STREAM)
> +		return -ENOTTY;
> +
> +	return v4l2_subdev_get_frame_interval(sd, state, fi);
> +}
> +
> +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 -ENOTTY;
> +
> +	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->info->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->info->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->info->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->info->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;
> +	fd->num_entries = 0;
> +
> +	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->info->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);
> +
> +	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_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->info->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->info->num_links;
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < des->info->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->info->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)
> +		goto err_free_streams_masks;
> +
> +	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->info->num_phys; i++) {
> +		if (des->phys[i].enabled) {
> +			phy = &des->phys[i];
> +			break;
> +		}
> +	}
> +
> +	if (!phy)
> +		return 0;
> +
> +	for (i = 0; i < des->info->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 = max_des_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 = {
> +	.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_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);
> +		source->sd = NULL;
> +		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->info->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->info->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->info->tpg_patterns,
> +					     __ffs(des->info->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;
> +	struct max_des *des = priv->des;
> +	unsigned int i;
> +
> +	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);
> +
> +	for (i = 0; i < des->info->num_links; i++)
> +		fwnode_handle_put(priv->sources[i].ep_fwnode);
> +}
> +
> +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->info->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);
> +			if (!enable)
> +				return ret;
> +			goto err_rollback;
> +		}
> +	}
> +
> +	return 0;
> +
> +err_rollback:
> +	while (i--) {
> +		struct max_des_link *link = &des->links[i];
> +
> +		if (!link->enabled)
> +			continue;
> +
> +		if (!priv->pocs[i])
> +			continue;
> +
> +		regulator_disable(priv->pocs[i]);
> +	}
> +
> +	return ret;
> +}
> +
> +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->info->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->info->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->info->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->info->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. */

WARNING: 'unsed' may be misspelled - perhaps 'unused'?
#3101: FILE: drivers/media/i2c/maxim-serdes/max_des.c:3053:
+	/* Find an unsed PHY to send unampped data to. */
                   ^^^^^
> +	for (i = 0; i < des->info->num_phys; i++) {
> +		phy = &des->phys[i];
> +
> +		if (!phy->enabled) {
> +			priv->unused_phy = phy;
> +			break;
> +		}
> +	}
> +
> +	for (i = 0; i < des->info->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->info->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->info->num_links;
> +	}
> +
> +	for (i = 0; i < des->info->num_links; i++) {
> +		link = &des->links[i];
> +		link->index = i;
> +	}
> +
> +	for (i = 0; i < des->info->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->info->num_phys,
> +				 sizeof(*des->phys), GFP_KERNEL);
> +	if (!des->phys)
> +		return -ENOMEM;
> +
> +	des->pipes = devm_kcalloc(priv->dev, des->info->num_pipes,
> +				  sizeof(*des->pipes), GFP_KERNEL);
> +	if (!des->pipes)
> +		return -ENOMEM;
> +
> +	des->links = devm_kcalloc(priv->dev, des->info->num_links,
> +				  sizeof(*des->links), GFP_KERNEL);
> +	if (!des->links)
> +		return -ENOMEM;
> +
> +	priv->sources = devm_kcalloc(priv->dev, des->info->num_links,
> +				     sizeof(*priv->sources), GFP_KERNEL);
> +	if (!priv->sources)
> +		return -ENOMEM;
> +
> +	priv->pocs = devm_kcalloc(priv->dev, des->info->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->info->num_phys > MAX_DES_NUM_PHYS)
> +		return -E2BIG;
> +
> +	if (des->info->num_pipes > MAX_DES_NUM_PIPES)
> +		return -E2BIG;
> +
> +	if (des->info->num_links > MAX_DES_NUM_LINKS)
> +		return -E2BIG;
> +
> +	if (des->info->num_links > des->info->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->info->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 000000000000..3ad8246b1981
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max_des.h
> @@ -0,0 +1,157 @@
> +/* 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 i2c_adapter *adapter;
> +};
> +
> +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;
> +	u64 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_info {
> +	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;
> +};
> +
> +struct max_des_ops {
> +#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_info *info;
> +	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.53.0
> 
> 

-- 
Kind Regards,
Niklas Söderlund

^ permalink raw reply

* Re: [PATCH v13 14/22] media: i2c: add Maxim GMSL2/3 serializer framework
From: Niklas Söderlund @ 2026-06-10 14:32 UTC (permalink / raw)
  To: dumitru.ceclan
  Cc: Tomi Valkeinen, Mauro Carvalho Chehab, Sakari Ailus,
	Laurent Pinchart, Julien Massot, Rob Herring, Greg Kroah-Hartman,
	mitrutzceclan, linux-media, linux-kernel, devicetree,
	linux-staging, linux-gpio, Martin Hecht, Cosmin Tanislav
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, &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 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

^ permalink raw reply

* Re: [PATCH] staging: media: atomisp: use kvmalloc_objs() in make_histogram()
From: Andy Shevchenko @ 2026-06-10 13:36 UTC (permalink / raw)
  To: Rodrigo Gobbi
  Cc: andy, hansg, mchehab, sakari.ailus, gregkh, feng, ~lkcamp/patches,
	linux-kernel-mentees, linux-kernel, linux-media, linux-staging
In-Reply-To: <20260609215110.118860-1-rodrigo.gobbi.7@gmail.com>

On Tue, Jun 09, 2026 at 06:46:31PM -0300, Rodrigo Gobbi wrote:
> Replace kvmalloc() with multiply with kvmalloc_objs(), which handles
> the size multiplication internally with overflow checking, silenting
> checkpatch warn.
> 
> Signed-off-by: Rodrigo Gobbi <rodrigo.gobbi.7@gmail.com>
> ---
> Hi, all,
> There is a ongoing effort like this for other files from atomisp
> at [1], yet, it is not covering the same file.
> Tks and regards.
> 
> [1] https://lore.kernel.org/all/20260413112904.98864-1-feng@innora.ai/

Yeah, the problem is that the activity seems stale. Can you pickup all
the patches from the mailing list that have not been yet applied (regarding
k*alloc() uses) and combine them into series or so and update regarding to
Sakari's comments?


-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* [PATCH 0/2] staging: rtl8723bs: enum labels naming, coding style
From: Aiman Najjar @ 2026-06-10  1:42 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: kernel-janitors, linux-staging, linux-kernel, Aiman Najjar

Enum labels generated by token-pasting macros adopt camel case. This
patch removes the use of macro to generate the enum label names and
adopts new captialized names.

Signed-off-by: Aiman Najjar <aiman@linux.com>
---
Aiman Najjar (2):
      staging: rtl8723bs: drop GEN_CMD_CODE macro and capitalize labels
      staging: rtl8723bs: drop GEN_EVT_CODE macro and capitalize labels

 drivers/staging/rtl8723bs/core/rtw_ap.c          |   4 +-
 drivers/staging/rtl8723bs/core/rtw_cmd.c         | 190 +++++++++++------------
 drivers/staging/rtl8723bs/core/rtw_mlme.c        |   4 +-
 drivers/staging/rtl8723bs/core/rtw_mlme_ext.c    |  30 ++--
 drivers/staging/rtl8723bs/include/rtw_cmd.h      | 154 +++++++++---------
 drivers/staging/rtl8723bs/include/rtw_event.h    |   4 -
 drivers/staging/rtl8723bs/include/rtw_mlme_ext.h |  54 +++----
 7 files changed, 214 insertions(+), 226 deletions(-)
---
base-commit: ad4605d69bab07941460db0a0cba88ac5c3302cf
change-id: 20260609-rtl8723bs-code-style-0871b80b6799

Best regards,
--  
Aiman Najjar <aiman@linux.com>


^ permalink raw reply

* [PATCH 2/2] staging: rtl8723bs: drop GEN_EVT_CODE macro and capitalize labels
From: Aiman Najjar @ 2026-06-10  1:42 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: kernel-janitors, linux-staging, linux-kernel, Aiman Najjar
In-Reply-To: <20260609-rtl8723bs-code-style-v1-0-daa0e85359a6@linux.com>

The use of GEN_EVT_CODE macro to generate event enum label name is applied
inconsistently and is confusing, it also makes it harder to make use of
tools such as clangd when looking up symbols.

Replace them with writing the enum labels directly and adopting
new capitalized names instead of the current camel case ones.

Signed-off-by: Aiman Najjar <aiman@linux.com>
---
 drivers/staging/rtl8723bs/core/rtw_mlme_ext.c    | 12 +++---
 drivers/staging/rtl8723bs/include/rtw_event.h    |  4 --
 drivers/staging/rtl8723bs/include/rtw_mlme_ext.h | 54 ++++++++++++------------
 3 files changed, 33 insertions(+), 37 deletions(-)

diff --git a/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c b/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c
index abcc666d5dbe..833f802ffe62 100644
--- a/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c
+++ b/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c
@@ -4400,7 +4400,7 @@ void report_survey_event(struct adapter *padapter, union recv_frame *precv_frame
 
 	pc2h_evt_hdr = (struct C2HEvent_Header *)(pevtcmd);
 	pc2h_evt_hdr->len = sizeof(struct survey_event);
-	pc2h_evt_hdr->ID = GEN_EVT_CODE(_Survey);
+	pc2h_evt_hdr->ID = SURVEY_EVENT;
 	pc2h_evt_hdr->seq = atomic_inc_return(&pmlmeext->event_seq);
 
 	psurvey_evt = (struct survey_event *)(pevtcmd + sizeof(struct C2HEvent_Header));
@@ -4453,7 +4453,7 @@ void report_surveydone_event(struct adapter *padapter)
 
 	pc2h_evt_hdr = (struct C2HEvent_Header *)(pevtcmd);
 	pc2h_evt_hdr->len = sizeof(struct surveydone_event);
-	pc2h_evt_hdr->ID = GEN_EVT_CODE(_SurveyDone);
+	pc2h_evt_hdr->ID = SURVEY_DONE_EVENT;
 	pc2h_evt_hdr->seq = atomic_inc_return(&pmlmeext->event_seq);
 
 	psurveydone_evt = (struct surveydone_event *)(pevtcmd + sizeof(struct C2HEvent_Header));
@@ -4498,7 +4498,7 @@ void report_join_res(struct adapter *padapter, int res)
 
 	pc2h_evt_hdr = (struct C2HEvent_Header *)(pevtcmd);
 	pc2h_evt_hdr->len = sizeof(struct joinbss_event);
-	pc2h_evt_hdr->ID = GEN_EVT_CODE(_JoinBss);
+	pc2h_evt_hdr->ID = JOIN_BSS_EVENT;
 	pc2h_evt_hdr->seq = atomic_inc_return(&pmlmeext->event_seq);
 
 	pjoinbss_evt = (struct joinbss_event *)(pevtcmd + sizeof(struct C2HEvent_Header));
@@ -4547,7 +4547,7 @@ void report_wmm_edca_update(struct adapter *padapter)
 
 	pc2h_evt_hdr = (struct C2HEvent_Header *)(pevtcmd);
 	pc2h_evt_hdr->len = sizeof(struct wmm_event);
-	pc2h_evt_hdr->ID = GEN_EVT_CODE(_WMM);
+	pc2h_evt_hdr->ID = WMM_EVENT;
 	pc2h_evt_hdr->seq = atomic_inc_return(&pmlmeext->event_seq);
 
 	pwmm_event = (struct wmm_event *)(pevtcmd + sizeof(struct C2HEvent_Header));
@@ -4593,7 +4593,7 @@ void report_del_sta_event(struct adapter *padapter, unsigned char *MacAddr, unsi
 
 	pc2h_evt_hdr = (struct C2HEvent_Header *)(pevtcmd);
 	pc2h_evt_hdr->len = sizeof(struct stadel_event);
-	pc2h_evt_hdr->ID = GEN_EVT_CODE(_DelSTA);
+	pc2h_evt_hdr->ID = DEL_STA_EVENT;
 	pc2h_evt_hdr->seq = atomic_inc_return(&pmlmeext->event_seq);
 
 	pdel_sta_evt = (struct stadel_event *)(pevtcmd + sizeof(struct C2HEvent_Header));
@@ -4644,7 +4644,7 @@ void report_add_sta_event(struct adapter *padapter, unsigned char *MacAddr, int
 
 	pc2h_evt_hdr = (struct C2HEvent_Header *)(pevtcmd);
 	pc2h_evt_hdr->len = sizeof(struct stassoc_event);
-	pc2h_evt_hdr->ID = GEN_EVT_CODE(_AddSTA);
+	pc2h_evt_hdr->ID = ADD_STA_EVENT;
 	pc2h_evt_hdr->seq = atomic_inc_return(&pmlmeext->event_seq);
 
 	padd_sta_evt = (struct stassoc_event *)(pevtcmd + sizeof(struct C2HEvent_Header));
diff --git a/drivers/staging/rtl8723bs/include/rtw_event.h b/drivers/staging/rtl8723bs/include/rtw_event.h
index 62e0dec249ad..e5cb46c2a731 100644
--- a/drivers/staging/rtl8723bs/include/rtw_event.h
+++ b/drivers/staging/rtl8723bs/include/rtw_event.h
@@ -64,10 +64,6 @@ struct wmm_event {
 	unsigned char wmm;
 };
 
-#define GEN_EVT_CODE(event)	event ## _EVT_
-
-
-
 struct fwevent {
 	u32 parmsize;
 	void (*event_callback)(struct adapter *dev, u8 *pbuf);
diff --git a/drivers/staging/rtl8723bs/include/rtw_mlme_ext.h b/drivers/staging/rtl8723bs/include/rtw_mlme_ext.h
index 95769f90d196..5adcff9a734b 100644
--- a/drivers/staging/rtl8723bs/include/rtw_mlme_ext.h
+++ b/drivers/staging/rtl8723bs/include/rtw_mlme_ext.h
@@ -679,33 +679,33 @@ void rtw_dummy_event_callback(struct adapter *adapter, u8 *pbuf);
 void rtw_fwdbg_event_callback(struct adapter *adapter, u8 *pbuf);
 
 enum {
-	GEN_EVT_CODE(_Read_MACREG) = 0, /*0*/
-	GEN_EVT_CODE(_Read_BBREG),
-	GEN_EVT_CODE(_Read_RFREG),
-	GEN_EVT_CODE(_Read_EEPROM),
-	GEN_EVT_CODE(_Read_EFUSE),
-	GEN_EVT_CODE(_Read_CAM),			/*5*/
-	GEN_EVT_CODE(_Get_BasicRate),
-	GEN_EVT_CODE(_Get_DataRate),
-	GEN_EVT_CODE(_Survey),	 /*8*/
-	GEN_EVT_CODE(_SurveyDone),	 /*9*/
-
-	GEN_EVT_CODE(_JoinBss), /*10*/
-	GEN_EVT_CODE(_AddSTA),
-	GEN_EVT_CODE(_DelSTA),
-	GEN_EVT_CODE(_AtimDone),
-	GEN_EVT_CODE(_TX_Report),
-	GEN_EVT_CODE(_CCX_Report),			/*15*/
-	GEN_EVT_CODE(_DTM_Report),
-	GEN_EVT_CODE(_TX_Rate_Statistics),
-	GEN_EVT_CODE(_C2HLBK),
-	GEN_EVT_CODE(_FWDBG),
-	GEN_EVT_CODE(_C2HFEEDBACK),               /*20*/
-	GEN_EVT_CODE(_ADDBA),
-	GEN_EVT_CODE(_C2HBCN),
-	GEN_EVT_CODE(_ReportPwrState),		/* filen: only for PCIE, USB */
-	GEN_EVT_CODE(_CloseRF),				/* filen: only for PCIE, work around ASPM */
-	GEN_EVT_CODE(_WMM),					/*25*/
+	READ_MACREG_EVENT = 0,		/*0*/
+	READ_BBREG_EVENT,
+	READ_RFREG_EVENT,
+	READ_EEPROM_EVENT,
+	READ_EFUSE_EVENT,
+	READ_CAM_EVENT,			/*5*/
+	GET_BASICRATE_EVENT,
+	GET_DATARATE_EVENT,
+	SURVEY_EVENT,			/*8*/
+	SURVEY_DONE_EVENT,		/*9*/
+
+	JOIN_BSS_EVENT,			/*10*/
+	ADD_STA_EVENT,
+	DEL_STA_EVENT,
+	ATIM_DONE_EVENT,
+	TX_REPORT_EVENT,
+	CCX_REPORT_EVENT,		/*15*/
+	DTM_REPORT_EVENT,
+	TX_RATE_STATISTICS_EVENT,
+	C2HLBK_EVENT,
+	FWDBG_EVENT,
+	C2HFEEDBACK_EVENT,               /*20*/
+	ADDBA_EVENT,
+	C2HBCN_EVENT,
+	REPORT_PWR_STATE_EVENT,		/* filen: only for PCIE, USB */
+	CLOSE_RF_EVENT,			/* filen: only for PCIE, work around ASPM */
+	WMM_EVENT,			/*25*/
 	MAX_C2HEVT
 };
 

-- 
2.54.0


^ permalink raw reply related

* [PATCH 1/2] staging: rtl8723bs: drop GEN_CMD_CODE macro and capitalize labels
From: Aiman Najjar @ 2026-06-10  1:42 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: kernel-janitors, linux-staging, linux-kernel, Aiman Najjar
In-Reply-To: <20260609-rtl8723bs-code-style-v1-0-daa0e85359a6@linux.com>

The use of GEN_CMD_CODE macro to generate cmd enum label names is applied
inconsistently and is confusing, it also makes it harder to make use of
tools such as clangd when looking up symbols.

Replace them with writing the enum labels directly and adopting
new capitalized names instead of the current camel case ones.

Signed-off-by: Aiman Najjar <aiman@linux.com>
---
 drivers/staging/rtl8723bs/core/rtw_ap.c       |   4 +-
 drivers/staging/rtl8723bs/core/rtw_cmd.c      | 190 +++++++++++++-------------
 drivers/staging/rtl8723bs/core/rtw_mlme.c     |   4 +-
 drivers/staging/rtl8723bs/core/rtw_mlme_ext.c |  18 +--
 drivers/staging/rtl8723bs/include/rtw_cmd.h   | 154 ++++++++++-----------
 5 files changed, 181 insertions(+), 189 deletions(-)

diff --git a/drivers/staging/rtl8723bs/core/rtw_ap.c b/drivers/staging/rtl8723bs/core/rtw_ap.c
index 4b4012411011..5a183b169282 100644
--- a/drivers/staging/rtl8723bs/core/rtw_ap.c
+++ b/drivers/staging/rtl8723bs/core/rtw_ap.c
@@ -1237,7 +1237,7 @@ u8 rtw_ap_set_pairwise_key(struct adapter *padapter, struct sta_info *psta)
 		goto exit;
 	}
 
-	init_h2fwcmd_w_parm_no_rsp(ph2c, psetstakey_para, _SetStaKey_CMD_);
+	init_h2fwcmd_w_parm_no_rsp(ph2c, psetstakey_para, SET_STA_KEY_CMD);
 
 	psetstakey_para->algorithm = (u8)psta->dot118021XPrivacy;
 
@@ -1300,7 +1300,7 @@ static int rtw_ap_set_key(struct adapter *padapter,
 
 	memcpy(&psetkeyparm->key[0], key, keylen);
 
-	pcmd->cmdcode = _SetKey_CMD_;
+	pcmd->cmdcode = SET_KEY_CMD;
 	pcmd->parmbuf = (u8 *)psetkeyparm;
 	pcmd->cmdsz =  (sizeof(struct setkey_parm));
 	pcmd->rsp = NULL;
diff --git a/drivers/staging/rtl8723bs/core/rtw_cmd.c b/drivers/staging/rtl8723bs/core/rtw_cmd.c
index c1185c25ed36..b3637796187d 100644
--- a/drivers/staging/rtl8723bs/core/rtw_cmd.c
+++ b/drivers/staging/rtl8723bs/core/rtw_cmd.c
@@ -11,78 +11,78 @@
 #include <linux/delay.h>
 
 static struct _cmd_callback rtw_cmd_callback[] = {
-	{GEN_CMD_CODE(_Read_MACREG), NULL}, /*0*/
-	{GEN_CMD_CODE(_Write_MACREG), NULL},
-	{GEN_CMD_CODE(_Read_BBREG), &rtw_getbbrfreg_cmdrsp_callback},
-	{GEN_CMD_CODE(_Write_BBREG), NULL},
-	{GEN_CMD_CODE(_Read_RFREG), &rtw_getbbrfreg_cmdrsp_callback},
-	{GEN_CMD_CODE(_Write_RFREG), NULL}, /*5*/
-	{GEN_CMD_CODE(_Read_EEPROM), NULL},
-	{GEN_CMD_CODE(_Write_EEPROM), NULL},
-	{GEN_CMD_CODE(_Read_EFUSE), NULL},
-	{GEN_CMD_CODE(_Write_EFUSE), NULL},
-
-	{GEN_CMD_CODE(_Read_CAM),	NULL},	/*10*/
-	{GEN_CMD_CODE(_Write_CAM),	 NULL},
-	{GEN_CMD_CODE(_setBCNITV), NULL},
-	{GEN_CMD_CODE(_setMBIDCFG), NULL},
-	{GEN_CMD_CODE(_JoinBss), &rtw_joinbss_cmd_callback},  /*14*/
-	{GEN_CMD_CODE(_DisConnect), &rtw_disassoc_cmd_callback}, /*15*/
-	{GEN_CMD_CODE(_CreateBss), &rtw_createbss_cmd_callback},
-	{GEN_CMD_CODE(_SetOpMode), NULL},
-	{GEN_CMD_CODE(_SiteSurvey), &rtw_survey_cmd_callback}, /*18*/
-	{GEN_CMD_CODE(_SetAuth), NULL},
-
-	{GEN_CMD_CODE(_SetKey), NULL},	/*20*/
-	{GEN_CMD_CODE(_SetStaKey), &rtw_setstaKey_cmdrsp_callback},
-	{GEN_CMD_CODE(_SetAssocSta), &rtw_setassocsta_cmdrsp_callback},
-	{GEN_CMD_CODE(_DelAssocSta), NULL},
-	{GEN_CMD_CODE(_SetStaPwrState), NULL},
-	{GEN_CMD_CODE(_SetBasicRate), NULL}, /*25*/
-	{GEN_CMD_CODE(_GetBasicRate), NULL},
-	{GEN_CMD_CODE(_SetDataRate), NULL},
-	{GEN_CMD_CODE(_GetDataRate), NULL},
-	{GEN_CMD_CODE(_SetPhyInfo), NULL},
-
-	{GEN_CMD_CODE(_GetPhyInfo), NULL}, /*30*/
-	{GEN_CMD_CODE(_SetPhy), NULL},
-	{GEN_CMD_CODE(_GetPhy), NULL},
-	{GEN_CMD_CODE(_readRssi), NULL},
-	{GEN_CMD_CODE(_readGain), NULL},
-	{GEN_CMD_CODE(_SetAtim), NULL}, /*35*/
-	{GEN_CMD_CODE(_SetPwrMode), NULL},
-	{GEN_CMD_CODE(_JoinbssRpt), NULL},
-	{GEN_CMD_CODE(_SetRaTable), NULL},
-	{GEN_CMD_CODE(_GetRaTable), NULL},
-
-	{GEN_CMD_CODE(_GetCCXReport), NULL}, /*40*/
-	{GEN_CMD_CODE(_GetDTMReport),	NULL},
-	{GEN_CMD_CODE(_GetTXRateStatistics), NULL},
-	{GEN_CMD_CODE(_SetUsbSuspend), NULL},
-	{GEN_CMD_CODE(_SetH2cLbk), NULL},
-	{GEN_CMD_CODE(_AddBAReq), NULL}, /*45*/
-	{GEN_CMD_CODE(_SetChannel), NULL},		/*46*/
-	{GEN_CMD_CODE(_SetTxPower), NULL},
-	{GEN_CMD_CODE(_SwitchAntenna), NULL},
-	{GEN_CMD_CODE(_SetCrystalCap), NULL},
-	{GEN_CMD_CODE(_SetSingleCarrierTx), NULL},	/*50*/
-
-	{GEN_CMD_CODE(_SetSingleToneTx), NULL}, /*51*/
-	{GEN_CMD_CODE(_SetCarrierSuppressionTx), NULL},
-	{GEN_CMD_CODE(_SetContinuousTx), NULL},
-	{GEN_CMD_CODE(_SwitchBandwidth), NULL},		/*54*/
-	{GEN_CMD_CODE(_TX_Beacon), NULL},/*55*/
-
-	{GEN_CMD_CODE(_Set_MLME_EVT), NULL},/*56*/
-	{GEN_CMD_CODE(_Set_Drv_Extra), NULL},/*57*/
-	{GEN_CMD_CODE(_Set_H2C_MSG), NULL},/*58*/
-	{GEN_CMD_CODE(_SetChannelPlan), NULL},/*59*/
-
-	{GEN_CMD_CODE(_SetChannelSwitch), NULL},/*60*/
-	{GEN_CMD_CODE(_TDLS), NULL},/*61*/
-	{GEN_CMD_CODE(_ChkBMCSleepq), NULL}, /*62*/
-
-	{GEN_CMD_CODE(_RunInThreadCMD), NULL},/*63*/
+	{READ_MACREG_CMD, NULL}, /*0*/
+	{WRITE_MACREG_CMD, NULL},
+	{READ_BBREG_CMD, &rtw_getbbrfreg_cmdrsp_callback},
+	{WRITE_BBREG_CMD, NULL},
+	{READ_RFREG_CMD, &rtw_getbbrfreg_cmdrsp_callback},
+	{WRITE_RFREG_CMD, NULL}, /*5*/
+	{READ_EEPROM_CMD, NULL},
+	{WRITE_EEPROM_CMD, NULL},
+	{READ_EFUSE_CMD, NULL},
+	{WRITE_EFUSE_CMD, NULL},
+
+	{READ_CAM_CMD, NULL},	/*10*/
+	{WRITE_CAM_CMD, NULL},
+	{SET_BCNITV_CMD, NULL},
+	{SET_MBIDCFG_CMD, NULL},
+	{JOIN_BSS_CMD, &rtw_joinbss_cmd_callback},  /*14*/
+	{DISCONNECT_CMD, &rtw_disassoc_cmd_callback}, /*15*/
+	{CREATE_BSS_CMD, &rtw_createbss_cmd_callback},
+	{SET_OP_MODE_CMD, NULL},
+	{SITE_SURVEY_CMD, &rtw_survey_cmd_callback}, /*18*/
+	{SET_AUTH_CMD, NULL},
+
+	{SET_KEY_CMD, NULL},	/*20*/
+	{SET_STA_KEY_CMD, &rtw_setstaKey_cmdrsp_callback},
+	{SET_ASSOC_STA_CMD, &rtw_setassocsta_cmdrsp_callback},
+	{DEL_ASSOC_STA_CMD, NULL},
+	{SET_STA_PWR_STATE_CMD, NULL},
+	{SET_BASIC_RATE_CMD, NULL}, /*25*/
+	{GET_BASIC_RATE_CMD, NULL},
+	{SET_DATA_RATE_CMD, NULL},
+	{GET_DATA_RATE_CMD, NULL},
+	{SET_PHY_INFO_CMD, NULL},
+
+	{GET_PHY_INFO_CMD, NULL}, /*30*/
+	{SET_PHY_CMD, NULL},
+	{GET_PHY_CMD, NULL},
+	{READ_RSSI_CMD, NULL},
+	{READ_GAIN_CMD, NULL},
+	{SET_ATIM_CMD, NULL}, /*35*/
+	{SET_PWR_MODE_CMD, NULL},
+	{JOIN_BSS_RPT_CMD, NULL},
+	{SET_RA_TABLE_CMD, NULL},
+	{GET_RA_TABLE_CMD, NULL},
+
+	{GET_CCX_REPORT_CMD, NULL}, /*40*/
+	{GET_DTM_REPORT_CMD,	NULL},
+	{GET_TX_RATE_STATISTICS_CMD, NULL},
+	{SET_USB_SUSPEND_CMD, NULL},
+	{SET_H2C_LBK_CMD, NULL},
+	{ADD_BA_REQ_CMD, NULL}, /*45*/
+	{SET_CHANNEL_CMD, NULL},		/*46*/
+	{SET_TX_POWER_CMD, NULL},
+	{SWITCH_ANTENNA_CMD, NULL},
+	{SET_CRYSTAL_CAP_CMD, NULL},
+	{SET_SINGLE_CARRIER_TX_CMD, NULL},	/*50*/
+
+	{SET_SINGLE_TONE_TX_CMD, NULL}, /*51*/
+	{SET_CARRIER_SUPPRESSION_TX_CMD, NULL},
+	{SET_CONTINUOUS_TX_CMD, NULL},
+	{SWITCH_BANDWIDTH_CMD, NULL},		/*54*/
+	{TX_BEACON_CMD, NULL},/*55*/
+
+	{SET_MLME_EVT_CMD, NULL},/*56*/
+	{SET_DRV_EXTRA_CMD, NULL},/*57*/
+	{SET_H2C_MSG_CMD, NULL},/*58*/
+	{SET_CHANNEL_PLAN_CMD, NULL},/*59*/
+
+	{SET_CHANNEL_SWITCH_CMD, NULL},/*60*/
+	{TDLS_CMD, NULL},/*61*/
+	{CHK_BMC_SLEEPQ_CMD, NULL}, /*62*/
+
+	{RUN_IN_THREAD_CMD, NULL},/*63*/
 };
 
 static struct cmd_hdl wlancmds[] = {
@@ -301,7 +301,7 @@ int rtw_cmd_filter(struct cmd_priv *pcmdpriv, struct cmd_obj *cmd_obj)
 {
 	u8 bAllow = false; /* set to true to allow enqueuing cmd when hw_init_completed is false */
 
-	if (cmd_obj->cmdcode == GEN_CMD_CODE(_SetChannelPlan))
+	if (cmd_obj->cmdcode == SET_CHANNEL_PLAN_CMD)
 		bAllow = true;
 
 	if ((!pcmdpriv->padapter->hw_init_completed && !bAllow) ||
@@ -343,8 +343,8 @@ struct	cmd_obj	*rtw_dequeue_cmd(struct cmd_priv *pcmdpriv)
 
 void rtw_free_cmd_obj(struct cmd_obj *pcmd)
 {
-	if ((pcmd->cmdcode != _JoinBss_CMD_) &&
-	    (pcmd->cmdcode != _CreateBss_CMD_)) {
+	if ((pcmd->cmdcode != JOIN_BSS_CMD) &&
+	    (pcmd->cmdcode != CREATE_BSS_CMD)) {
 		/* free parmbuf in cmd_obj */
 		kfree(pcmd->parmbuf);
 	}
@@ -501,7 +501,7 @@ int rtw_cmd_thread(void *context)
 			break;
 		}
 
-		if (pcmd->cmdcode == GEN_CMD_CODE(_Set_Drv_Extra)) {
+		if (pcmd->cmdcode == SET_DRV_EXTRA_CMD) {
 			extra_parm = (struct drvextra_cmd_parm *)pcmd->parmbuf;
 			if (extra_parm->pbuf && extra_parm->size > 0)
 				kfree(extra_parm->pbuf);
@@ -546,7 +546,7 @@ u8 rtw_sitesurvey_cmd(struct adapter  *padapter, struct ndis_802_11_ssid *ssid,
 
 	rtw_free_network_queue(padapter, false);
 
-	init_h2fwcmd_w_parm_no_rsp(ph2c, psurveyPara, GEN_CMD_CODE(_SiteSurvey));
+	init_h2fwcmd_w_parm_no_rsp(ph2c, psurveyPara, SITE_SURVEY_CMD);
 
 	/* psurveyPara->bsslimit = 48; */
 	psurveyPara->scan_mode = pmlmepriv->scan_mode;
@@ -609,7 +609,7 @@ u8 rtw_createbss_cmd(struct adapter  *padapter)
 	}
 
 	INIT_LIST_HEAD(&pcmd->list);
-	pcmd->cmdcode = _CreateBss_CMD_;
+	pcmd->cmdcode = CREATE_BSS_CMD;
 	pcmd->parmbuf = (unsigned char *)pdev_network;
 	pcmd->cmdsz = get_wlan_bssid_ex_sz((struct wlan_bssid_ex *)pdev_network);
 	pcmd->rsp = NULL;
@@ -642,7 +642,7 @@ int rtw_startbss_cmd(struct adapter  *padapter, int flags)
 		}
 
 		INIT_LIST_HEAD(&pcmd->list);
-		pcmd->cmdcode = GEN_CMD_CODE(_CreateBss);
+		pcmd->cmdcode = CREATE_BSS_CMD;
 		pcmd->parmbuf = NULL;
 		pcmd->cmdsz =  0;
 		pcmd->rsp = NULL;
@@ -777,7 +777,7 @@ u8 rtw_joinbss_cmd(struct adapter  *padapter, struct wlan_network *pnetwork)
 	pcmd->cmdsz = get_wlan_bssid_ex_sz(psecnetwork);/* get cmdsz before endian conversion */
 
 	INIT_LIST_HEAD(&pcmd->list);
-	pcmd->cmdcode = _JoinBss_CMD_;/* GEN_CMD_CODE(_JoinBss) */
+	pcmd->cmdcode = JOIN_BSS_CMD;
 	pcmd->parmbuf = (unsigned char *)psecnetwork;
 	pcmd->rsp = NULL;
 	pcmd->rspsz = 0;
@@ -811,7 +811,7 @@ u8 rtw_disassoc_cmd(struct adapter *padapter, u32 deauth_timeout_ms, bool enqueu
 			kfree(param);
 			goto exit;
 		}
-		init_h2fwcmd_w_parm_no_rsp(cmdobj, param, _DisConnect_CMD_);
+		init_h2fwcmd_w_parm_no_rsp(cmdobj, param, DISCONNECT_CMD);
 		res = rtw_enqueue_cmd(cmdpriv, cmdobj);
 	} else {
 		/* no need to enqueue, do the cmd hdl directly and free cmd parameter */
@@ -847,7 +847,7 @@ u8 rtw_setopmode_cmd(struct adapter  *padapter, enum ndis_802_11_network_infrast
 			goto exit;
 		}
 
-		init_h2fwcmd_w_parm_no_rsp(ph2c, psetop, _SetOpMode_CMD_);
+		init_h2fwcmd_w_parm_no_rsp(ph2c, psetop, SET_OP_MODE_CMD);
 		res = rtw_enqueue_cmd(pcmdpriv, ph2c);
 	} else {
 		setopmode_hdl(padapter, (u8 *)psetop);
@@ -904,7 +904,7 @@ u8 rtw_setstakey_cmd(struct adapter *padapter, struct sta_info *sta, u8 unicast_
 			goto exit;
 		}
 
-		init_h2fwcmd_w_parm_no_rsp(ph2c, psetstakey_para, _SetStaKey_CMD_);
+		init_h2fwcmd_w_parm_no_rsp(ph2c, psetstakey_para, SET_STA_KEY_CMD);
 		ph2c->rsp = (u8 *)psetstakey_rsp;
 		ph2c->rspsz = sizeof(struct set_stakey_rsp);
 		res = rtw_enqueue_cmd(pcmdpriv, ph2c);
@@ -955,7 +955,7 @@ u8 rtw_clearstakey_cmd(struct adapter *padapter, struct sta_info *sta, u8 enqueu
 			goto exit;
 		}
 
-		init_h2fwcmd_w_parm_no_rsp(ph2c, psetstakey_para, _SetStaKey_CMD_);
+		init_h2fwcmd_w_parm_no_rsp(ph2c, psetstakey_para, SET_STA_KEY_CMD);
 		ph2c->rsp = (u8 *)psetstakey_rsp;
 		ph2c->rspsz = sizeof(struct set_stakey_rsp);
 
@@ -993,7 +993,7 @@ u8 rtw_addbareq_cmd(struct adapter *padapter, u8 tid, u8 *addr)
 	paddbareq_parm->tid = tid;
 	memcpy(paddbareq_parm->addr, addr, ETH_ALEN);
 
-	init_h2fwcmd_w_parm_no_rsp(ph2c, paddbareq_parm, GEN_CMD_CODE(_AddBAReq));
+	init_h2fwcmd_w_parm_no_rsp(ph2c, paddbareq_parm, ADD_BA_REQ_CMD);
 
 	/* rtw_enqueue_cmd(pcmdpriv, ph2c); */
 	res = rtw_enqueue_cmd(pcmdpriv, ph2c);
@@ -1028,7 +1028,7 @@ u8 rtw_reset_securitypriv_cmd(struct adapter *padapter)
 	pdrvextra_cmd_parm->size = 0;
 	pdrvextra_cmd_parm->pbuf = NULL;
 
-	init_h2fwcmd_w_parm_no_rsp(ph2c, pdrvextra_cmd_parm, GEN_CMD_CODE(_Set_Drv_Extra));
+	init_h2fwcmd_w_parm_no_rsp(ph2c, pdrvextra_cmd_parm, SET_DRV_EXTRA_CMD);
 
 	/* rtw_enqueue_cmd(pcmdpriv, ph2c); */
 	res = rtw_enqueue_cmd(pcmdpriv, ph2c);
@@ -1061,7 +1061,7 @@ u8 rtw_free_assoc_resources_cmd(struct adapter *padapter)
 	pdrvextra_cmd_parm->size = 0;
 	pdrvextra_cmd_parm->pbuf = NULL;
 
-	init_h2fwcmd_w_parm_no_rsp(ph2c, pdrvextra_cmd_parm, GEN_CMD_CODE(_Set_Drv_Extra));
+	init_h2fwcmd_w_parm_no_rsp(ph2c, pdrvextra_cmd_parm, SET_DRV_EXTRA_CMD);
 
 	/* rtw_enqueue_cmd(pcmdpriv, ph2c); */
 	res = rtw_enqueue_cmd(pcmdpriv, ph2c);
@@ -1094,7 +1094,7 @@ u8 rtw_dynamic_chk_wk_cmd(struct adapter *padapter)
 	pdrvextra_cmd_parm->type = 0;
 	pdrvextra_cmd_parm->size = 0;
 	pdrvextra_cmd_parm->pbuf = NULL;
-	init_h2fwcmd_w_parm_no_rsp(ph2c, pdrvextra_cmd_parm, GEN_CMD_CODE(_Set_Drv_Extra));
+	init_h2fwcmd_w_parm_no_rsp(ph2c, pdrvextra_cmd_parm, SET_DRV_EXTRA_CMD);
 
 	/* rtw_enqueue_cmd(pcmdpriv, ph2c); */
 	res = rtw_enqueue_cmd(pcmdpriv, ph2c);
@@ -1343,7 +1343,7 @@ u8 rtw_lps_ctrl_wk_cmd(struct adapter *padapter, u8 lps_ctrl_type, u8 enqueue)
 		pdrvextra_cmd_parm->size = 0;
 		pdrvextra_cmd_parm->pbuf = NULL;
 
-		init_h2fwcmd_w_parm_no_rsp(ph2c, pdrvextra_cmd_parm, GEN_CMD_CODE(_Set_Drv_Extra));
+		init_h2fwcmd_w_parm_no_rsp(ph2c, pdrvextra_cmd_parm, SET_DRV_EXTRA_CMD);
 
 		res = rtw_enqueue_cmd(pcmdpriv, ph2c);
 	} else {
@@ -1384,7 +1384,7 @@ u8 rtw_dm_in_lps_wk_cmd(struct adapter *padapter)
 	pdrvextra_cmd_parm->size = 0;
 	pdrvextra_cmd_parm->pbuf = NULL;
 
-	init_h2fwcmd_w_parm_no_rsp(ph2c, pdrvextra_cmd_parm, GEN_CMD_CODE(_Set_Drv_Extra));
+	init_h2fwcmd_w_parm_no_rsp(ph2c, pdrvextra_cmd_parm, SET_DRV_EXTRA_CMD);
 
 	res = rtw_enqueue_cmd(pcmdpriv, ph2c);
 
@@ -1446,7 +1446,7 @@ u8 rtw_dm_ra_mask_wk_cmd(struct adapter *padapter, u8 *psta)
 	pdrvextra_cmd_parm->size = 0;
 	pdrvextra_cmd_parm->pbuf = psta;
 
-	init_h2fwcmd_w_parm_no_rsp(ph2c, pdrvextra_cmd_parm, GEN_CMD_CODE(_Set_Drv_Extra));
+	init_h2fwcmd_w_parm_no_rsp(ph2c, pdrvextra_cmd_parm, SET_DRV_EXTRA_CMD);
 
 	res = rtw_enqueue_cmd(pcmdpriv, ph2c);
 
@@ -1479,7 +1479,7 @@ u8 rtw_ps_cmd(struct adapter *padapter)
 	pdrvextra_cmd_parm->type = 0;
 	pdrvextra_cmd_parm->size = 0;
 	pdrvextra_cmd_parm->pbuf = NULL;
-	init_h2fwcmd_w_parm_no_rsp(ppscmd, pdrvextra_cmd_parm, GEN_CMD_CODE(_Set_Drv_Extra));
+	init_h2fwcmd_w_parm_no_rsp(ppscmd, pdrvextra_cmd_parm, SET_DRV_EXTRA_CMD);
 
 	res = rtw_enqueue_cmd(pcmdpriv, ppscmd);
 
@@ -1550,7 +1550,7 @@ u8 rtw_chk_hi_queue_cmd(struct adapter *padapter)
 	pdrvextra_cmd_parm->size = 0;
 	pdrvextra_cmd_parm->pbuf = NULL;
 
-	init_h2fwcmd_w_parm_no_rsp(ph2c, pdrvextra_cmd_parm, GEN_CMD_CODE(_Set_Drv_Extra));
+	init_h2fwcmd_w_parm_no_rsp(ph2c, pdrvextra_cmd_parm, SET_DRV_EXTRA_CMD);
 
 	res = rtw_enqueue_cmd(pcmdpriv, ph2c);
 
@@ -1642,7 +1642,7 @@ u8 rtw_c2h_packet_wk_cmd(struct adapter *padapter, u8 *pbuf, u16 length)
 	pdrvextra_cmd_parm->size = length;
 	pdrvextra_cmd_parm->pbuf = pbuf;
 
-	init_h2fwcmd_w_parm_no_rsp(ph2c, pdrvextra_cmd_parm, GEN_CMD_CODE(_Set_Drv_Extra));
+	init_h2fwcmd_w_parm_no_rsp(ph2c, pdrvextra_cmd_parm, SET_DRV_EXTRA_CMD);
 
 	res = rtw_enqueue_cmd(pcmdpriv, ph2c);
 
@@ -1677,7 +1677,7 @@ u8 rtw_c2h_wk_cmd(struct adapter *padapter, u8 *c2h_evt)
 	pdrvextra_cmd_parm->size =  c2h_evt ? 16 : 0;
 	pdrvextra_cmd_parm->pbuf = c2h_evt;
 
-	init_h2fwcmd_w_parm_no_rsp(ph2c, pdrvextra_cmd_parm, GEN_CMD_CODE(_Set_Drv_Extra));
+	init_h2fwcmd_w_parm_no_rsp(ph2c, pdrvextra_cmd_parm, SET_DRV_EXTRA_CMD);
 
 	res = rtw_enqueue_cmd(pcmdpriv, ph2c);
 
diff --git a/drivers/staging/rtl8723bs/core/rtw_mlme.c b/drivers/staging/rtl8723bs/core/rtw_mlme.c
index ddfc56f0253d..ddddd5e462b4 100644
--- a/drivers/staging/rtl8723bs/core/rtw_mlme.c
+++ b/drivers/staging/rtl8723bs/core/rtw_mlme.c
@@ -1862,7 +1862,7 @@ signed int rtw_set_auth(struct adapter *adapter, struct security_priv *psecurity
 
 	psetauthparm->mode = (unsigned char)psecuritypriv->dot11AuthAlgrthm;
 
-	pcmd->cmdcode = _SetAuth_CMD_;
+	pcmd->cmdcode = SET_AUTH_CMD;
 	pcmd->parmbuf = (unsigned char *)psetauthparm;
 	pcmd->cmdsz =  (sizeof(struct setauth_parm));
 	pcmd->rsp = NULL;
@@ -1933,7 +1933,7 @@ signed int rtw_set_key(struct adapter *adapter, struct security_priv *psecurityp
 			goto exit;
 		}
 
-		pcmd->cmdcode = _SetKey_CMD_;
+		pcmd->cmdcode = SET_KEY_CMD;
 		pcmd->parmbuf = (u8 *)psetkeyparm;
 		pcmd->cmdsz =  (sizeof(struct setkey_parm));
 		pcmd->rsp = NULL;
diff --git a/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c b/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c
index 5f00fe282d1b..abcc666d5dbe 100644
--- a/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c
+++ b/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c
@@ -4391,7 +4391,7 @@ void report_survey_event(struct adapter *padapter, union recv_frame *precv_frame
 
 	INIT_LIST_HEAD(&pcmd_obj->list);
 
-	pcmd_obj->cmdcode = GEN_CMD_CODE(_Set_MLME_EVT);
+	pcmd_obj->cmdcode = SET_MLME_EVT_CMD;
 	pcmd_obj->cmdsz = cmdsz;
 	pcmd_obj->parmbuf = pevtcmd;
 
@@ -4444,7 +4444,7 @@ void report_surveydone_event(struct adapter *padapter)
 
 	INIT_LIST_HEAD(&pcmd_obj->list);
 
-	pcmd_obj->cmdcode = GEN_CMD_CODE(_Set_MLME_EVT);
+	pcmd_obj->cmdcode = SET_MLME_EVT_CMD;
 	pcmd_obj->cmdsz = cmdsz;
 	pcmd_obj->parmbuf = pevtcmd;
 
@@ -4489,7 +4489,7 @@ void report_join_res(struct adapter *padapter, int res)
 
 	INIT_LIST_HEAD(&pcmd_obj->list);
 
-	pcmd_obj->cmdcode = GEN_CMD_CODE(_Set_MLME_EVT);
+	pcmd_obj->cmdcode = SET_MLME_EVT_CMD;
 	pcmd_obj->cmdsz = cmdsz;
 	pcmd_obj->parmbuf = pevtcmd;
 
@@ -4538,7 +4538,7 @@ void report_wmm_edca_update(struct adapter *padapter)
 
 	INIT_LIST_HEAD(&pcmd_obj->list);
 
-	pcmd_obj->cmdcode = GEN_CMD_CODE(_Set_MLME_EVT);
+	pcmd_obj->cmdcode = SET_MLME_EVT_CMD;
 	pcmd_obj->cmdsz = cmdsz;
 	pcmd_obj->parmbuf = pevtcmd;
 
@@ -4584,7 +4584,7 @@ void report_del_sta_event(struct adapter *padapter, unsigned char *MacAddr, unsi
 
 	INIT_LIST_HEAD(&pcmd_obj->list);
 
-	pcmd_obj->cmdcode = GEN_CMD_CODE(_Set_MLME_EVT);
+	pcmd_obj->cmdcode = SET_MLME_EVT_CMD;
 	pcmd_obj->cmdsz = cmdsz;
 	pcmd_obj->parmbuf = pevtcmd;
 
@@ -4635,7 +4635,7 @@ void report_add_sta_event(struct adapter *padapter, unsigned char *MacAddr, int
 
 	INIT_LIST_HEAD(&pcmd_obj->list);
 
-	pcmd_obj->cmdcode = GEN_CMD_CODE(_Set_MLME_EVT);
+	pcmd_obj->cmdcode = SET_MLME_EVT_CMD;
 	pcmd_obj->cmdsz = cmdsz;
 	pcmd_obj->parmbuf = pevtcmd;
 
@@ -5085,7 +5085,7 @@ void survey_timer_hdl(struct timer_list *t)
 			return;
 		}
 
-		init_h2fwcmd_w_parm_no_rsp(ph2c, psurveyPara, GEN_CMD_CODE(_SiteSurvey));
+		init_h2fwcmd_w_parm_no_rsp(ph2c, psurveyPara, SITE_SURVEY_CMD);
 		rtw_enqueue_cmd(pcmdpriv, ph2c);
 	}
 }
@@ -5696,7 +5696,7 @@ u8 chk_bmc_sleepq_cmd(struct adapter *padapter)
 		goto exit;
 	}
 
-	init_h2fwcmd_w_parm_no_parm_rsp(ph2c, GEN_CMD_CODE(_ChkBMCSleepq));
+	init_h2fwcmd_w_parm_no_parm_rsp(ph2c, CHK_BMC_SLEEPQ_CMD);
 
 	res = rtw_enqueue_cmd(pcmdpriv, ph2c);
 
@@ -5734,7 +5734,7 @@ u8 set_tx_beacon_cmd(struct adapter *padapter)
 				      pmlmeinfo->hidden_ssid_mode);
 	ptxBeacon_parm->network.ie_length += len_diff;
 
-	init_h2fwcmd_w_parm_no_rsp(ph2c, ptxBeacon_parm, GEN_CMD_CODE(_TX_Beacon));
+	init_h2fwcmd_w_parm_no_rsp(ph2c, ptxBeacon_parm, TX_BEACON_CMD);
 
 	res = rtw_enqueue_cmd(pcmdpriv, ph2c);
 
diff --git a/drivers/staging/rtl8723bs/include/rtw_cmd.h b/drivers/staging/rtl8723bs/include/rtw_cmd.h
index c4c3edee809d..af94d31425ba 100644
--- a/drivers/staging/rtl8723bs/include/rtw_cmd.h
+++ b/drivers/staging/rtl8723bs/include/rtw_cmd.h
@@ -551,9 +551,6 @@ struct RunInThread_param {
 };
 
 
-#define GEN_CMD_CODE(cmd)	cmd ## _CMD_
-
-
 /*
 
 Result:
@@ -630,86 +627,81 @@ struct _cmd_callback {
 };
 
 enum {
-	GEN_CMD_CODE(_Read_MACREG),	/*0*/
-	GEN_CMD_CODE(_Write_MACREG),
-	GEN_CMD_CODE(_Read_BBREG),
-	GEN_CMD_CODE(_Write_BBREG),
-	GEN_CMD_CODE(_Read_RFREG),
-	GEN_CMD_CODE(_Write_RFREG), /*5*/
-	GEN_CMD_CODE(_Read_EEPROM),
-	GEN_CMD_CODE(_Write_EEPROM),
-	GEN_CMD_CODE(_Read_EFUSE),
-	GEN_CMD_CODE(_Write_EFUSE),
-
-	GEN_CMD_CODE(_Read_CAM),	/*10*/
-	GEN_CMD_CODE(_Write_CAM),
-	GEN_CMD_CODE(_setBCNITV),
-	GEN_CMD_CODE(_setMBIDCFG),
-	GEN_CMD_CODE(_JoinBss),   /*14*/
-	GEN_CMD_CODE(_DisConnect), /*15*/
-	GEN_CMD_CODE(_CreateBss),
-	GEN_CMD_CODE(_SetOpMode),
-	GEN_CMD_CODE(_SiteSurvey),  /*18*/
-	GEN_CMD_CODE(_SetAuth),
-
-	GEN_CMD_CODE(_SetKey),	/*20*/
-	GEN_CMD_CODE(_SetStaKey),
-	GEN_CMD_CODE(_SetAssocSta),
-	GEN_CMD_CODE(_DelAssocSta),
-	GEN_CMD_CODE(_SetStaPwrState),
-	GEN_CMD_CODE(_SetBasicRate), /*25*/
-	GEN_CMD_CODE(_GetBasicRate),
-	GEN_CMD_CODE(_SetDataRate),
-	GEN_CMD_CODE(_GetDataRate),
-	GEN_CMD_CODE(_SetPhyInfo),
-
-	GEN_CMD_CODE(_GetPhyInfo),	/*30*/
-	GEN_CMD_CODE(_SetPhy),
-	GEN_CMD_CODE(_GetPhy),
-	GEN_CMD_CODE(_readRssi),
-	GEN_CMD_CODE(_readGain),
-	GEN_CMD_CODE(_SetAtim), /*35*/
-	GEN_CMD_CODE(_SetPwrMode),
-	GEN_CMD_CODE(_JoinbssRpt),
-	GEN_CMD_CODE(_SetRaTable),
-	GEN_CMD_CODE(_GetRaTable),
-
-	GEN_CMD_CODE(_GetCCXReport), /*40*/
-	GEN_CMD_CODE(_GetDTMReport),
-	GEN_CMD_CODE(_GetTXRateStatistics),
-	GEN_CMD_CODE(_SetUsbSuspend),
-	GEN_CMD_CODE(_SetH2cLbk),
-	GEN_CMD_CODE(_AddBAReq), /*45*/
-	GEN_CMD_CODE(_SetChannel), /*46*/
-	GEN_CMD_CODE(_SetTxPower),
-	GEN_CMD_CODE(_SwitchAntenna),
-	GEN_CMD_CODE(_SetCrystalCap),
-	GEN_CMD_CODE(_SetSingleCarrierTx), /*50*/
-
-	GEN_CMD_CODE(_SetSingleToneTx),/*51*/
-	GEN_CMD_CODE(_SetCarrierSuppressionTx),
-	GEN_CMD_CODE(_SetContinuousTx),
-	GEN_CMD_CODE(_SwitchBandwidth), /*54*/
-	GEN_CMD_CODE(_TX_Beacon), /*55*/
-
-	GEN_CMD_CODE(_Set_MLME_EVT), /*56*/
-	GEN_CMD_CODE(_Set_Drv_Extra), /*57*/
-	GEN_CMD_CODE(_Set_H2C_MSG), /*58*/
-
-	GEN_CMD_CODE(_SetChannelPlan), /*59*/
-
-	GEN_CMD_CODE(_SetChannelSwitch), /*60*/
-	GEN_CMD_CODE(_TDLS), /*61*/
-	GEN_CMD_CODE(_ChkBMCSleepq), /*62*/
-
-	GEN_CMD_CODE(_RunInThreadCMD), /*63*/
+	READ_MACREG_CMD,	/*0*/
+	WRITE_MACREG_CMD,
+	READ_BBREG_CMD,
+	WRITE_BBREG_CMD,
+	READ_RFREG_CMD,
+	WRITE_RFREG_CMD, /*5*/
+	READ_EEPROM_CMD,
+	WRITE_EEPROM_CMD,
+	READ_EFUSE_CMD,
+	WRITE_EFUSE_CMD,
+
+	READ_CAM_CMD,	/*10*/
+	WRITE_CAM_CMD,
+	SET_BCNITV_CMD,
+	SET_MBIDCFG_CMD,
+	JOIN_BSS_CMD,   /*14*/
+	DISCONNECT_CMD, /*15*/
+	CREATE_BSS_CMD,
+	SET_OP_MODE_CMD,
+	SITE_SURVEY_CMD,  /*18*/
+	SET_AUTH_CMD,
+
+	SET_KEY_CMD,	/*20*/
+	SET_STA_KEY_CMD,
+	SET_ASSOC_STA_CMD,
+	DEL_ASSOC_STA_CMD,
+	SET_STA_PWR_STATE_CMD,
+	SET_BASIC_RATE_CMD, /*25*/
+	GET_BASIC_RATE_CMD,
+	SET_DATA_RATE_CMD,
+	GET_DATA_RATE_CMD,
+	SET_PHY_INFO_CMD,
+
+	GET_PHY_INFO_CMD,	/*30*/
+	SET_PHY_CMD,
+	GET_PHY_CMD,
+	READ_RSSI_CMD,
+	READ_GAIN_CMD,
+	SET_ATIM_CMD, /*35*/
+	SET_PWR_MODE_CMD,
+	JOIN_BSS_RPT_CMD,
+	SET_RA_TABLE_CMD,
+	GET_RA_TABLE_CMD,
+
+	GET_CCX_REPORT_CMD, /*40*/
+	GET_DTM_REPORT_CMD,
+	GET_TX_RATE_STATISTICS_CMD,
+	SET_USB_SUSPEND_CMD,
+	SET_H2C_LBK_CMD,
+	ADD_BA_REQ_CMD, /*45*/
+	SET_CHANNEL_CMD, /*46*/
+	SET_TX_POWER_CMD,
+	SWITCH_ANTENNA_CMD,
+	SET_CRYSTAL_CAP_CMD,
+	SET_SINGLE_CARRIER_TX_CMD, /*50*/
+
+	SET_SINGLE_TONE_TX_CMD,/*51*/
+	SET_CARRIER_SUPPRESSION_TX_CMD,
+	SET_CONTINUOUS_TX_CMD,
+	SWITCH_BANDWIDTH_CMD, /*54*/
+	TX_BEACON_CMD, /*55*/
+
+	SET_MLME_EVT_CMD, /*56*/
+	SET_DRV_EXTRA_CMD, /*57*/
+	SET_H2C_MSG_CMD, /*58*/
+
+	SET_CHANNEL_PLAN_CMD, /*59*/
+
+	SET_CHANNEL_SWITCH_CMD, /*60*/
+	TDLS_CMD, /*61*/
+	CHK_BMC_SLEEPQ_CMD, /*62*/
+
+	RUN_IN_THREAD_CMD, /*63*/
 
 	MAX_H2CCMD
 };
 
-#define _GetBBReg_CMD_		_Read_BBREG_CMD_
-#define _SetBBReg_CMD_		_Write_BBREG_CMD_
-#define _GetRFReg_CMD_		_Read_RFREG_CMD_
-#define _SetRFReg_CMD_		_Write_RFREG_CMD_
-
 #endif /*  _CMD_H_ */

-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH] staging: greybus: remove driver depending on nonexistent config option
From: Ethan Nelson-Moore @ 2026-06-10  1:14 UTC (permalink / raw)
  To: Agatha Isabelle Moreira
  Cc: greybus-dev, linux-staging, linux-kernel, Johan Hovold,
	Alex Elder, Greg Kroah-Hartman, Jakub Kicinski, Namjae Jeon
In-Reply-To: <guidaiFoSi.541328.2026169aiie51mhcpgO149o_23655_LINUXKERNEL_AGATHA_@links.agatha.dev>

Hi, Agatha,

On Tue, Jun 9, 2026 at 4:56 PM Agatha Isabelle Moreira <code@agatha.dev> wrote:
> On Tue, Jun 09, 2026 at 01:26:58PM -0700, Ethan Nelson-Moore wrote:
> > The Greybus Arche Platform driver depends on the config option
> > USB_HSIC_USB3613, which has never existed in mainline Linux. Therefore,
>
> Actually it dosn't. The `depends on` line says:
>
>      depends on USB_HSIC_USB3613 || COMPILE_TEST
>
> That's a logical OR operation, it depends on USB_HSIC_USB3613 OR
> COMPILE_TEST.

The function of COMPILE_TEST is to allow drivers to be compiled in
environments where they do not work, to enable better code coverage
and easier testing of tree-wide changes without cross compilers. It
should never be enabled for production use cases. So yes, technically,
it does depend on either option, but only on one "real" option.
Therefore, I don't think there is anything wrong with the commit
message.

Ethan

^ permalink raw reply

* Re: [PATCH] staging: sm750fb: make g_fbmode array const
From: Brock Haftner @ 2026-06-10  0:58 UTC (permalink / raw)
  To: Ahmet Sezgin Duran
  Cc: Sudip Mukherjee, Teddy Wang, Greg Kroah-Hartman, outreachy,
	linux-fbdev, linux-staging, linux-kernel
In-Reply-To: <0d8d9f38-3cce-4da2-9ba8-f8e99f7b4dee@sezginduran.net>

On 6/8/26, Ahmet Sezgin Duran wrote:
> Did you compile this patch while enabling sm750fb driver in the config?

No, I did not. I apologize for the mistake.
I ran make drivers/staging/sm750fb/ prior to submitting, however I
failed to realize the driver was not enabled in my .config.

Cheers,
Brock

^ permalink raw reply

* Re: [PATCH] staging: greybus: remove driver depending on nonexistent config option
From: Agatha Isabelle Moreira @ 2026-06-09 23:56 UTC (permalink / raw)
  To: Ethan Nelson-Moore
  Cc: greybus-dev, linux-staging, linux-kernel, Johan Hovold,
	Alex Elder, Greg Kroah-Hartman, Jakub Kicinski, Namjae Jeon
In-Reply-To: <20260609202705.183875-1-enelsonmoore@gmail.com>

On Tue, Jun 09, 2026 at 01:26:58PM -0700, Ethan Nelson-Moore wrote:
> The Greybus Arche Platform driver depends on the config option
> USB_HSIC_USB3613, which has never existed in mainline Linux. Therefore,

Actually it dosn't. The `depends on` line says:

     depends on USB_HSIC_USB3613 || COMPILE_TEST

That's a logical OR operation, it depends on USB_HSIC_USB3613 OR
COMPILE_TEST.

> it is impossible for anyone to be using it with unmodified mainline
> kernels. Remove it and move the former maintainer to the CREDITS file.

Indeed, the impossible for anyone to be using holds true, as mentioned
in:
https://lore.kernel.org/all/aVuPidYUPZxCOdRp@stanley.mountain/

I think this should be removed, but I think the commit message could be
worked.

Sincerely,
Agatha Isabelle Moreira

^ permalink raw reply

* [PATCH v3] staging: rtl8723bs: fix unnamed parameters warning detected at checkpatch
From: Rodrigo Gobbi @ 2026-06-09 22:07 UTC (permalink / raw)
  To: gregkh
  Cc: ~lkcamp/patches, linux-kernel-mentees, linux-staging,
	linux-kernel, a.nasrolahi01

Detected the following WARNING: function definition argument
'struct adapter *' should also have an identifier name.
Add explicit names on those definitions.

Signed-off-by: Rodrigo Gobbi <rodrigo.gobbi.7@gmail.com>
---
Hi,

v2 was not applying anymore after a rebase, sending v3.
Tks and regards.

Changelog:
v3: rebase; the rtl8723bs/include/rtw_btcoex.h from v2 is not needed anymore.
v2: https://lore.kernel.org/all/20260523142312.9223-1-rodrigo.gobbi.7@gmail.com/
v1: https://lore.kernel.org/all/20260407133416.13983-1-rodrigo.gobbi.7@gmail.com/
---
 drivers/staging/rtl8723bs/include/hal_intf.h    | 2 +-
 drivers/staging/rtl8723bs/include/rtw_pwrctrl.h | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/staging/rtl8723bs/include/hal_intf.h b/drivers/staging/rtl8723bs/include/hal_intf.h
index ee8ad26325b5..8a85e8419e47 100644
--- a/drivers/staging/rtl8723bs/include/hal_intf.h
+++ b/drivers/staging/rtl8723bs/include/hal_intf.h
@@ -239,7 +239,7 @@ c2h_id_filter rtw_hal_c2h_id_filter_ccx(struct adapter *adapter);
 s32 rtw_hal_macid_sleep(struct adapter *padapter, u32 macid);
 s32 rtw_hal_macid_wakeup(struct adapter *padapter, u32 macid);
 
-s32 rtw_hal_fill_h2c_cmd(struct adapter *, u8 ElementID, u32 CmdLen, u8 *pCmdBuffer);
+s32 rtw_hal_fill_h2c_cmd(struct adapter *adapter, u8 ElementID, u32 CmdLen, u8 *pCmdBuffer);
 
 void SetHwReg8723BS(struct adapter *padapter, u8 variable, u8 *val);
 void GetHwReg8723BS(struct adapter *padapter, u8 variable, u8 *val);
diff --git a/drivers/staging/rtl8723bs/include/rtw_pwrctrl.h b/drivers/staging/rtl8723bs/include/rtw_pwrctrl.h
index 6977892a5742..c8592c4753c2 100644
--- a/drivers/staging/rtl8723bs/include/rtw_pwrctrl.h
+++ b/drivers/staging/rtl8723bs/include/rtw_pwrctrl.h
@@ -215,8 +215,8 @@ struct pwrctrl_priv {
 extern void rtw_init_pwrctrl_priv(struct adapter *adapter);
 extern void rtw_free_pwrctrl_priv(struct adapter *adapter);
 
-s32 rtw_register_task_alive(struct adapter *, u32 task);
-void rtw_unregister_task_alive(struct adapter *, u32 task);
+s32 rtw_register_task_alive(struct adapter *adapter, u32 task);
+void rtw_unregister_task_alive(struct adapter *adapter, u32 task);
 extern s32 rtw_register_tx_alive(struct adapter *padapter);
 extern void rtw_unregister_tx_alive(struct adapter *padapter);
 extern s32 rtw_register_cmd_alive(struct adapter *padapter);
-- 
2.48.1


^ permalink raw reply related

* [PATCH] staging: media: atomisp: use kvmalloc_objs() in make_histogram()
From: Rodrigo Gobbi @ 2026-06-09 21:46 UTC (permalink / raw)
  To: andy, hansg, mchehab, sakari.ailus, gregkh, feng
  Cc: ~lkcamp/patches, linux-kernel-mentees, linux-kernel, linux-media,
	linux-staging

Replace kvmalloc() with multiply with kvmalloc_objs(), which handles
the size multiplication internally with overflow checking, silenting
checkpatch warn.

Signed-off-by: Rodrigo Gobbi <rodrigo.gobbi.7@gmail.com>
---
Hi, all,
There is a ongoing effort like this for other files from atomisp
at [1], yet, it is not covering the same file.
Tks and regards.

[1] https://lore.kernel.org/all/20260413112904.98864-1-feng@innora.ai/
---
 drivers/staging/media/atomisp/pci/sh_css_metrics.c | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/drivers/staging/media/atomisp/pci/sh_css_metrics.c b/drivers/staging/media/atomisp/pci/sh_css_metrics.c
index edf473dd86ca..d3ae737f1764 100644
--- a/drivers/staging/media/atomisp/pci/sh_css_metrics.c
+++ b/drivers/staging/media/atomisp/pci/sh_css_metrics.c
@@ -59,16 +59,13 @@ make_histogram(struct sh_css_pc_histogram *histogram, unsigned int length)
 		return;
 	if (histogram->run)
 		return;
-	histogram->run = kvmalloc(length * sizeof(*histogram->run),
-				  GFP_KERNEL);
+	histogram->run = kvmalloc_objs(*histogram->run, length);
 	if (!histogram->run)
 		return;
-	histogram->stall = kvmalloc(length * sizeof(*histogram->stall),
-				    GFP_KERNEL);
+	histogram->stall = kvmalloc_objs(*histogram->stall, length);
 	if (!histogram->stall)
 		return;
-	histogram->msink = kvmalloc(length * sizeof(*histogram->msink),
-				    GFP_KERNEL);
+	histogram->msink = kvmalloc_objs(*histogram->msink, length);
 	if (!histogram->msink)
 		return;
 
-- 
2.48.1


^ permalink raw reply related

* [PATCH] staging: greybus: remove driver depending on nonexistent config option
From: Ethan Nelson-Moore @ 2026-06-09 20:26 UTC (permalink / raw)
  To: greybus-dev, linux-staging, linux-kernel
  Cc: Johan Hovold, Alex Elder, Greg Kroah-Hartman, Ethan Nelson-Moore,
	Jakub Kicinski, Namjae Jeon

The Greybus Arche Platform driver depends on the config option
USB_HSIC_USB3613, which has never existed in mainline Linux. Therefore,
it is impossible for anyone to be using it with unmodified mainline
kernels. Remove it and move the former maintainer to the CREDITS file.

Signed-off-by: Ethan Nelson-Moore <enelsonmoore@gmail.com>
---
 CREDITS                                  |   4 +
 MAINTAINERS                              |   7 -
 drivers/staging/greybus/Kconfig          |   9 -
 drivers/staging/greybus/Makefile         |   6 -
 drivers/staging/greybus/arche-apb-ctrl.c | 491 -----------------
 drivers/staging/greybus/arche-platform.c | 653 -----------------------
 drivers/staging/greybus/arche_platform.h |  28 -
 7 files changed, 4 insertions(+), 1194 deletions(-)
 delete mode 100644 drivers/staging/greybus/arche-apb-ctrl.c
 delete mode 100644 drivers/staging/greybus/arche-platform.c
 delete mode 100644 drivers/staging/greybus/arche_platform.h

diff --git a/CREDITS b/CREDITS
index 17962bdd6dbd..19a493aceabf 100644
--- a/CREDITS
+++ b/CREDITS
@@ -1701,6 +1701,10 @@ S: Talstr. 1
 S: D - 72072 Tuebingen
 S: Germany
 
+N: Vaibhav Hiremath
+E: hvaibhav.linux@gmail.com
+D: Greybus Arche Platform driver
+
 N: Richard Hirst
 E: richard@sleepie.demon.co.uk
 E: rhirst@linuxcare.com
diff --git a/MAINTAINERS b/MAINTAINERS
index e035a3be797c..2461e8a6a45a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11122,13 +11122,6 @@ M:	Bryan O'Donoghue <pure.logic@nexus-software.ie>
 S:	Maintained
 F:	drivers/staging/greybus/loopback.c
 
-GREYBUS PLATFORM DRIVERS
-M:	Vaibhav Hiremath <hvaibhav.linux@gmail.com>
-S:	Maintained
-F:	drivers/staging/greybus/arche-apb-ctrl.c
-F:	drivers/staging/greybus/arche-platform.c
-F:	drivers/staging/greybus/arche_platform.h
-
 GREYBUS SDIO/GPIO/SPI PROTOCOLS DRIVERS
 M:	Rui Miguel Silva <rmfrfs@gmail.com>
 S:	Maintained
diff --git a/drivers/staging/greybus/Kconfig b/drivers/staging/greybus/Kconfig
index 7635f3e850fa..c25da61570eb 100644
--- a/drivers/staging/greybus/Kconfig
+++ b/drivers/staging/greybus/Kconfig
@@ -204,13 +204,4 @@ config GREYBUS_USB
 
 endif	# GREYBUS_BRIDGED_PHY
 
-config GREYBUS_ARCHE
-	tristate "Greybus Arche Platform driver"
-	depends on USB_HSIC_USB3613 || COMPILE_TEST
-	help
-	  Select this option if you have an Arche device.
-
-	  To compile this code as a module, chose M here: the module
-	  will be called gb-arche.ko
-
 endif	# GREYBUS
diff --git a/drivers/staging/greybus/Makefile b/drivers/staging/greybus/Makefile
index 7c5e89622334..7b9807f8a1a3 100644
--- a/drivers/staging/greybus/Makefile
+++ b/drivers/staging/greybus/Makefile
@@ -65,9 +65,3 @@ obj-$(CONFIG_GREYBUS_SDIO)		+= gb-sdio.o
 obj-$(CONFIG_GREYBUS_SPI)		+= gb-spi.o gb-spilib.o
 obj-$(CONFIG_GREYBUS_UART)		+= gb-uart.o
 obj-$(CONFIG_GREYBUS_USB)		+= gb-usb.o
-
-
-# Greybus Platform driver
-gb-arche-y	:= arche-platform.o arche-apb-ctrl.o
-
-obj-$(CONFIG_GREYBUS_ARCHE)	+= gb-arche.o
diff --git a/drivers/staging/greybus/arche-apb-ctrl.c b/drivers/staging/greybus/arche-apb-ctrl.c
deleted file mode 100644
index 19a6e59b6d5c..000000000000
--- a/drivers/staging/greybus/arche-apb-ctrl.c
+++ /dev/null
@@ -1,491 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Arche Platform driver to control APB.
- *
- * Copyright 2014-2015 Google Inc.
- * Copyright 2014-2015 Linaro Ltd.
- */
-
-#include <linux/clk.h>
-#include <linux/delay.h>
-#include <linux/gpio/consumer.h>
-#include <linux/interrupt.h>
-#include <linux/module.h>
-#include <linux/pinctrl/consumer.h>
-#include <linux/platform_device.h>
-#include <linux/pm.h>
-#include <linux/property.h>
-#include <linux/regulator/consumer.h>
-#include <linux/spinlock.h>
-#include <linux/mod_devicetable.h>
-#include "arche_platform.h"
-
-static void apb_bootret_deassert(struct device *dev);
-
-struct arche_apb_ctrl_drvdata {
-	/* Control GPIO signals to and from AP <=> AP Bridges */
-	struct gpio_desc *resetn;
-	struct gpio_desc *boot_ret;
-	struct gpio_desc *pwroff;
-	struct gpio_desc *wake_in;
-	struct gpio_desc *wake_out;
-	struct gpio_desc *pwrdn;
-
-	enum arche_platform_state state;
-	bool init_disabled;
-
-	struct regulator *vcore;
-	struct regulator *vio;
-
-	struct gpio_desc *clk_en;
-	struct clk *clk;
-
-	struct pinctrl *pinctrl;
-	struct pinctrl_state *pin_default;
-
-	/* V2: SPI Bus control  */
-	struct gpio_desc *spi_en;
-	bool spi_en_polarity_high;
-};
-
-/*
- * Note that these low level api's are active high
- */
-static inline void deassert_reset(struct gpio_desc *gpio)
-{
-	gpiod_set_raw_value(gpio, 1);
-}
-
-static inline void assert_reset(struct gpio_desc *gpio)
-{
-	gpiod_set_raw_value(gpio, 0);
-}
-
-/*
- * Note: Please do not modify the below sequence, as it is as per the spec
- */
-static int coldboot_seq(struct platform_device *pdev)
-{
-	struct device *dev = &pdev->dev;
-	struct arche_apb_ctrl_drvdata *apb = platform_get_drvdata(pdev);
-	int ret;
-
-	if (apb->init_disabled ||
-	    apb->state == ARCHE_PLATFORM_STATE_ACTIVE)
-		return 0;
-
-	/* Hold APB in reset state */
-	assert_reset(apb->resetn);
-
-	if (apb->state == ARCHE_PLATFORM_STATE_FW_FLASHING && apb->spi_en)
-		devm_gpiod_put(dev, apb->spi_en);
-
-	/* Enable power to APB */
-	if (!IS_ERR(apb->vcore)) {
-		ret = regulator_enable(apb->vcore);
-		if (ret) {
-			dev_err(dev, "failed to enable core regulator\n");
-			return ret;
-		}
-	}
-
-	if (!IS_ERR(apb->vio)) {
-		ret = regulator_enable(apb->vio);
-		if (ret) {
-			dev_err(dev, "failed to enable IO regulator\n");
-			return ret;
-		}
-	}
-
-	apb_bootret_deassert(dev);
-
-	/* On DB3 clock was not mandatory */
-	if (apb->clk_en)
-		gpiod_set_value(apb->clk_en, 1);
-
-	usleep_range(100, 200);
-
-	/* deassert reset to APB : Active-low signal */
-	deassert_reset(apb->resetn);
-
-	apb->state = ARCHE_PLATFORM_STATE_ACTIVE;
-
-	return 0;
-}
-
-static int fw_flashing_seq(struct platform_device *pdev)
-{
-	struct device *dev = &pdev->dev;
-	struct arche_apb_ctrl_drvdata *apb = platform_get_drvdata(pdev);
-	int ret;
-
-	if (apb->init_disabled ||
-	    apb->state == ARCHE_PLATFORM_STATE_FW_FLASHING)
-		return 0;
-
-	ret = regulator_enable(apb->vcore);
-	if (ret) {
-		dev_err(dev, "failed to enable core regulator\n");
-		return ret;
-	}
-
-	ret = regulator_enable(apb->vio);
-	if (ret) {
-		dev_err(dev, "failed to enable IO regulator\n");
-		return ret;
-	}
-
-	if (apb->spi_en) {
-		unsigned long flags;
-
-		if (apb->spi_en_polarity_high)
-			flags = GPIOD_OUT_HIGH;
-		else
-			flags = GPIOD_OUT_LOW;
-
-		apb->spi_en = devm_gpiod_get(dev, "spi-en", flags);
-		if (IS_ERR(apb->spi_en)) {
-			ret = PTR_ERR(apb->spi_en);
-			dev_err(dev, "Failed requesting SPI bus en GPIO: %d\n",
-				ret);
-			return ret;
-		}
-	}
-
-	/* for flashing device should be in reset state */
-	assert_reset(apb->resetn);
-	apb->state = ARCHE_PLATFORM_STATE_FW_FLASHING;
-
-	return 0;
-}
-
-static int standby_boot_seq(struct platform_device *pdev)
-{
-	struct device *dev = &pdev->dev;
-	struct arche_apb_ctrl_drvdata *apb = platform_get_drvdata(pdev);
-
-	if (apb->init_disabled)
-		return 0;
-
-	/*
-	 * Even if it is in OFF state,
-	 * then we do not want to change the state
-	 */
-	if (apb->state == ARCHE_PLATFORM_STATE_STANDBY ||
-	    apb->state == ARCHE_PLATFORM_STATE_OFF)
-		return 0;
-
-	if (apb->state == ARCHE_PLATFORM_STATE_FW_FLASHING && apb->spi_en)
-		devm_gpiod_put(dev, apb->spi_en);
-
-	/*
-	 * As per WDM spec, do nothing
-	 *
-	 * Pasted from WDM spec,
-	 *  - A falling edge on POWEROFF_L is detected (a)
-	 *  - WDM enters standby mode, but no output signals are changed
-	 */
-
-	/* TODO: POWEROFF_L is input to WDM module  */
-	apb->state = ARCHE_PLATFORM_STATE_STANDBY;
-	return 0;
-}
-
-static void poweroff_seq(struct platform_device *pdev)
-{
-	struct device *dev = &pdev->dev;
-	struct arche_apb_ctrl_drvdata *apb = platform_get_drvdata(pdev);
-
-	if (apb->init_disabled || apb->state == ARCHE_PLATFORM_STATE_OFF)
-		return;
-
-	if (apb->state == ARCHE_PLATFORM_STATE_FW_FLASHING && apb->spi_en)
-		devm_gpiod_put(dev, apb->spi_en);
-
-	/* disable the clock */
-	if (apb->clk_en)
-		gpiod_set_value(apb->clk_en, 0);
-
-	if (!IS_ERR(apb->vcore) && regulator_is_enabled(apb->vcore) > 0)
-		regulator_disable(apb->vcore);
-
-	if (!IS_ERR(apb->vio) && regulator_is_enabled(apb->vio) > 0)
-		regulator_disable(apb->vio);
-
-	/* As part of exit, put APB back in reset state */
-	assert_reset(apb->resetn);
-	apb->state = ARCHE_PLATFORM_STATE_OFF;
-
-	/* TODO: May have to send an event to SVC about this exit */
-}
-
-static void apb_bootret_deassert(struct device *dev)
-{
-	struct arche_apb_ctrl_drvdata *apb = dev_get_drvdata(dev);
-
-	gpiod_set_value(apb->boot_ret, 0);
-}
-
-int apb_ctrl_coldboot(struct device *dev)
-{
-	return coldboot_seq(to_platform_device(dev));
-}
-
-int apb_ctrl_fw_flashing(struct device *dev)
-{
-	return fw_flashing_seq(to_platform_device(dev));
-}
-
-int apb_ctrl_standby_boot(struct device *dev)
-{
-	return standby_boot_seq(to_platform_device(dev));
-}
-
-void apb_ctrl_poweroff(struct device *dev)
-{
-	poweroff_seq(to_platform_device(dev));
-}
-
-static ssize_t state_store(struct device *dev,
-			   struct device_attribute *attr,
-			   const char *buf, size_t count)
-{
-	struct platform_device *pdev = to_platform_device(dev);
-	struct arche_apb_ctrl_drvdata *apb = platform_get_drvdata(pdev);
-	int ret = 0;
-	bool is_disabled;
-
-	if (sysfs_streq(buf, "off")) {
-		if (apb->state == ARCHE_PLATFORM_STATE_OFF)
-			return count;
-
-		poweroff_seq(pdev);
-	} else if (sysfs_streq(buf, "active")) {
-		if (apb->state == ARCHE_PLATFORM_STATE_ACTIVE)
-			return count;
-
-		poweroff_seq(pdev);
-		is_disabled = apb->init_disabled;
-		apb->init_disabled = false;
-		ret = coldboot_seq(pdev);
-		if (ret)
-			apb->init_disabled = is_disabled;
-	} else if (sysfs_streq(buf, "standby")) {
-		if (apb->state == ARCHE_PLATFORM_STATE_STANDBY)
-			return count;
-
-		ret = standby_boot_seq(pdev);
-	} else if (sysfs_streq(buf, "fw_flashing")) {
-		if (apb->state == ARCHE_PLATFORM_STATE_FW_FLASHING)
-			return count;
-
-		/*
-		 * First we want to make sure we power off everything
-		 * and then enter FW flashing state
-		 */
-		poweroff_seq(pdev);
-		ret = fw_flashing_seq(pdev);
-	} else {
-		dev_err(dev, "unknown state\n");
-		ret = -EINVAL;
-	}
-
-	return ret ? ret : count;
-}
-
-static ssize_t state_show(struct device *dev,
-			  struct device_attribute *attr, char *buf)
-{
-	struct arche_apb_ctrl_drvdata *apb = dev_get_drvdata(dev);
-
-	switch (apb->state) {
-	case ARCHE_PLATFORM_STATE_OFF:
-		return sysfs_emit(buf, "off%s\n",
-				apb->init_disabled ? ",disabled" : "");
-	case ARCHE_PLATFORM_STATE_ACTIVE:
-		return sysfs_emit(buf, "active\n");
-	case ARCHE_PLATFORM_STATE_STANDBY:
-		return sysfs_emit(buf, "standby\n");
-	case ARCHE_PLATFORM_STATE_FW_FLASHING:
-		return sysfs_emit(buf, "fw_flashing\n");
-	default:
-		return sysfs_emit(buf, "unknown state\n");
-	}
-}
-
-static DEVICE_ATTR_RW(state);
-
-static int apb_ctrl_get_fw_data(struct platform_device *pdev,
-				struct arche_apb_ctrl_drvdata *apb)
-{
-	struct device *dev = &pdev->dev;
-	int ret;
-
-	apb->resetn = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
-	if (IS_ERR(apb->resetn)) {
-		ret = PTR_ERR(apb->resetn);
-		dev_err(dev, "Failed requesting reset GPIO: %d\n", ret);
-		return ret;
-	}
-
-	apb->boot_ret = devm_gpiod_get(dev, "boot-ret", GPIOD_OUT_LOW);
-	if (IS_ERR(apb->boot_ret)) {
-		ret = PTR_ERR(apb->boot_ret);
-		dev_err(dev, "Failed requesting bootret GPIO: %d\n", ret);
-		return ret;
-	}
-
-	/* It's not mandatory to support power management interface */
-	apb->pwroff = devm_gpiod_get_optional(dev, "pwr-off", GPIOD_IN);
-	if (IS_ERR(apb->pwroff)) {
-		ret = PTR_ERR(apb->pwroff);
-		dev_err(dev, "Failed requesting pwroff_n GPIO: %d\n", ret);
-		return ret;
-	}
-
-	/* Do not make clock mandatory as of now (for DB3) */
-	apb->clk_en = devm_gpiod_get_optional(dev, "clock-en", GPIOD_OUT_LOW);
-	if (IS_ERR(apb->clk_en)) {
-		ret = PTR_ERR(apb->clk_en);
-		dev_err(dev, "Failed requesting APB clock en GPIO: %d\n", ret);
-		return ret;
-	}
-
-	apb->pwrdn = devm_gpiod_get(dev, "pwr-down", GPIOD_OUT_LOW);
-	if (IS_ERR(apb->pwrdn)) {
-		ret = PTR_ERR(apb->pwrdn);
-		dev_warn(dev, "Failed requesting power down GPIO: %d\n", ret);
-		return ret;
-	}
-
-	/* Regulators are optional, as we may have fixed supply coming in */
-	apb->vcore = devm_regulator_get(dev, "vcore");
-	if (IS_ERR(apb->vcore))
-		dev_warn(dev, "no core regulator found\n");
-
-	apb->vio = devm_regulator_get(dev, "vio");
-	if (IS_ERR(apb->vio))
-		dev_warn(dev, "no IO regulator found\n");
-
-	apb->pinctrl = devm_pinctrl_get(&pdev->dev);
-	if (IS_ERR(apb->pinctrl)) {
-		dev_err(&pdev->dev, "could not get pinctrl handle\n");
-		return PTR_ERR(apb->pinctrl);
-	}
-	apb->pin_default = pinctrl_lookup_state(apb->pinctrl, "default");
-	if (IS_ERR(apb->pin_default)) {
-		dev_err(&pdev->dev, "could not get default pin state\n");
-		return PTR_ERR(apb->pin_default);
-	}
-
-	/* Only applicable for platform >= V2 */
-	if (device_property_read_bool(&pdev->dev, "gb,spi-en-active-high"))
-		apb->spi_en_polarity_high = true;
-
-	return 0;
-}
-
-static int arche_apb_ctrl_probe(struct platform_device *pdev)
-{
-	int ret;
-	struct arche_apb_ctrl_drvdata *apb;
-	struct device *dev = &pdev->dev;
-
-	apb = devm_kzalloc(&pdev->dev, sizeof(*apb), GFP_KERNEL);
-	if (!apb)
-		return -ENOMEM;
-
-	ret = apb_ctrl_get_fw_data(pdev, apb);
-	if (ret) {
-		dev_err(dev, "failed to get apb devicetree data %d\n", ret);
-		return ret;
-	}
-
-	/* Initially set APB to OFF state */
-	apb->state = ARCHE_PLATFORM_STATE_OFF;
-	/* Check whether device needs to be enabled on boot */
-	if (device_property_read_bool(&pdev->dev, "arche,init-disable"))
-		apb->init_disabled = true;
-
-	platform_set_drvdata(pdev, apb);
-
-	/* Create sysfs interface to allow user to change state dynamically */
-	ret = device_create_file(dev, &dev_attr_state);
-	if (ret) {
-		dev_err(dev, "failed to create state file in sysfs\n");
-		return ret;
-	}
-
-	dev_info(&pdev->dev, "Device registered successfully\n");
-	return 0;
-}
-
-static void arche_apb_ctrl_remove(struct platform_device *pdev)
-{
-	device_remove_file(&pdev->dev, &dev_attr_state);
-	poweroff_seq(pdev);
-	platform_set_drvdata(pdev, NULL);
-}
-
-static int __maybe_unused arche_apb_ctrl_suspend(struct device *dev)
-{
-	/*
-	 * If timing profile permits, we may shutdown bridge
-	 * completely
-	 *
-	 * TODO: sequence ??
-	 *
-	 * Also, need to make sure we meet precondition for unipro suspend
-	 * Precondition: Definition ???
-	 */
-	return 0;
-}
-
-static int __maybe_unused arche_apb_ctrl_resume(struct device *dev)
-{
-	/*
-	 * At least for ES2 we have to meet the delay requirement between
-	 * unipro switch and AP bridge init, depending on whether bridge is in
-	 * OFF state or standby state.
-	 *
-	 * Based on whether bridge is in standby or OFF state we may have to
-	 * assert multiple signals. Please refer to WDM spec, for more info.
-	 *
-	 */
-	return 0;
-}
-
-static void arche_apb_ctrl_shutdown(struct platform_device *pdev)
-{
-	apb_ctrl_poweroff(&pdev->dev);
-}
-
-static SIMPLE_DEV_PM_OPS(arche_apb_ctrl_pm_ops, arche_apb_ctrl_suspend,
-			 arche_apb_ctrl_resume);
-
-static const struct of_device_id arche_apb_ctrl_of_match[] = {
-	{ .compatible = "usbffff,2", },
-	{ },
-};
-MODULE_DEVICE_TABLE(of, arche_apb_ctrl_of_match);
-
-static struct platform_driver arche_apb_ctrl_device_driver = {
-	.probe		= arche_apb_ctrl_probe,
-	.remove		= arche_apb_ctrl_remove,
-	.shutdown	= arche_apb_ctrl_shutdown,
-	.driver		= {
-		.name	= "arche-apb-ctrl",
-		.pm	= &arche_apb_ctrl_pm_ops,
-		.of_match_table = arche_apb_ctrl_of_match,
-	}
-};
-
-int __init arche_apb_init(void)
-{
-	return platform_driver_register(&arche_apb_ctrl_device_driver);
-}
-
-void __exit arche_apb_exit(void)
-{
-	platform_driver_unregister(&arche_apb_ctrl_device_driver);
-}
diff --git a/drivers/staging/greybus/arche-platform.c b/drivers/staging/greybus/arche-platform.c
deleted file mode 100644
index de5de59ea8ab..000000000000
--- a/drivers/staging/greybus/arche-platform.c
+++ /dev/null
@@ -1,653 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Arche Platform driver to enable Unipro link.
- *
- * Copyright 2014-2015 Google Inc.
- * Copyright 2014-2015 Linaro Ltd.
- */
-
-#include <linux/clk.h>
-#include <linux/delay.h>
-#include <linux/gpio/consumer.h>
-#include <linux/init.h>
-#include <linux/module.h>
-#include <linux/of_platform.h>
-#include <linux/pinctrl/consumer.h>
-#include <linux/platform_device.h>
-#include <linux/pm.h>
-#include <linux/interrupt.h>
-#include <linux/irq.h>
-#include <linux/suspend.h>
-#include <linux/time.h>
-#include <linux/greybus.h>
-#include <linux/of.h>
-#include "arche_platform.h"
-
-#if IS_ENABLED(CONFIG_USB_HSIC_USB3613)
-#include <linux/usb/usb3613.h>
-#else
-static inline int usb3613_hub_mode_ctrl(bool unused)
-{
-	return 0;
-}
-#endif
-
-#define WD_COLDBOOT_PULSE_WIDTH_MS	30
-
-enum svc_wakedetect_state {
-	WD_STATE_IDLE,			/* Default state = pulled high/low */
-	WD_STATE_BOOT_INIT,		/* WD = falling edge (low) */
-	WD_STATE_COLDBOOT_TRIG,		/* WD = rising edge (high), > 30msec */
-	WD_STATE_STANDBYBOOT_TRIG,	/* As of now not used ?? */
-	WD_STATE_COLDBOOT_START,	/* Cold boot process started */
-	WD_STATE_STANDBYBOOT_START,	/* Not used */
-};
-
-struct arche_platform_drvdata {
-	/* Control GPIO signals to and from AP <=> SVC */
-	struct gpio_desc *svc_reset;
-	bool is_reset_act_hi;
-	struct gpio_desc *svc_sysboot;
-	struct gpio_desc *wake_detect; /* bi-dir,maps to WAKE_MOD & WAKE_FRAME signals */
-
-	enum arche_platform_state state;
-
-	struct gpio_desc *svc_refclk_req;
-	struct clk *svc_ref_clk;
-
-	struct pinctrl *pinctrl;
-	struct pinctrl_state *pin_default;
-
-	int num_apbs;
-
-	enum svc_wakedetect_state wake_detect_state;
-	int wake_detect_irq;
-	spinlock_t wake_lock;			/* Protect wake_detect_state */
-	struct mutex platform_state_mutex;	/* Protect state */
-	unsigned long wake_detect_start;
-	struct notifier_block pm_notifier;
-
-	struct device *dev;
-};
-
-/* Requires calling context to hold arche_pdata->platform_state_mutex */
-static void arche_platform_set_state(struct arche_platform_drvdata *arche_pdata,
-				     enum arche_platform_state state)
-{
-	arche_pdata->state = state;
-}
-
-/* Requires arche_pdata->wake_lock is held by calling context */
-static void arche_platform_set_wake_detect_state(struct arche_platform_drvdata *arche_pdata,
-						 enum svc_wakedetect_state state)
-{
-	arche_pdata->wake_detect_state = state;
-}
-
-static inline void svc_reset_onoff(struct gpio_desc *gpio, bool onoff)
-{
-	gpiod_set_raw_value(gpio, onoff);
-}
-
-static int apb_cold_boot(struct device *dev, void *data)
-{
-	int ret;
-
-	ret = apb_ctrl_coldboot(dev);
-	if (ret)
-		dev_warn(dev, "failed to coldboot\n");
-
-	/*Child nodes are independent, so do not exit coldboot operation */
-	return 0;
-}
-
-static int apb_poweroff(struct device *dev, void *data)
-{
-	apb_ctrl_poweroff(dev);
-
-	/* Enable HUB3613 into HUB mode. */
-	if (usb3613_hub_mode_ctrl(false))
-		dev_warn(dev, "failed to control hub device\n");
-
-	return 0;
-}
-
-static void arche_platform_wd_irq_en(struct arche_platform_drvdata *arche_pdata)
-{
-	/* Enable interrupt here, to read event back from SVC */
-	enable_irq(arche_pdata->wake_detect_irq);
-}
-
-static irqreturn_t arche_platform_wd_irq_thread(int irq, void *devid)
-{
-	struct arche_platform_drvdata *arche_pdata = devid;
-	unsigned long flags;
-
-	spin_lock_irqsave(&arche_pdata->wake_lock, flags);
-	if (arche_pdata->wake_detect_state != WD_STATE_COLDBOOT_TRIG) {
-		/* Something is wrong */
-		spin_unlock_irqrestore(&arche_pdata->wake_lock, flags);
-		return IRQ_HANDLED;
-	}
-
-	arche_platform_set_wake_detect_state(arche_pdata,
-					     WD_STATE_COLDBOOT_START);
-	spin_unlock_irqrestore(&arche_pdata->wake_lock, flags);
-
-	/* It should complete power cycle, so first make sure it is poweroff */
-	device_for_each_child(arche_pdata->dev, NULL, apb_poweroff);
-
-	/* Bring APB out of reset: cold boot sequence */
-	device_for_each_child(arche_pdata->dev, NULL, apb_cold_boot);
-
-	/* Enable HUB3613 into HUB mode. */
-	if (usb3613_hub_mode_ctrl(true))
-		dev_warn(arche_pdata->dev, "failed to control hub device\n");
-
-	spin_lock_irqsave(&arche_pdata->wake_lock, flags);
-	arche_platform_set_wake_detect_state(arche_pdata, WD_STATE_IDLE);
-	spin_unlock_irqrestore(&arche_pdata->wake_lock, flags);
-
-	return IRQ_HANDLED;
-}
-
-static irqreturn_t arche_platform_wd_irq(int irq, void *devid)
-{
-	struct arche_platform_drvdata *arche_pdata = devid;
-	unsigned long flags;
-
-	spin_lock_irqsave(&arche_pdata->wake_lock, flags);
-
-	if (gpiod_get_value(arche_pdata->wake_detect)) {
-		/* wake/detect rising */
-
-		/*
-		 * If wake/detect line goes high after low, within less than
-		 * 30msec, then standby boot sequence is initiated, which is not
-		 * supported/implemented as of now. So ignore it.
-		 */
-		if (arche_pdata->wake_detect_state == WD_STATE_BOOT_INIT) {
-			if (time_before(jiffies,
-					arche_pdata->wake_detect_start +
-					msecs_to_jiffies(WD_COLDBOOT_PULSE_WIDTH_MS))) {
-				arche_platform_set_wake_detect_state(arche_pdata,
-								     WD_STATE_IDLE);
-			} else {
-				/*
-				 * Check we are not in middle of irq thread
-				 * already
-				 */
-				if (arche_pdata->wake_detect_state !=
-						WD_STATE_COLDBOOT_START) {
-					arche_platform_set_wake_detect_state(arche_pdata,
-									     WD_STATE_COLDBOOT_TRIG);
-					spin_unlock_irqrestore(&arche_pdata->wake_lock,
-							       flags);
-					return IRQ_WAKE_THREAD;
-				}
-			}
-		}
-	} else {
-		/* wake/detect falling */
-		if (arche_pdata->wake_detect_state == WD_STATE_IDLE) {
-			arche_pdata->wake_detect_start = jiffies;
-			/*
-			 * In the beginning, when wake/detect goes low
-			 * (first time), we assume it is meant for coldboot
-			 * and set the flag. If wake/detect line stays low
-			 * beyond 30msec, then it is coldboot else fallback
-			 * to standby boot.
-			 */
-			arche_platform_set_wake_detect_state(arche_pdata,
-							     WD_STATE_BOOT_INIT);
-		}
-	}
-
-	spin_unlock_irqrestore(&arche_pdata->wake_lock, flags);
-
-	return IRQ_HANDLED;
-}
-
-/*
- * Requires arche_pdata->platform_state_mutex to be held
- */
-static int
-arche_platform_coldboot_seq(struct arche_platform_drvdata *arche_pdata)
-{
-	int ret;
-
-	if (arche_pdata->state == ARCHE_PLATFORM_STATE_ACTIVE)
-		return 0;
-
-	dev_info(arche_pdata->dev, "Booting from cold boot state\n");
-
-	svc_reset_onoff(arche_pdata->svc_reset, arche_pdata->is_reset_act_hi);
-
-	gpiod_set_value(arche_pdata->svc_sysboot, 0);
-	usleep_range(100, 200);
-
-	ret = clk_prepare_enable(arche_pdata->svc_ref_clk);
-	if (ret) {
-		dev_err(arche_pdata->dev, "failed to enable svc_ref_clk: %d\n",
-			ret);
-		return ret;
-	}
-
-	/* bring SVC out of reset */
-	svc_reset_onoff(arche_pdata->svc_reset, !arche_pdata->is_reset_act_hi);
-
-	arche_platform_set_state(arche_pdata, ARCHE_PLATFORM_STATE_ACTIVE);
-
-	return 0;
-}
-
-/*
- * Requires arche_pdata->platform_state_mutex to be held
- */
-static int
-arche_platform_fw_flashing_seq(struct arche_platform_drvdata *arche_pdata)
-{
-	int ret;
-
-	if (arche_pdata->state == ARCHE_PLATFORM_STATE_FW_FLASHING)
-		return 0;
-
-	dev_info(arche_pdata->dev, "Switching to FW flashing state\n");
-
-	svc_reset_onoff(arche_pdata->svc_reset, arche_pdata->is_reset_act_hi);
-
-	gpiod_set_value(arche_pdata->svc_sysboot, 1);
-
-	usleep_range(100, 200);
-
-	ret = clk_prepare_enable(arche_pdata->svc_ref_clk);
-	if (ret) {
-		dev_err(arche_pdata->dev, "failed to enable svc_ref_clk: %d\n",
-			ret);
-		return ret;
-	}
-
-	svc_reset_onoff(arche_pdata->svc_reset,	!arche_pdata->is_reset_act_hi);
-
-	arche_platform_set_state(arche_pdata, ARCHE_PLATFORM_STATE_FW_FLASHING);
-
-	return 0;
-}
-
-/*
- * Requires arche_pdata->platform_state_mutex to be held
- */
-static void
-arche_platform_poweroff_seq(struct arche_platform_drvdata *arche_pdata)
-{
-	unsigned long flags;
-
-	if (arche_pdata->state == ARCHE_PLATFORM_STATE_OFF)
-		return;
-
-	/* If in fw_flashing mode, then no need to repeate things again */
-	if (arche_pdata->state != ARCHE_PLATFORM_STATE_FW_FLASHING) {
-		disable_irq(arche_pdata->wake_detect_irq);
-
-		spin_lock_irqsave(&arche_pdata->wake_lock, flags);
-		arche_platform_set_wake_detect_state(arche_pdata,
-						     WD_STATE_IDLE);
-		spin_unlock_irqrestore(&arche_pdata->wake_lock, flags);
-	}
-
-	clk_disable_unprepare(arche_pdata->svc_ref_clk);
-
-	/* As part of exit, put APB back in reset state */
-	svc_reset_onoff(arche_pdata->svc_reset,	arche_pdata->is_reset_act_hi);
-
-	arche_platform_set_state(arche_pdata, ARCHE_PLATFORM_STATE_OFF);
-}
-
-static ssize_t state_store(struct device *dev,
-			   struct device_attribute *attr,
-			   const char *buf, size_t count)
-{
-	struct arche_platform_drvdata *arche_pdata = dev_get_drvdata(dev);
-	int ret = 0;
-
-	mutex_lock(&arche_pdata->platform_state_mutex);
-
-	if (sysfs_streq(buf, "off")) {
-		if (arche_pdata->state == ARCHE_PLATFORM_STATE_OFF)
-			goto exit;
-
-		/*  If SVC goes down, bring down APB's as well */
-		device_for_each_child(arche_pdata->dev, NULL, apb_poweroff);
-
-		arche_platform_poweroff_seq(arche_pdata);
-
-	} else if (sysfs_streq(buf, "active")) {
-		if (arche_pdata->state == ARCHE_PLATFORM_STATE_ACTIVE)
-			goto exit;
-
-		/* First we want to make sure we power off everything
-		 * and then activate back again
-		 */
-		device_for_each_child(arche_pdata->dev, NULL, apb_poweroff);
-		arche_platform_poweroff_seq(arche_pdata);
-
-		arche_platform_wd_irq_en(arche_pdata);
-		ret = arche_platform_coldboot_seq(arche_pdata);
-		if (ret)
-			goto exit;
-
-	} else if (sysfs_streq(buf, "standby")) {
-		if (arche_pdata->state == ARCHE_PLATFORM_STATE_STANDBY)
-			goto exit;
-
-		dev_warn(arche_pdata->dev, "standby state not supported\n");
-	} else if (sysfs_streq(buf, "fw_flashing")) {
-		if (arche_pdata->state == ARCHE_PLATFORM_STATE_FW_FLASHING)
-			goto exit;
-
-		/*
-		 * Here we only control SVC.
-		 *
-		 * In case of FW_FLASHING mode we do not want to control
-		 * APBs, as in case of V2, SPI bus is shared between both
-		 * the APBs. So let user chose which APB he wants to flash.
-		 */
-		arche_platform_poweroff_seq(arche_pdata);
-
-		ret = arche_platform_fw_flashing_seq(arche_pdata);
-		if (ret)
-			goto exit;
-	} else {
-		dev_err(arche_pdata->dev, "unknown state\n");
-		ret = -EINVAL;
-	}
-
-exit:
-	mutex_unlock(&arche_pdata->platform_state_mutex);
-	return ret ? ret : count;
-}
-
-static ssize_t state_show(struct device *dev,
-			  struct device_attribute *attr, char *buf)
-{
-	struct arche_platform_drvdata *arche_pdata = dev_get_drvdata(dev);
-
-	switch (arche_pdata->state) {
-	case ARCHE_PLATFORM_STATE_OFF:
-		return sysfs_emit(buf, "off\n");
-	case ARCHE_PLATFORM_STATE_ACTIVE:
-		return sysfs_emit(buf, "active\n");
-	case ARCHE_PLATFORM_STATE_STANDBY:
-		return sysfs_emit(buf, "standby\n");
-	case ARCHE_PLATFORM_STATE_FW_FLASHING:
-		return sysfs_emit(buf, "fw_flashing\n");
-	default:
-		return sysfs_emit(buf, "unknown state\n");
-	}
-}
-
-static DEVICE_ATTR_RW(state);
-
-static int arche_platform_pm_notifier(struct notifier_block *notifier,
-				      unsigned long pm_event, void *unused)
-{
-	struct arche_platform_drvdata *arche_pdata =
-		container_of(notifier, struct arche_platform_drvdata,
-			     pm_notifier);
-	int ret = NOTIFY_DONE;
-
-	mutex_lock(&arche_pdata->platform_state_mutex);
-	switch (pm_event) {
-	case PM_SUSPEND_PREPARE:
-		if (arche_pdata->state != ARCHE_PLATFORM_STATE_ACTIVE) {
-			ret = NOTIFY_STOP;
-			break;
-		}
-		device_for_each_child(arche_pdata->dev, NULL, apb_poweroff);
-		arche_platform_poweroff_seq(arche_pdata);
-		break;
-	case PM_POST_SUSPEND:
-		if (arche_pdata->state != ARCHE_PLATFORM_STATE_OFF)
-			break;
-
-		arche_platform_wd_irq_en(arche_pdata);
-		arche_platform_coldboot_seq(arche_pdata);
-		break;
-	default:
-		break;
-	}
-	mutex_unlock(&arche_pdata->platform_state_mutex);
-
-	return ret;
-}
-
-static int arche_platform_probe(struct platform_device *pdev)
-{
-	struct arche_platform_drvdata *arche_pdata;
-	struct device *dev = &pdev->dev;
-	struct device_node *np = dev->of_node;
-	int ret;
-	unsigned int flags;
-
-	arche_pdata = devm_kzalloc(&pdev->dev, sizeof(*arche_pdata),
-				   GFP_KERNEL);
-	if (!arche_pdata)
-		return -ENOMEM;
-
-	/* setup svc reset gpio */
-	arche_pdata->is_reset_act_hi = of_property_read_bool(np,
-							     "svc,reset-active-high");
-	if (arche_pdata->is_reset_act_hi)
-		flags = GPIOD_OUT_HIGH;
-	else
-		flags = GPIOD_OUT_LOW;
-
-	arche_pdata->svc_reset = devm_gpiod_get(dev, "svc,reset", flags);
-	if (IS_ERR(arche_pdata->svc_reset)) {
-		ret = PTR_ERR(arche_pdata->svc_reset);
-		dev_err(dev, "failed to request svc-reset GPIO: %d\n", ret);
-		return ret;
-	}
-	arche_platform_set_state(arche_pdata, ARCHE_PLATFORM_STATE_OFF);
-
-	arche_pdata->svc_sysboot = devm_gpiod_get(dev, "svc,sysboot",
-						  GPIOD_OUT_LOW);
-	if (IS_ERR(arche_pdata->svc_sysboot)) {
-		ret = PTR_ERR(arche_pdata->svc_sysboot);
-		dev_err(dev, "failed to request sysboot0 GPIO: %d\n", ret);
-		return ret;
-	}
-
-	/* setup the clock request gpio first */
-	arche_pdata->svc_refclk_req = devm_gpiod_get(dev, "svc,refclk-req",
-						     GPIOD_IN);
-	if (IS_ERR(arche_pdata->svc_refclk_req)) {
-		ret = PTR_ERR(arche_pdata->svc_refclk_req);
-		dev_err(dev, "failed to request svc-clk-req GPIO: %d\n", ret);
-		return ret;
-	}
-
-	/* setup refclk2 to follow the pin */
-	arche_pdata->svc_ref_clk = devm_clk_get(dev, "svc_ref_clk");
-	if (IS_ERR(arche_pdata->svc_ref_clk)) {
-		ret = PTR_ERR(arche_pdata->svc_ref_clk);
-		dev_err(dev, "failed to get svc_ref_clk: %d\n", ret);
-		return ret;
-	}
-
-	platform_set_drvdata(pdev, arche_pdata);
-
-	arche_pdata->num_apbs = of_get_child_count(np);
-	dev_dbg(dev, "Number of APB's available - %d\n", arche_pdata->num_apbs);
-
-	arche_pdata->wake_detect = devm_gpiod_get(dev, "svc,wake-detect",
-						  GPIOD_IN);
-	if (IS_ERR(arche_pdata->wake_detect)) {
-		ret = PTR_ERR(arche_pdata->wake_detect);
-		dev_err(dev, "Failed requesting wake_detect GPIO: %d\n", ret);
-		return ret;
-	}
-
-	arche_platform_set_wake_detect_state(arche_pdata, WD_STATE_IDLE);
-
-	arche_pdata->dev = &pdev->dev;
-
-	spin_lock_init(&arche_pdata->wake_lock);
-	mutex_init(&arche_pdata->platform_state_mutex);
-	arche_pdata->wake_detect_irq =
-		gpiod_to_irq(arche_pdata->wake_detect);
-
-	ret = devm_request_threaded_irq(dev, arche_pdata->wake_detect_irq,
-					arche_platform_wd_irq,
-					arche_platform_wd_irq_thread,
-					IRQF_TRIGGER_FALLING |
-					IRQF_TRIGGER_RISING | IRQF_ONESHOT,
-					dev_name(dev), arche_pdata);
-	if (ret) {
-		dev_err(dev, "failed to request wake detect IRQ %d\n", ret);
-		return ret;
-	}
-	disable_irq(arche_pdata->wake_detect_irq);
-
-	ret = device_create_file(dev, &dev_attr_state);
-	if (ret) {
-		dev_err(dev, "failed to create state file in sysfs\n");
-		return ret;
-	}
-
-	ret = of_platform_populate(np, NULL, NULL, dev);
-	if (ret) {
-		dev_err(dev, "failed to populate child nodes %d\n", ret);
-		goto err_device_remove;
-	}
-
-	arche_pdata->pm_notifier.notifier_call = arche_platform_pm_notifier;
-	ret = register_pm_notifier(&arche_pdata->pm_notifier);
-	if (ret) {
-		dev_err(dev, "failed to register pm notifier %d\n", ret);
-		goto err_depopulate;
-	}
-
-	/* Explicitly power off if requested */
-	if (!of_property_read_bool(pdev->dev.of_node, "arche,init-off")) {
-		mutex_lock(&arche_pdata->platform_state_mutex);
-		ret = arche_platform_coldboot_seq(arche_pdata);
-		if (ret) {
-			mutex_unlock(&arche_pdata->platform_state_mutex);
-			dev_err(dev, "Failed to cold boot svc %d\n", ret);
-			goto err_unregister_pm_notifier;
-		}
-		arche_platform_wd_irq_en(arche_pdata);
-		mutex_unlock(&arche_pdata->platform_state_mutex);
-	}
-
-	dev_info(dev, "Device registered successfully\n");
-	return 0;
-
-err_unregister_pm_notifier:
-	unregister_pm_notifier(&arche_pdata->pm_notifier);
-err_depopulate:
-	of_platform_depopulate(dev);
-err_device_remove:
-	device_remove_file(&pdev->dev, &dev_attr_state);
-	return ret;
-}
-
-static void arche_platform_remove(struct platform_device *pdev)
-{
-	struct arche_platform_drvdata *arche_pdata = platform_get_drvdata(pdev);
-
-	unregister_pm_notifier(&arche_pdata->pm_notifier);
-	device_remove_file(&pdev->dev, &dev_attr_state);
-	of_platform_depopulate(&pdev->dev);
-	arche_platform_poweroff_seq(arche_pdata);
-
-	if (usb3613_hub_mode_ctrl(false))
-		dev_warn(arche_pdata->dev, "failed to control hub device\n");
-}
-
-static __maybe_unused int arche_platform_suspend(struct device *dev)
-{
-	/*
-	 * If timing profile permits, we may shutdown bridge
-	 * completely
-	 *
-	 * TODO: define shutdown sequence
-	 *
-	 * Also, need to make sure we meet precondition for unipro suspend
-	 * Precondition: Definition ???
-	 */
-	return 0;
-}
-
-static __maybe_unused int arche_platform_resume(struct device *dev)
-{
-	/*
-	 * At least for ES2 we have to meet the delay requirement between
-	 * unipro switch and AP bridge init, depending on whether bridge is in
-	 * OFF state or standby state.
-	 *
-	 * Based on whether bridge is in standby or OFF state we may have to
-	 * assert multiple signals. Please refer to WDM spec, for more info.
-	 *
-	 */
-	return 0;
-}
-
-static void arche_platform_shutdown(struct platform_device *pdev)
-{
-	struct arche_platform_drvdata *arche_pdata = platform_get_drvdata(pdev);
-
-	arche_platform_poweroff_seq(arche_pdata);
-
-	usb3613_hub_mode_ctrl(false);
-}
-
-static SIMPLE_DEV_PM_OPS(arche_platform_pm_ops,
-			arche_platform_suspend,
-			arche_platform_resume);
-
-static const struct of_device_id arche_platform_of_match[] = {
-	/* Use PID/VID of SVC device */
-	{ .compatible = "google,arche-platform", },
-	{ },
-};
-MODULE_DEVICE_TABLE(of, arche_platform_of_match);
-
-static struct platform_driver arche_platform_device_driver = {
-	.probe		= arche_platform_probe,
-	.remove		= arche_platform_remove,
-	.shutdown	= arche_platform_shutdown,
-	.driver		= {
-		.name	= "arche-platform-ctrl",
-		.pm	= &arche_platform_pm_ops,
-		.of_match_table = arche_platform_of_match,
-	}
-};
-
-static int __init arche_init(void)
-{
-	int retval;
-
-	retval = platform_driver_register(&arche_platform_device_driver);
-	if (retval)
-		return retval;
-
-	retval = arche_apb_init();
-	if (retval)
-		platform_driver_unregister(&arche_platform_device_driver);
-
-	return retval;
-}
-module_init(arche_init);
-
-static void __exit arche_exit(void)
-{
-	arche_apb_exit();
-	platform_driver_unregister(&arche_platform_device_driver);
-}
-module_exit(arche_exit);
-
-MODULE_LICENSE("GPL v2");
-MODULE_AUTHOR("Vaibhav Hiremath <vaibhav.hiremath@linaro.org>");
-MODULE_DESCRIPTION("Arche Platform Driver");
diff --git a/drivers/staging/greybus/arche_platform.h b/drivers/staging/greybus/arche_platform.h
deleted file mode 100644
index 9d997f2d6517..000000000000
--- a/drivers/staging/greybus/arche_platform.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * Arche Platform driver to enable Unipro link.
- *
- * Copyright 2015-2016 Google Inc.
- * Copyright 2015-2016 Linaro Ltd.
- */
-
-#ifndef __ARCHE_PLATFORM_H
-#define __ARCHE_PLATFORM_H
-
-enum arche_platform_state {
-	ARCHE_PLATFORM_STATE_OFF,
-	ARCHE_PLATFORM_STATE_ACTIVE,
-	ARCHE_PLATFORM_STATE_STANDBY,
-	ARCHE_PLATFORM_STATE_FW_FLASHING,
-};
-
-int __init arche_apb_init(void);
-void __exit arche_apb_exit(void);
-
-/* Operational states for the APB device */
-int apb_ctrl_coldboot(struct device *dev);
-int apb_ctrl_fw_flashing(struct device *dev);
-int apb_ctrl_standby_boot(struct device *dev);
-void apb_ctrl_poweroff(struct device *dev);
-
-#endif	/* __ARCHE_PLATFORM_H */
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH] media: atomisp: Fix local variable shadowing warnings
From: Rhys Tumelty @ 2026-06-09 20:09 UTC (permalink / raw)
  To: andy.shevchenko
  Cc: andy, dan.carpenter, gregkh, hansg, linux-kernel, linux-media,
	linux-staging, mchehab, rhys, sakari.ailus
In-Reply-To: <CAHp75Ve3Cs95e4CLHDfO0_T=gSDa2tDBS+eZTAJoHxWd_zWMAA@mail.gmail.com>

On Tue, Jun 9, 2026 at 11:42 AM Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
> Before even looking towards (some of) the W=2 warnings, please pay
> attention to the real issues the driver has.

Thanks for the feedback. I'm quite new to kernel development,
and I'm mainly just trying to get my footing on sending in
patches, currently starting with W=2. I'll have a look
at the TODOs or any known bugs to see if there are any minor
issues I can trust myself to help with.

Best regards,
Rhys

^ permalink raw reply

* Re: [PATCH] staging: rtl8723bs: core: avoid NULL pointer dereference in c2h_wk_callback
From: Andy Shevchenko @ 2026-06-09 18:05 UTC (permalink / raw)
  To: nika bakuradze
  Cc: Greg Kroah-Hartman, Khushal Chitturi, Archit Anant, Minu Jin,
	Kees Cook, Hans de Goede, linux-staging, linux-kernel, stable
In-Reply-To: <CAHyzTT3R-cOpJdE=hKPGSBSdC-BiY29y40DURvKjCN4V+w5EAg@mail.gmail.com>

On Tue, Jun 09, 2026 at 08:40:39PM +0400, nika bakuradze wrote:

First of all, do not top-post!

> You're right, kmalloc(16) effectively won't fail. This is my first
> kernel patch so I was being overcautious with the framing.
> 
> Should I resend v2 with the else continue form you suggested,
> or drop the patch entirely?

To some extent the patch makes sense (at least for you to train your skills in
Linux kernel processes, et cetera). I would go with the v2 that uses my approach.
Also drop Fixes tag, consider this as an improvement to make code robust.

> On Tue, Jun 9, 2026 at 11:15 AM Andy Shevchenko
> <andriy.shevchenko@intel.com> wrote:
> > On Mon, Jun 08, 2026 at 11:06:58PM +0400, Nikoloz Bakuradze wrote:
> > > c2h_wk_callback() allocates a 16-byte buffer with kmalloc(GFP_ATOMIC)
> > > when the c2h event needs to be read by the host. The existing guard
> > > only wraps the read step, so on allocation failure the loop body falls
> > > through with a NULL c2h_evt and dereferences it in rtw_hal_c2h_valid()
> > > (via c2h_evt_valid() which reads buf->id).
> > >
> > > Restructure the check into an early continue so the rest of the loop
> > > iteration cannot be reached with a NULL pointer.
> >
> >
> > Not sure if we need any Fixes tag. kmalloc(16) won't ever fail (otherwise
> > the system is already in the state when nothing can help).

...

> > >                       c2h_evt = kmalloc(16, GFP_ATOMIC);
> > > -                     if (c2h_evt) {
> > > -                             /* This C2H event is not read, read & clear now */
> > > -                             if (c2h_evt_read_88xx(adapter, c2h_evt) != _SUCCESS) {
> > > -                                     kfree(c2h_evt);
> > > -                                     continue;
> > > -                             }
> >
> > > +                     if (!c2h_evt)
> > > +                             continue;
> > > +                     /* This C2H event is not read, read & clear now */
> > > +                     if (c2h_evt_read_88xx(adapter, c2h_evt) != _SUCCESS) {
> > > +                             kfree(c2h_evt);
> > > +                             continue;
> >
> > It's too verbose way of saying
> >
> >                         } else
> >                                 continue;
> >
> > here.
> >
> > >                       }

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* Re: [PATCH] staging: rtl8723bs: core: avoid NULL pointer dereference in c2h_wk_callback
From: nika bakuradze @ 2026-06-09 16:40 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Greg Kroah-Hartman, Khushal Chitturi, Archit Anant, Minu Jin,
	Kees Cook, Hans de Goede, linux-staging, linux-kernel, stable
In-Reply-To: <aie9bqpiNDJ_IU0M@ashevche-desk.local>

You're right, kmalloc(16) effectively won't fail. This is my first
kernel patch so I was being overcautious with the framing.

Should I resend v2 with the else continue form you suggested,
or drop the patch entirely?

Regards,
Nikoloz Bakuradze

On Tue, Jun 9, 2026 at 11:15 AM Andy Shevchenko
<andriy.shevchenko@intel.com> wrote:
>
> On Mon, Jun 08, 2026 at 11:06:58PM +0400, Nikoloz Bakuradze wrote:
> > c2h_wk_callback() allocates a 16-byte buffer with kmalloc(GFP_ATOMIC)
> > when the c2h event needs to be read by the host. The existing guard
> > only wraps the read step, so on allocation failure the loop body falls
> > through with a NULL c2h_evt and dereferences it in rtw_hal_c2h_valid()
> > (via c2h_evt_valid() which reads buf->id).
> >
> > Restructure the check into an early continue so the rest of the loop
> > iteration cannot be reached with a NULL pointer.
>
>
> Not sure if we need any Fixes tag. kmalloc(16) won't ever fail (otherwise
> the system is already in the state when nothing can help).
>
> ...
>
> >                       c2h_evt = kmalloc(16, GFP_ATOMIC);
> > -                     if (c2h_evt) {
> > -                             /* This C2H event is not read, read & clear now */
> > -                             if (c2h_evt_read_88xx(adapter, c2h_evt) != _SUCCESS) {
> > -                                     kfree(c2h_evt);
> > -                                     continue;
> > -                             }
>
> > +                     if (!c2h_evt)
> > +                             continue;
> > +                     /* This C2H event is not read, read & clear now */
> > +                     if (c2h_evt_read_88xx(adapter, c2h_evt) != _SUCCESS) {
> > +                             kfree(c2h_evt);
> > +                             continue;
>
> It's too verbose way of saying
>
>                         } else
>                                 continue;
>
> here.
>
> >                       }
>
> --
> With Best Regards,
> Andy Shevchenko
>
>

^ permalink raw reply

* Re: [PATCH] staging: media: atomisp: fix variable shadowing warnings
From: Andy Shevchenko @ 2026-06-09 10:42 UTC (permalink / raw)
  To: Rhys Tumelty
  Cc: Hans de Goede, Mauro Carvalho Chehab, Greg Kroah-Hartman,
	Andy Shevchenko, Sakari Ailus, Dan Carpenter, linux-media,
	linux-staging, linux-kernel
In-Reply-To: <20260608151617.3393471-1-rhys@tumelty.co.uk>

On Mon, Jun 8, 2026 at 6:16 PM Rhys Tumelty <rhys@tumelty.co.uk> wrote:
>
> Fix local variable shadowing warnings, flagged by a W=2
> kernel build, due to -Werror=shadow.
>
> In atomisp_css_stop(), an inner loop 'i' index shadows
> an outer unsigned int i. Rename the inner loop index to 'k'
>
> In ia_css_stream_create(), the block-local 'effective_res'
> struct shadows an outer local declaration. Rename the
> block scoped instance in the loop over pipes to
> 'pipe_effective_res' to clearly show context.

Before even looking towards (some of) the W=2 warnings, please pay
attention to the real issues the driver has.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply

* Re: [PATCH] staging: rtl8723bs: core: avoid NULL pointer dereference in c2h_wk_callback
From: Andy Shevchenko @ 2026-06-09  7:14 UTC (permalink / raw)
  To: Nikoloz Bakuradze
  Cc: Greg Kroah-Hartman, Khushal Chitturi, Archit Anant, Minu Jin,
	Kees Cook, Hans de Goede, linux-staging, linux-kernel, stable
In-Reply-To: <20260608190700.85755-1-nbakuradze28@gmail.com>

On Mon, Jun 08, 2026 at 11:06:58PM +0400, Nikoloz Bakuradze wrote:
> c2h_wk_callback() allocates a 16-byte buffer with kmalloc(GFP_ATOMIC)
> when the c2h event needs to be read by the host. The existing guard
> only wraps the read step, so on allocation failure the loop body falls
> through with a NULL c2h_evt and dereferences it in rtw_hal_c2h_valid()
> (via c2h_evt_valid() which reads buf->id).
> 
> Restructure the check into an early continue so the rest of the loop
> iteration cannot be reached with a NULL pointer.


Not sure if we need any Fixes tag. kmalloc(16) won't ever fail (otherwise
the system is already in the state when nothing can help).

...

>  			c2h_evt = kmalloc(16, GFP_ATOMIC);
> -			if (c2h_evt) {
> -				/* This C2H event is not read, read & clear now */
> -				if (c2h_evt_read_88xx(adapter, c2h_evt) != _SUCCESS) {
> -					kfree(c2h_evt);
> -					continue;
> -				}

> +			if (!c2h_evt)
> +				continue;
> +			/* This C2H event is not read, read & clear now */
> +			if (c2h_evt_read_88xx(adapter, c2h_evt) != _SUCCESS) {
> +				kfree(c2h_evt);
> +				continue;

It's too verbose way of saying

			} else
				continue;

here.

>  			}

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* Re: [PATCH] staging: sm750fb: make g_fbmode array const
From: Ahmet Sezgin Duran @ 2026-06-09  6:12 UTC (permalink / raw)
  To: Brock Haftner, Sudip Mukherjee, Teddy Wang, Greg Kroah-Hartman,
	outreachy
  Cc: linux-fbdev, linux-staging, linux-kernel
In-Reply-To: <20260609011736.17401-1-brockhaftner@gmail.com>

On 6/9/26 4:17 AM, Brock Haftner wrote:
> The g_fbmode array is a static array of constant strings, but the pointer
> array itself is not marked as const. Fix the checkpatch.pl warning by
> adding the const modifier to the array declaration.
> 
> Signed-off-by: Brock Haftner <brockhaftner@gmail.com>
> ---
>   drivers/staging/sm750fb/sm750.c | 2 +-
>   1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/staging/sm750fb/sm750.c b/drivers/staging/sm750fb/sm750.c
> index 89c811e0806c..8f533f3b1b42 100644
> --- a/drivers/staging/sm750fb/sm750.c
> +++ b/drivers/staging/sm750fb/sm750.c
> @@ -21,7 +21,7 @@
>   static int g_hwcursor = 1;
>   static int g_noaccel __ro_after_init;
>   static int g_nomtrr __ro_after_init;
> -static const char *g_fbmode[] = {NULL, NULL};
> +static const char * const g_fbmode[] = {NULL, NULL};
>   static const char *g_def_fbmode = "1024x768-32@60";
>   static char *g_settings;
>   static int g_dualview __ro_after_init;

Did you compile this patch while enabling sm750fb driver in the config?

Regards,
Ahmet Sezgin Duran

^ permalink raw reply

* [PATCH] staging: sm750fb: make g_fbmode array const
From: Brock Haftner @ 2026-06-09  1:17 UTC (permalink / raw)
  To: Sudip Mukherjee, Teddy Wang, Greg Kroah-Hartman, outreachy
  Cc: linux-fbdev, linux-staging, linux-kernel, Brock Haftner

The g_fbmode array is a static array of constant strings, but the pointer
array itself is not marked as const. Fix the checkpatch.pl warning by
adding the const modifier to the array declaration.

Signed-off-by: Brock Haftner <brockhaftner@gmail.com>
---
 drivers/staging/sm750fb/sm750.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/staging/sm750fb/sm750.c b/drivers/staging/sm750fb/sm750.c
index 89c811e0806c..8f533f3b1b42 100644
--- a/drivers/staging/sm750fb/sm750.c
+++ b/drivers/staging/sm750fb/sm750.c
@@ -21,7 +21,7 @@
 static int g_hwcursor = 1;
 static int g_noaccel __ro_after_init;
 static int g_nomtrr __ro_after_init;
-static const char *g_fbmode[] = {NULL, NULL};
+static const char * const g_fbmode[] = {NULL, NULL};
 static const char *g_def_fbmode = "1024x768-32@60";
 static char *g_settings;
 static int g_dualview __ro_after_init;
-- 
2.54.0


^ permalink raw reply related

* Re: [linux-5.10.y 1/3] HID: core: Add printk_ratelimited variants to hid_warn() etc
From: Sasha Levin @ 2026-06-09  0:51 UTC (permalink / raw)
  To: lee, Jiri Kosina, Benjamin Tissoires, Viresh Kumar, Johan Hovold,
	Alex Elder, Greg Kroah-Hartman, linux-input, linux-kernel,
	greybus-dev, linux-staging
  Cc: Sasha Levin, stable, Vicki Pfau, Jiri Kosina
In-Reply-To: <20260608100236.2781796-1-lee@kernel.org>

> [linux-5.10.y 1/3] HID: core: Add printk_ratelimited variants to hid_warn() etc
> [linux-5.10.y 2/3] HID: pass the buffer size to hid_report_raw_event
> [linux-5.10.y 3/3] HID: core: Fix size_t specifier in hid_report_raw_event()

Whole series queued for 5.10, thanks.

--
Thanks,
Sasha

^ permalink raw reply

* [PATCH] staging: rtl8723bs: core: avoid NULL pointer dereference in c2h_wk_callback
From: Nikoloz Bakuradze @ 2026-06-08 19:06 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Khushal Chitturi, Archit Anant, Minu Jin,
	Andy Shevchenko, Kees Cook, Hans de Goede, linux-staging,
	linux-kernel
  Cc: Nikoloz Bakuradze, stable

c2h_wk_callback() allocates a 16-byte buffer with kmalloc(GFP_ATOMIC)
when the c2h event needs to be read by the host. The existing guard
only wraps the read step, so on allocation failure the loop body falls
through with a NULL c2h_evt and dereferences it in rtw_hal_c2h_valid()
(via c2h_evt_valid() which reads buf->id).

Restructure the check into an early continue so the rest of the loop
iteration cannot be reached with a NULL pointer.

Fixes: 554c0a3abf21 ("staging: Add rtl8723bs sdio wifi driver")
Cc: stable@vger.kernel.org
Signed-off-by: Nikoloz Bakuradze <nbakuradze28@gmail.com>
---
 drivers/staging/rtl8723bs/core/rtw_cmd.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/drivers/staging/rtl8723bs/core/rtw_cmd.c b/drivers/staging/rtl8723bs/core/rtw_cmd.c
index c1185c25ed369..874970116f920 100644
--- a/drivers/staging/rtl8723bs/core/rtw_cmd.c
+++ b/drivers/staging/rtl8723bs/core/rtw_cmd.c
@@ -1702,12 +1702,12 @@ static void c2h_wk_callback(struct work_struct *work)
 			c2h_evt_clear(adapter);
 		} else {
 			c2h_evt = kmalloc(16, GFP_ATOMIC);
-			if (c2h_evt) {
-				/* This C2H event is not read, read & clear now */
-				if (c2h_evt_read_88xx(adapter, c2h_evt) != _SUCCESS) {
-					kfree(c2h_evt);
-					continue;
-				}
+			if (!c2h_evt)
+				continue;
+			/* This C2H event is not read, read & clear now */
+			if (c2h_evt_read_88xx(adapter, c2h_evt) != _SUCCESS) {
+				kfree(c2h_evt);
+				continue;
 			}
 		}
 
-- 
2.54.0


^ permalink raw reply related

* [PATCH] staging: media: atomisp: fix variable shadowing warnings
From: Rhys Tumelty @ 2026-06-08 15:16 UTC (permalink / raw)
  To: Hans de Goede, Mauro Carvalho Chehab, Greg Kroah-Hartman
  Cc: Andy Shevchenko, Sakari Ailus, Dan Carpenter, linux-media,
	linux-staging, linux-kernel, Rhys Tumelty

Fix local variable shadowing warnings, flagged by a W=2
kernel build, due to -Werror=shadow.

In atomisp_css_stop(), an inner loop 'i' index shadows
an outer unsigned int i. Rename the inner loop index to 'k'

In ia_css_stream_create(), the block-local 'effective_res'
struct shadows an outer local declaration. Rename the
block scoped instance in the loop over pipes to
'pipe_effective_res' to clearly show context.

Signed-off-by: Rhys Tumelty <rhys@tumelty.co.uk>
---
 .../media/atomisp/pci/atomisp_compat_css20.c       |  6 +++---
 drivers/staging/media/atomisp/pci/sh_css.c         | 14 +++++++-------
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/drivers/staging/media/atomisp/pci/atomisp_compat_css20.c b/drivers/staging/media/atomisp/pci/atomisp_compat_css20.c
index be5f37f4a6fd..da945fddb5ea 100644
--- a/drivers/staging/media/atomisp/pci/atomisp_compat_css20.c
+++ b/drivers/staging/media/atomisp/pci/atomisp_compat_css20.c
@@ -1775,10 +1775,10 @@ void atomisp_css_stop(struct atomisp_sub_device *asd, bool in_reset)
 
 	if (!in_reset) {
 		struct atomisp_stream_env *stream_env;
-		int i, j;
+		int k, j;
 
-		for (i = 0; i < ATOMISP_INPUT_STREAM_NUM; i++) {
-			stream_env = &asd->stream_env[i];
+		for (k = 0; k < ATOMISP_INPUT_STREAM_NUM; k++) {
+			stream_env = &asd->stream_env[k];
 			for (j = 0; j < IA_CSS_PIPE_ID_NUM; j++) {
 				ia_css_pipe_config_defaults(
 				    &stream_env->pipe_configs[j]);
diff --git a/drivers/staging/media/atomisp/pci/sh_css.c b/drivers/staging/media/atomisp/pci/sh_css.c
index cd1be313c758..44c77179abb8 100644
--- a/drivers/staging/media/atomisp/pci/sh_css.c
+++ b/drivers/staging/media/atomisp/pci/sh_css.c
@@ -7971,22 +7971,22 @@ ia_css_stream_create(const struct ia_css_stream_config *stream_config,
 	}
 
 	for (i = 0; i < num_pipes; i++) {
-		struct ia_css_resolution effective_res;
+		struct ia_css_resolution pipe_effective_res;
 
 		curr_pipe = pipes[i];
 		/* set current stream */
 		curr_pipe->stream = curr_stream;
 		/* take over effective info */
 
-		effective_res = curr_pipe->config.input_effective_res;
-		if (effective_res.height == 0 || effective_res.width == 0) {
-			effective_res = curr_pipe->stream->config.input_config.effective_res;
+		pipe_effective_res = curr_pipe->config.input_effective_res;
+		if (pipe_effective_res.height == 0 || pipe_effective_res.width == 0) {
+			pipe_effective_res = curr_pipe->stream->config.input_config.effective_res;
 
-			curr_pipe->config.input_effective_res = effective_res;
+			curr_pipe->config.input_effective_res = pipe_effective_res;
 		}
 		IA_CSS_LOG("effective_res=%dx%d",
-			   effective_res.width,
-			   effective_res.height);
+			   pipe_effective_res.width,
+			   pipe_effective_res.height);
 	}
 
 	err = ia_css_stream_isp_parameters_init(curr_stream);
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH] media: atomisp: Fix resource leak in atomisp_pci_probe()
From: Dawei Feng @ 2026-06-08 14:05 UTC (permalink / raw)
  To: error27
  Cc: abdelrahmanfekry375, andy, dawei.feng, gregkh, hansg, jianhao.xu,
	linux-kernel, linux-media, linux-staging, mchehab, sakari.ailus,
	zilin
In-Reply-To: <aiaCoV1DQKBx4ph6@stanley.mountain>

Hi Dan,

On Mon, Jun 08, 2026 at 11:51:45 +0300, Dan Carpenter wrote:
> The code is buggy, but this isn't the right fix.
> 
> Here is generally the standard way to do error handling.
> https://staticthinking.wordpress.com/2022/04/28/free-the-last-thing-style/
> 
> 1. An allocation should clean up it's own partial allocations.  That
> should not be handled in the caller.  2.  Every allocation function should
> have a mirror cleanup function.
> 
> The atomisp_uninitialize_modules() function is just a dummy and was never
> actually implemented.  The correct thing is to implement it.

Thanks for the review and the link to the error handling guidelines. 

Thanks for the pointer. I'll update it in v2.

Thanks,
Dawei

^ permalink raw reply

* [linux-5.10.y 3/3] HID: core: Fix size_t specifier in hid_report_raw_event()
From: Lee Jones @ 2026-06-08 10:02 UTC (permalink / raw)
  To: lee, Jiri Kosina, Benjamin Tissoires, Viresh Kumar, Johan Hovold,
	Alex Elder, Greg Kroah-Hartman, linux-input, linux-kernel,
	greybus-dev, linux-staging
  Cc: stable, Nathan Chancellor, Miguel Ojeda, Linus Torvalds,
	Sasha Levin
In-Reply-To: <20260608100236.2781796-1-lee@kernel.org>

From: Nathan Chancellor <nathan@kernel.org>

[ Upstream commit 4d3a2a466b8d68d852a1f3bbf11204b718428dc4 ]

When building for 32-bit platforms, for which 'size_t' is
'unsigned int', there are warnings around using the incorrect format
specifier to print bsize in hid_report_raw_event():

  drivers/hid/hid-core.c:2054:29: error: format specifies type 'long' but the argument has type 'size_t' (aka 'unsigned int') [-Werror,-Wformat]
   2053 |                 hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %ld)\n",
        |                                                                                         ~~~
        |                                                                                         %zu
   2054 |                                      report->id, csize, bsize);
        |                                                         ^~~~~
  drivers/hid/hid-core.c:2076:29: error: format specifies type 'long' but the argument has type 'size_t' (aka 'unsigned int') [-Werror,-Wformat]
   2075 |                 hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %ld)\n",
        |                                                                                          ~~~
        |                                                                                          %zu
   2076 |                                      report->id, rsize, bsize);
        |                                                         ^~~~~

Use the proper 'size_t' format specifier, '%zu', to clear up the
warnings.

Cc: stable@vger.kernel.org
Fixes: 2c85c61d1332 ("HID: pass the buffer size to hid_report_raw_event")
Reported-by: Miguel Ojeda <ojeda@kernel.org>
Closes: https://lore.kernel.org/20260516020430.110135-1-ojeda@kernel.org/
Signed-off-by: Nathan Chancellor <nathan@kernel.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
(cherry picked from commit 3ab135238832446399614e7a4bb796d620717806)
Signed-off-by: Lee Jones <lee@kernel.org>
(cherry picked from commit 0f77a993b5426cca1b046c9ab4b2f8355a4d45dc)
Signed-off-by: Lee Jones <lee@kernel.org>
(cherry picked from commit 70333a8f866aad8cbd6956e2ec4ace159fa4243b)
Signed-off-by: Lee Jones <lee@kernel.org>
---
 drivers/hid/hid-core.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index c73f4ac16fdf..918c66d5bc93 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -1793,7 +1793,7 @@ int hid_report_raw_event(struct hid_device *hid, int type, u8 *data,
 		return 0;
 
 	if (unlikely(bsize < csize)) {
-		hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %ld)\n",
+		hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %zu)\n",
 				     report->id, csize, bsize);
 		return -EINVAL;
 	}
@@ -1815,7 +1815,7 @@ int hid_report_raw_event(struct hid_device *hid, int type, u8 *data,
 		rsize = max_buffer_size;
 
 	if (bsize < rsize) {
-		hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %ld)\n",
+		hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %zu)\n",
 				     report->id, rsize, bsize);
 		return -EINVAL;
 	}
-- 
2.54.0.1032.g2f8565e1d1-goog


^ permalink raw reply related

* [linux-5.10.y 2/3] HID: pass the buffer size to hid_report_raw_event
From: Lee Jones @ 2026-06-08 10:02 UTC (permalink / raw)
  To: lee, Jiri Kosina, Benjamin Tissoires, Viresh Kumar, Johan Hovold,
	Alex Elder, Greg Kroah-Hartman, linux-input, linux-kernel,
	greybus-dev, linux-staging
  Cc: stable, Benjamin Tissoires, Jiri Kosina, Sasha Levin
In-Reply-To: <20260608100236.2781796-1-lee@kernel.org>

From: Benjamin Tissoires <bentiss@kernel.org>

[ Upstream commit 2c85c61d1332e1e16f020d76951baf167dcb6f7a ]

commit 0a3fe972a7cb ("HID: core: Mitigate potential OOB by removing
bogus memset()") enforced the provided data to be at least the size of
the declared buffer in the report descriptor to prevent a buffer
overflow. However, we can try to be smarter by providing both the buffer
size and the data size, meaning that hid_report_raw_event() can make
better decision whether we should plaining reject the buffer (buffer
overflow attempt) or if we can safely memset it to 0 and pass it to the
rest of the stack.

Fixes: 0a3fe972a7cb ("HID: core: Mitigate potential OOB by removing bogus memset()")
Cc: stable@vger.kernel.org
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
Acked-by: Johan Hovold <johan@kernel.org>
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
Stable-dep-of: 206342541fc8 ("HID: core: introduce hid_safe_input_report()")
Signed-off-by: Sasha Levin <sashal@kernel.org>
(cherry picked from commit 509c2605065004fc4cd86ee50a9350d402785307)
[Lee: Backported to linux-6.12.y and beyond]
Signed-off-by: Lee Jones <lee@kernel.org>
(cherry picked from commit f9393998660f146970047bda31526aeb96190f28)
Signed-off-by: Lee Jones <lee@kernel.org>
---
 drivers/hid/hid-core.c           | 29 ++++++++++++++++++++++-------
 drivers/hid/hid-gfrm.c           |  4 ++--
 drivers/hid/hid-logitech-hidpp.c |  2 +-
 drivers/hid/hid-multitouch.c     |  2 +-
 drivers/hid/hid-primax.c         |  2 +-
 drivers/hid/hid-vivaldi.c        |  2 +-
 drivers/hid/wacom_sys.c          |  6 +++---
 drivers/staging/greybus/hid.c    |  2 +-
 include/linux/hid.h              |  4 ++--
 9 files changed, 34 insertions(+), 19 deletions(-)

diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index aa9ae6ccb28a..c73f4ac16fdf 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -1775,8 +1775,8 @@ int __hid_request(struct hid_device *hid, struct hid_report *report,
 }
 EXPORT_SYMBOL_GPL(__hid_request);
 
-int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, u32 size,
-		int interrupt)
+int hid_report_raw_event(struct hid_device *hid, int type, u8 *data,
+			 size_t bufsize, u32 size, int interrupt)
 {
 	struct hid_report_enum *report_enum = hid->report_enum + type;
 	struct hid_report *report;
@@ -1784,16 +1784,24 @@ int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, u32 size,
 	int max_buffer_size = HID_MAX_BUFFER_SIZE;
 	unsigned int a;
 	u32 rsize, csize = size;
+	size_t bsize = bufsize;
 	u8 *cdata = data;
 	int ret = 0;
 
 	report = hid_get_report(report_enum, data);
 	if (!report)
-		goto out;
+		return 0;
+
+	if (unlikely(bsize < csize)) {
+		hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %ld)\n",
+				     report->id, csize, bsize);
+		return -EINVAL;
+	}
 
 	if (report_enum->numbered) {
 		cdata++;
 		csize--;
+		bsize--;
 	}
 
 	rsize = hid_compute_report_size(report);
@@ -1806,9 +1814,15 @@ int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, u32 size,
 	else if (rsize > max_buffer_size)
 		rsize = max_buffer_size;
 
+	if (bsize < rsize) {
+		hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %ld)\n",
+				     report->id, rsize, bsize);
+		return -EINVAL;
+	}
+
 	if (csize < rsize) {
 		dbg_hid("report %d is too short, (%d < %d)\n", report->id,
-				csize, rsize);
+			csize, rsize);
 		memset(cdata + csize, 0, rsize - csize);
 	}
 
@@ -1817,7 +1831,7 @@ int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, u32 size,
 	if (hid->claimed & HID_CLAIMED_HIDRAW) {
 		ret = hidraw_report_event(hid, data, size);
 		if (ret)
-			goto out;
+			return ret;
 	}
 
 	if (hid->claimed != HID_CLAIMED_HIDRAW && report->maxfield) {
@@ -1830,7 +1844,7 @@ int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, u32 size,
 
 	if (hid->claimed & HID_CLAIMED_INPUT)
 		hidinput_report_event(hid, report);
-out:
+
 	return ret;
 }
 EXPORT_SYMBOL_GPL(hid_report_raw_event);
@@ -1851,6 +1865,7 @@ int hid_input_report(struct hid_device *hid, int type, u8 *data, u32 size, int i
 	struct hid_report_enum *report_enum;
 	struct hid_driver *hdrv;
 	struct hid_report *report;
+	size_t bufsize = size;
 	int ret = 0;
 
 	if (!hid)
@@ -1889,7 +1904,7 @@ int hid_input_report(struct hid_device *hid, int type, u8 *data, u32 size, int i
 			goto unlock;
 	}
 
-	ret = hid_report_raw_event(hid, type, data, size, interrupt);
+	ret = hid_report_raw_event(hid, type, data, bufsize, size, interrupt);
 
 unlock:
 	up(&hid->driver_input_lock);
diff --git a/drivers/hid/hid-gfrm.c b/drivers/hid/hid-gfrm.c
index 699186ff2349..d2a56bf92b41 100644
--- a/drivers/hid/hid-gfrm.c
+++ b/drivers/hid/hid-gfrm.c
@@ -66,7 +66,7 @@ static int gfrm_raw_event(struct hid_device *hdev, struct hid_report *report,
 	switch (data[1]) {
 	case GFRM100_SEARCH_KEY_DOWN:
 		ret = hid_report_raw_event(hdev, HID_INPUT_REPORT, search_key_dn,
-					   sizeof(search_key_dn), 1);
+					   sizeof(search_key_dn), sizeof(search_key_dn), 1);
 		break;
 
 	case GFRM100_SEARCH_KEY_AUDIO_DATA:
@@ -74,7 +74,7 @@ static int gfrm_raw_event(struct hid_device *hdev, struct hid_report *report,
 
 	case GFRM100_SEARCH_KEY_UP:
 		ret = hid_report_raw_event(hdev, HID_INPUT_REPORT, search_key_up,
-					   sizeof(search_key_up), 1);
+					   sizeof(search_key_up), sizeof(search_key_up), 1);
 		break;
 
 	default:
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 98562a0ed0c3..d31f2737b13d 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -3176,7 +3176,7 @@ static int hidpp10_consumer_keys_raw_event(struct hidpp_device *hidpp,
 	memcpy(&consumer_report[1], &data[3], 4);
 	/* We are called from atomic context */
 	hid_report_raw_event(hidpp->hid_dev, HID_INPUT_REPORT,
-			     consumer_report, 5, 1);
+			     consumer_report, sizeof(consumer_report), 5, 1);
 
 	return 1;
 }
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index 948bd59ab5d2..c3bcc23d7c7c 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -449,7 +449,7 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report)
 		}
 
 		ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, buf,
-					   size, 0);
+					   size, size, 0);
 		if (ret)
 			dev_warn(&hdev->dev, "failed to report feature\n");
 	}
diff --git a/drivers/hid/hid-primax.c b/drivers/hid/hid-primax.c
index 1e6413d07cae..16e2a811eda9 100644
--- a/drivers/hid/hid-primax.c
+++ b/drivers/hid/hid-primax.c
@@ -44,7 +44,7 @@ static int px_raw_event(struct hid_device *hid, struct hid_report *report,
 			data[0] |= (1 << (data[idx] - 0xE0));
 			data[idx] = 0;
 		}
-		hid_report_raw_event(hid, HID_INPUT_REPORT, data, size, 0);
+		hid_report_raw_event(hid, HID_INPUT_REPORT, data, size, size, 0);
 		return 1;
 
 	default:	/* unknown report */
diff --git a/drivers/hid/hid-vivaldi.c b/drivers/hid/hid-vivaldi.c
index d57ec1767037..fdfea1355ee7 100644
--- a/drivers/hid/hid-vivaldi.c
+++ b/drivers/hid/hid-vivaldi.c
@@ -126,7 +126,7 @@ static void vivaldi_feature_mapping(struct hid_device *hdev,
 	}
 
 	ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, report_data,
-				   report_len, 0);
+				   report_len, report_len, 0);
 	if (ret) {
 		dev_warn(&hdev->dev, "failed to report feature %d\n",
 			 field->report->id);
diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c
index abbfb53bb7dc..09b513812fff 100644
--- a/drivers/hid/wacom_sys.c
+++ b/drivers/hid/wacom_sys.c
@@ -79,7 +79,7 @@ static void wacom_wac_queue_flush(struct hid_device *hdev,
 		int err;
 
 		size = kfifo_out(fifo, buf, sizeof(buf));
-		err = hid_report_raw_event(hdev, HID_INPUT_REPORT, buf, size, false);
+		err = hid_report_raw_event(hdev, HID_INPUT_REPORT, buf, size, size, false);
 		if (err) {
 			hid_warn(hdev, "%s: unable to flush event due to error %d\n",
 				 __func__, err);
@@ -324,7 +324,7 @@ static void wacom_feature_mapping(struct hid_device *hdev,
 					       data, n, WAC_CMD_RETRIES);
 			if (ret == n && features->type == HID_GENERIC) {
 				ret = hid_report_raw_event(hdev,
-					HID_FEATURE_REPORT, data, n, 0);
+					HID_FEATURE_REPORT, data, n, n, 0);
 			} else if (ret == 2 && features->type != HID_GENERIC) {
 				features->touch_max = data[1];
 			} else {
@@ -385,7 +385,7 @@ static void wacom_feature_mapping(struct hid_device *hdev,
 					data, n, WAC_CMD_RETRIES);
 		if (ret == n) {
 			ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT,
-						   data, n, 0);
+						   data, n, n, 0);
 		} else {
 			hid_warn(hdev, "%s: could not retrieve sensor offsets\n",
 				 __func__);
diff --git a/drivers/staging/greybus/hid.c b/drivers/staging/greybus/hid.c
index ed706f39e87a..d68f60da0dd1 100644
--- a/drivers/staging/greybus/hid.c
+++ b/drivers/staging/greybus/hid.c
@@ -201,7 +201,7 @@ static void gb_hid_init_report(struct gb_hid *ghid, struct hid_report *report)
 	 * we just need to setup the input fields, so using
 	 * hid_report_raw_event is safe.
 	 */
-	hid_report_raw_event(ghid->hid, report->type, ghid->inbuf, size, 1);
+	hid_report_raw_event(ghid->hid, report->type, ghid->inbuf, ghid->bufsize, size, 1);
 }
 
 static void gb_hid_init_reports(struct gb_hid *ghid)
diff --git a/include/linux/hid.h b/include/linux/hid.h
index ab56fffb74a2..aaae2fecd4ae 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -1175,8 +1175,8 @@ static inline u32 hid_report_len(struct hid_report *report)
 	return DIV_ROUND_UP(report->size, 8) + (report->id > 0);
 }
 
-int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, u32 size,
-		int interrupt);
+int hid_report_raw_event(struct hid_device *hid, int type, u8 *data,
+			 size_t bufsize, u32 size, int interrupt);
 
 /* HID quirks API */
 unsigned long hid_lookup_quirk(const struct hid_device *hdev);
-- 
2.54.0.1032.g2f8565e1d1-goog


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox