From nobody Tue Sep 9 18:07:35 2025 Received: from mail-wr1-f41.google.com (mail-wr1-f41.google.com [209.85.221.41]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3E86A2E427E; Fri, 18 Jul 2025 15:25:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752852361; cv=none; b=tZbV9Xlg0mdxuQgxRdVM1LBqFtzvEf7ttMm9w0fVX5xXIh/iKoAzEAejmsBzGvpIjaXWPtxz5e2UTWZVjn8CqR+CVhJy1vo323jkidhwxh8+wGdMTI5MnHYkGwmEw3VrWf1T9P6Eko13W+fzfXnl0CQcL1BYmeErX/qHp9YPsu4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752852361; c=relaxed/simple; bh=iu13/4t6tLUjSnVyCYhYserDI5MIfmnWTuHSI9X9KaQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jDIIWwZWUGgfMOoBtmlvauc4s0WCcn1wH4Ujd07tFM22FtfVA0iJgQW/uOfEoSRC6vdbSR1hhndmWO5Wb37ox/5xIfVt0AbQ/2yV3GciCYaqc4BJ15n0ecTelodfOpG+oIrcVG84VPMgk5mY04LgUgcsOMBQgmK+6BdHAUi+5AM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=E3Pat2Dk; arc=none smtp.client-ip=209.85.221.41 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="E3Pat2Dk" Received: by mail-wr1-f41.google.com with SMTP id ffacd0b85a97d-3a4f379662cso1649527f8f.0; Fri, 18 Jul 2025 08:25:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1752852353; x=1753457153; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=wYtikEY04+PkW8km7wJCZSEs1xJ/iJKASvvsbCyJ5pc=; b=E3Pat2DkGbgU6HCHYdzVPpe9d3CuyV8cOV33w+34I04I5NGuBVwHgeMLpyoLY6OOkH JvFSoLR++qBphMe8SybTLY5pC6gnYEv1sl7hP7rYy0Ktqlpcr8nZOKOKKcDD3XyvpR+f Cg/2c9QrMw0fBTdmZ1plNcTDQtSJdBAaCAs/maXjkMC5+MH+t3RCvQTGAAzmEDKNVPDY cs6mvYr3/E1u0rTWJhwgMd7epp4FNiqk/1EPd24UbJ/yIxtfleWA6ahpKs/hywFJfbz+ nWFOQbNpBFOA2dVYh94K4q0YiH3syQhOAMIHtvEn8b+ErzmC5g/FFEMdopxPoAAgoYb8 wvGw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752852353; x=1753457153; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=wYtikEY04+PkW8km7wJCZSEs1xJ/iJKASvvsbCyJ5pc=; b=FICM+RtgT4ECMjqQOUxNN+EHN8UonoyI6YvadkkKUgFXZJpBpe5EMd5KbQKg9XrQTA DKqSJs+Bg6BNiVgM7g5EtgoVkZQo3q/158Lz0fmJ6L8DYp+NRO05lF+VJRmDUW9Qs9mK w1lf0DMr+RnbarJ0/5ylnHDjkhCKVVIyjWMXZKSNXYoR67pR2t2bnljg5zE9GHR8axH2 DeASzbbeiYSlm8k284/BnlhDWiClCYt4QoWX+q5Opoh8b1XYCyYNSRu40q70d+QI+LeK UJg0RUPdf04NSL9+oeFOALBr58PeuJ5EeQwS1dLYa6rwQyeqAcO4AvhYwA5i4ZdxciRe u01w== X-Forwarded-Encrypted: i=1; AJvYcCU26wV9r+IUKUukDuyvK4UDvCBPq9l5cj9PPw+mBx3EjRoBqfavzrXi6mEuLJ15eKctXJxci8OCZLJp@vger.kernel.org, AJvYcCVhVQakDPcy0XGSsDR9yqFaRm6ZIYS3sWBbTTccDXq82wdFH6kk09hbQWCxhBGd1Q9a70qfvK/DRhAreQ==@vger.kernel.org, AJvYcCWi7em34MoA9tH2CEL894JUOGwAxfsRXr9YZyHavYbddptBikWvSirstswXm2FflFGsHgt8Va8XL1+6oEHn@vger.kernel.org X-Gm-Message-State: AOJu0Yz+sj50uoG8Br1CnJWFA3VWSUpR+CilViYRTlUqHMoI497kJb41 UvhLZI0aqoT4+JmKRpeYSE9/IYoCCTslls70opu/h300HsJJGFX00mnZ X-Gm-Gg: ASbGnct8tyvuyV7d06sGruG2iNtNSNSmZlR2SBwrXXkWKg+jCC+6X2z9YMvb/W+9Wuu 6L284TfB1NKlIgRzoDeJj7uI+oN9Dw3z2P9T5JbkZ3MWeVgdu72IPg/RWuKUt+sI0El0hAh4em+ hbNVjeCKzfpU3sfElGCmvON2uTCvKGvSbKiTw6oviwK/PRp3/q4CZXKWWRIOMECUObR9/uiZhCY OzelyXRVTTiF1xsEwPGdS40XKxusWpgfiWN/a42t72cusyCMBdwT/h+xlWJ5npQJbCvlk0/jUGn rgRa/s24+r12Lc7/r4m2LCBqzh9MCB9iS1B/1/HwFUCz0w/DiCB5g20mLL2RZRcskQgir9upggS TdjHJdLEwP6t0j3+Ysy0T+MG8t9gpwlwJWd4= X-Google-Smtp-Source: AGHT+IFMYsxhxWGjVAHs9dIwpWJ7G0BCpUVZv5iJDjM6PaKxA22hbUFAdCYAY7vSWyMEDFAV6U1CEg== X-Received: by 2002:a5d:64e3:0:b0:3b4:9721:2b2d with SMTP id ffacd0b85a97d-3b60e4b8289mr10629619f8f.9.1752852351942; Fri, 18 Jul 2025 08:25:51 -0700 (PDT) Received: from demon-pc.localdomain ([86.121.79.159]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3b61ca4893fsm2195780f8f.52.2025.07.18.08.25.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 18 Jul 2025 08:25:51 -0700 (PDT) From: Cosmin Tanislav To: Cosmin Tanislav , Tomi Valkeinen , Mauro Carvalho Chehab , Rob Herring , =?UTF-8?q?Niklas=20S=C3=B6derlund?= , Julien Massot , Sakari Ailus , Laurent Pinchart , Greg Kroah-Hartman , Linus Walleij Cc: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-staging@lists.linux.dev, linux-gpio@vger.kernel.org, Cosmin Tanislav Subject: [PATCH v7 17/24] media: i2c: add Maxim GMSL2/3 deserializer framework Date: Fri, 18 Jul 2025 18:24:53 +0300 Message-ID: <20250718152500.2656391-18-demonsingur@gmail.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20250718152500.2656391-1-demonsingur@gmail.com> References: <20250718152500.2656391-1-demonsingur@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- drivers/media/i2c/maxim-serdes/Makefile | 2 +- drivers/media/i2c/maxim-serdes/max_des.c | 3181 ++++++++++++++++++++++ drivers/media/i2c/maxim-serdes/max_des.h | 153 ++ 3 files changed, 3335 insertions(+), 1 deletion(-) create mode 100644 drivers/media/i2c/maxim-serdes/max_des.c create mode 100644 drivers/media/i2c/maxim-serdes/max_des.h diff --git a/drivers/media/i2c/maxim-serdes/Makefile b/drivers/media/i2c/ma= xim-serdes/Makefile index 17511cb033690..b54326a5c81b5 100644 --- a/drivers/media/i2c/maxim-serdes/Makefile +++ b/drivers/media/i2c/maxim-serdes/Makefile @@ -1,3 +1,3 @@ # SPDX-License-Identifier: GPL-2.0 -max-serdes-objs :=3D max_serdes.o max_ser.o +max-serdes-objs :=3D max_serdes.o max_ser.o max_des.o obj-$(CONFIG_VIDEO_MAXIM_SERDES) +=3D max-serdes.o diff --git a/drivers/media/i2c/maxim-serdes/max_des.c b/drivers/media/i2c/m= axim-serdes/max_des.c new file mode 100644 index 0000000000000..cf815a1af4b34 --- /dev/null +++ b/drivers/media/i2c/maxim-serdes/max_des.c @@ -0,0 +1,3181 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Maxim GMSL2 Deserializer Driver + * + * Copyright (C) 2025 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "max_des.h" +#include "max_ser.h" +#include "max_serdes.h" + +#define MAX_DES_LINK_FREQUENCY_MIN 100000000ull +#define MAX_DES_LINK_FREQUENCY_DEFAULT 750000000ull +#define MAX_DES_LINK_FREQUENCY_MAX 1250000000ull + +#define MAX_DES_NUM_PHYS 4 +#define MAX_DES_NUM_LINKS 4 +#define MAX_DES_NUM_PIPES 8 + +struct max_des_priv { + struct max_des *des; + + struct device *dev; + struct i2c_client *client; + struct i2c_atr *atr; + struct i2c_mux_core *mux; + + struct media_pad *pads; + struct regulator **pocs; + struct max_serdes_source *sources; + u64 *streams_masks; + + struct notifier_block i2c_nb; + struct v4l2_subdev sd; + struct v4l2_async_notifier nf; + struct v4l2_ctrl_handler ctrl_handler; + + struct max_des_phy *unused_phy; +}; + +struct max_des_remap_context { + enum max_serdes_gmsl_mode mode; + /* Mark whether TPG is enabled */ + bool tpg; + /* Mark the PHYs to which each pipe is mapped. */ + unsigned long pipe_phy_masks[MAX_DES_NUM_PIPES]; + /* Mark the pipes in use. */ + bool pipe_in_use[MAX_DES_NUM_PIPES]; + /* Mark whether pipe has remapped VC ids. */ + bool vc_ids_remapped[MAX_DES_NUM_PIPES]; + /* Map between pipe VC ids and PHY VC ids. */ + unsigned int vc_ids_map[MAX_DES_NUM_PIPES][MAX_DES_NUM_PHYS][MAX_SERDES_V= C_ID_NUM]; + /* Mark whether a pipe VC id has been mapped to a PHY VC id. */ + unsigned long vc_ids_masks[MAX_DES_NUM_PIPES][MAX_DES_NUM_PHYS]; + /* Mark whether a PHY VC id has been mapped. */ + unsigned long dst_vc_ids_masks[MAX_DES_NUM_PHYS]; +}; + +struct max_des_mode_context { + bool phys_bpp8_shared_with_16[MAX_DES_NUM_PHYS]; + bool pipes_bpp8_shared_with_16[MAX_DES_NUM_PIPES]; + u32 phys_double_bpps[MAX_DES_NUM_PHYS]; + u32 pipes_double_bpps[MAX_DES_NUM_PIPES]; +}; + +struct max_des_route_hw { + struct max_serdes_source *source; + struct max_des_pipe *pipe; + struct max_des_phy *phy; + struct v4l2_mbus_frame_desc_entry entry; + bool is_tpg; +}; + +struct max_des_link_hw { + struct max_serdes_source *source; + struct max_des_link *link; + struct max_des_pipe *pipe; +}; + +static inline struct max_des_priv *sd_to_priv(struct v4l2_subdev *sd) +{ + return container_of(sd, struct max_des_priv, sd); +} + +static inline struct max_des_priv *nf_to_priv(struct v4l2_async_notifier *= nf) +{ + return container_of(nf, struct max_des_priv, nf); +} + +static inline struct max_des_priv *ctrl_to_priv(struct v4l2_ctrl_handler *= handler) +{ + return container_of(handler, struct max_des_priv, ctrl_handler); +} + +static inline bool max_des_pad_is_sink(struct max_des *des, u32 pad) +{ + return pad < des->ops->num_links; +} + +static inline bool max_des_pad_is_source(struct max_des *des, u32 pad) +{ + return pad >=3D des->ops->num_links && + pad < des->ops->num_links + des->ops->num_phys; +} + +static inline bool max_des_pad_is_tpg(struct max_des *des, u32 pad) +{ + return pad =3D=3D des->ops->num_links + des->ops->num_phys; +} + +static inline unsigned int max_des_link_to_pad(struct max_des *des, + struct max_des_link *link) +{ + return link->index; +} + +static inline unsigned int max_des_phy_to_pad(struct max_des *des, + struct max_des_phy *phy) +{ + return phy->index + des->ops->num_links; +} + +static inline unsigned int max_des_num_pads(struct max_des *des) +{ + return des->ops->num_links + des->ops->num_phys + + (des->ops->set_tpg ? 1 : 0); +} + +static struct max_des_phy *max_des_pad_to_phy(struct max_des *des, u32 pad) +{ + if (!max_des_pad_is_source(des, pad)) + return NULL; + + return &des->phys[pad - des->ops->num_links]; +} + +static struct max_des_link *max_des_pad_to_link(struct max_des *des, u32 p= ad) +{ + 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 =3D 0; i < des->ops->num_pipes; i++) { + struct max_des_pipe *pipe =3D &des->pipes[i]; + + if (pipe->link_id =3D=3D 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 *li= nk) +{ + 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 =3D 0; + unsigned int i; + + for (i =3D 0; i < des->ops->tpg_entries.num_entries; i++) { + entry =3D &des->ops->tpg_entries.entries[i]; + + if ((width !=3D 0 && width !=3D entry->width) || + (height !=3D 0 && height !=3D entry->height) || + (code !=3D 0 && code !=3D entry->code) || + (numerator !=3D 0 && numerator !=3D entry->interval.numerator) || + (denominator !=3D 0 && denominator !=3D entry->interval.denominator)) + continue; + + if (index =3D=3D target_index) + break; + + index++; + } + + if (i =3D=3D des->ops->tpg_entries.num_entries) + return NULL; + + return &des->ops->tpg_entries.entries[i]; +} + +static const struct max_serdes_tpg_entry * +max_des_find_state_tpg_entry(struct max_des *des, struct v4l2_subdev_state= *state, + unsigned int pad) +{ + struct v4l2_mbus_framefmt *fmt; + struct v4l2_fract *in; + + fmt =3D v4l2_subdev_state_get_format(state, pad, MAX_SERDES_TPG_STREAM); + if (!fmt) + return NULL; + + in =3D 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 =3D max_des_find_state_tpg_entry(des, state, pad); + if (!entry) + return -EINVAL; + + fd_entry->stream =3D MAX_SERDES_TPG_STREAM; + fd_entry->flags =3D V4L2_MBUS_FRAME_DESC_FL_LEN_MAX; + fd_entry->length =3D entry->width * entry->height * entry->bpp / 8; + fd_entry->pixelcode =3D entry->code; + fd_entry->bus.csi2.vc =3D 0; + fd_entry->bus.csi2.dt =3D 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 =3D priv->des; + + /* TPG injects its data into all pipes, but use pipe 0 for simplicity. */ + hw->pipe =3D &des->pipes[0]; + + hw->phy =3D 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 =3D 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 =3D 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 =3D max_des_pad_to_link(des, route->sink_pad); + if (!link) + return -ENOENT; + + hw->phy =3D max_des_pad_to_phy(des, route->source_pad); + if (!hw->phy) + return -ENOENT; + + hw->pipe =3D max_des_find_link_pipe(des, link); + if (!hw->pipe) + return -ENOENT; + + hw->source =3D max_des_get_link_source(priv, link); + if (!hw->source->sd) + return 0; + + ret =3D v4l2_subdev_call(hw->source->sd, pad, get_frame_desc, + hw->source->pad, &fd); + if (ret) + return ret; + + for (i =3D 0; i < fd.num_entries; i++) + if (fd.entry[i].stream =3D=3D route->sink_stream) + break; + + if (i =3D=3D fd.num_entries) + return -ENOENT; + + hw->entry =3D 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 =3D priv->des; + + memset(hw, 0, sizeof(*hw)); + + hw->link =3D link; + + hw->pipe =3D max_des_find_link_pipe(des, hw->link); + if (!hw->pipe) + return -ENOENT; + + hw->source =3D max_des_get_link_source(priv, hw->link); + + return 0; +} + +static int max_des_link_index_to_hw(struct max_des_priv *priv, unsigned in= t 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 =3D priv->des; + unsigned int mask =3D 0; + unsigned int i; + int ret; + + if (!des->ops->set_pipe_remap) + return 0; + + for (i =3D 0; i < num_remaps; i++) { + ret =3D des->ops->set_pipe_remap(des, pipe, i, &remaps[i]); + if (ret) + return ret; + + mask |=3D 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 =3D priv->des; + unsigned int mask =3D 0; + unsigned int i; + int ret; + + for (i =3D 0; i < num_vc_remaps; i++) { + ret =3D des->ops->set_pipe_vc_remap(des, pipe, i, &vc_remaps[i]); + if (ret) + return ret; + + mask |=3D 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 >=3D 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 =3D src_vc_id; + else + vc_id =3D ffz(context->dst_vc_ids_masks[phy_id]); + + if (vc_id !=3D src_vc_id) + context->vc_ids_remapped[pipe_id] =3D true; + + if (vc_id >=3D MAX_SERDES_VC_ID_NUM) + return -E2BIG; + + context->pipe_phy_masks[pipe_id] |=3D BIT(phy_id); + context->dst_vc_ids_masks[phy_id] |=3D BIT(vc_id); + + context->vc_ids_map[pipe_id][phy_id][src_vc_id] =3D vc_id; + context->vc_ids_masks[pipe_id][phy_id] |=3D 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 =3D 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 =3D max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + if (hw.is_tpg) + context->tpg =3D true; + + context->pipe_in_use[hw.pipe->index] =3D 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 =3D priv->des; + unsigned int i; + int ret; + + *modes =3D des->ops->modes; + + if (context->tpg) + *modes =3D BIT(des->ops->tpg_mode); + + for (i =3D 0; i < des->ops->num_links; i++) { + struct max_des_link_hw hw; + + ret =3D 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 &=3D 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 =3D priv->des; + unsigned int i; + int ret; + + context->mode =3D 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 =3D=3D BIT(MAX_SERDES_GMSL_PIXEL_MODE)) + return 0; + + for (i =3D 0; i < des->ops->num_links; i++) { + struct max_des_link_hw hw; + + ret =3D 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]) =3D=3D 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 =3D 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 =3D priv->des; + + /* Pixel mode deserializers always have the ability to remap VCs. */ + if (modes =3D=3D 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 =3D max_des_populate_remap_usage(priv, context, state); + if (ret) + return ret; + + ret =3D 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 =3D max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + keep_vc =3D max_des_should_keep_vc(priv, &hw, modes); + + ret =3D 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] =3D { 0 }; + u32 undoubled_bpps_phys[MAX_DES_NUM_PHYS] =3D { 0 }; + u32 bpps_pipes[MAX_DES_NUM_PIPES] =3D { 0 }; + struct max_des *des =3D priv->des; + struct v4l2_subdev_route *route; + unsigned int i; + int ret; + + if (mode !=3D 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 =3D max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + ret =3D max_serdes_get_fd_bpp(&hw.entry, &bpp); + if (ret) + return ret; + + bpps_pipes[hw.pipe->index] |=3D 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 =3D max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + ret =3D max_serdes_get_fd_bpp(&hw.entry, &bpp); + if (ret) + return ret; + + sink_bpps =3D bpps_pipes[hw.pipe->index]; + + ret =3D max_serdes_process_bpps(priv->dev, sink_bpps, ~0U, &doubled_bpp); + if (ret) + return ret; + + min_bpp =3D __ffs(sink_bpps); + max_bpp =3D __fls(sink_bpps); + pipe_id =3D hw.pipe->index; + phy_id =3D hw.phy->index; + + if (bpp =3D=3D doubled_bpp) { + context->phys_double_bpps[phy_id] |=3D BIT(bpp); + context->pipes_double_bpps[pipe_id] |=3D BIT(bpp); + } else { + undoubled_bpps_phys[phy_id] |=3D BIT(bpp); + } + + if (min_bpp =3D=3D 8 && max_bpp > 8) { + context->phys_bpp8_shared_with_16[phy_id] =3D true; + context->pipes_bpp8_shared_with_16[pipe_id] =3D true; + } else if (min_bpp =3D=3D 8 && max_bpp =3D=3D 8) { + bpp8_not_shared_with_16_phys[phy_id] =3D true; + } + } + + for (i =3D 0; i < des->ops->num_phys; i++) { + if (context->phys_bpp8_shared_with_16[i] && bpp8_not_shared_with_16_phys= [i]) { + dev_err(priv->dev, + "Cannot stream 8bpp coming from pipes padded to 16bpp " + "and pipes not padded to 16bpp on the same PHY\n"); + return -EINVAL; + } + } + + for (i =3D 0; i < des->ops->num_phys; i++) + context->phys_double_bpps[i] &=3D ~undoubled_bpps_phys[i]; + + for_each_active_route(&state->routing, route) { + struct max_des_route_hw hw; + + ret =3D max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + context->pipes_double_bpps[hw.pipe->index] &=3D + 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 =3D 0; i < *num_vc_remaps; i++) { + vc_remap =3D &vc_remaps[i]; + + if (vc_remap->src =3D=3D src_vc_id && vc_remap->dst =3D=3D dst_vc_id) + return 0; + } + + if (*num_vc_remaps =3D=3D MAX_SERDES_VC_ID_NUM) + return -E2BIG; + + vc_remaps[*num_vc_remaps].src =3D src_vc_id; + vc_remaps[*num_vc_remaps].dst =3D 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 =3D priv->des; + struct v4l2_subdev_route *route; + int ret; + + *num_vc_remaps =3D 0; + + if (context->mode !=3D 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 =3D max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + if (!with_tpg && hw.is_tpg) + continue; + + if (hw.pipe !=3D pipe) + continue; + + src_vc_id =3D hw.entry.bus.csi2.vc; + + ret =3D max_des_get_src_dst_vc_id(context, pipe->index, hw.phy->index, + src_vc_id, &dst_vc_id); + if (ret) + return ret; + + ret =3D 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 =3D context->pipes_double_bpps[pipe->index]; + + if ((double_bpps & BIT(8)) && + !context->pipes_bpp8_shared_with_16[pipe->index]) { + mode->dbl8 =3D true; + mode->dbl8mode =3D 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 =3D context->phys_bpp8_shared_with_16[phy->= index]; + u32 double_bpps =3D context->phys_double_bpps[phy->index]; + + if (BIT(8) & double_bpps) { + if (bpp8_pipe_shared_with_16) + mode->alt2_mem_map8 =3D true; + else + mode->alt_mem_map8 =3D true; + } + + if (BIT(10) & double_bpps) + mode->alt_mem_map10 =3D true; + + if (BIT(12) & double_bpps) + mode->alt_mem_map12 =3D true; +} + +static int max_des_set_modes(struct max_des_priv *priv, + struct max_des_mode_context *context) +{ + struct max_des *des =3D priv->des; + unsigned int i; + int ret; + + for (i =3D 0; i < des->ops->num_phys; i++) { + struct max_des_phy *phy =3D &des->phys[i]; + struct max_des_phy_mode mode =3D { 0 }; + + max_des_get_phy_mode(context, phy, &mode); + + if (phy->mode.alt_mem_map8 =3D=3D mode.alt_mem_map8 && + phy->mode.alt_mem_map10 =3D=3D mode.alt_mem_map10 && + phy->mode.alt_mem_map12 =3D=3D mode.alt_mem_map12 && + phy->mode.alt2_mem_map8 =3D=3D mode.alt2_mem_map8) + continue; + + if (des->ops->set_phy_mode) { + ret =3D des->ops->set_phy_mode(des, phy, &mode); + if (ret) + return ret; + } + + phy->mode =3D mode; + } + + for (i =3D 0; i < des->ops->num_pipes; i++) { + struct max_des_pipe *pipe =3D &des->pipes[i]; + struct max_des_pipe_mode mode =3D { 0 }; + + max_des_get_pipe_mode(context, pipe, &mode); + + if (pipe->mode.dbl8 =3D=3D mode.dbl8 && + pipe->mode.dbl10 =3D=3D mode.dbl10 && + pipe->mode.dbl12 =3D=3D mode.dbl12 && + pipe->mode.dbl8mode =3D=3D mode.dbl8mode && + pipe->mode.dbl10mode =3D=3D mode.dbl10mode) + continue; + + if (des->ops->set_pipe_mode) { + ret =3D des->ops->set_pipe_mode(des, pipe, &mode); + if (ret) + return ret; + } + + pipe->mode =3D mode; + } + + for (i =3D 0; i < des->ops->num_links; i++) { + struct max_des_link_hw hw; + u32 pipe_double_bpps =3D 0; + + ret =3D 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 =3D context->pipes_double_bpps[hw.pipe->index]; + + ret =3D 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 =3D priv->des; + unsigned int i; + int ret; + + if (des->ops->set_pipe_tunnel_enable) { + for (i =3D 0; i < des->ops->num_pipes; i++) { + struct max_des_pipe *pipe =3D &des->pipes[i]; + bool tunnel_mode =3D context->mode =3D=3D MAX_SERDES_GMSL_TUNNEL_MODE; + + ret =3D des->ops->set_pipe_tunnel_enable(des, pipe, tunnel_mode); + if (ret) + return ret; + } + } + + for (i =3D 0; i < des->ops->num_links; i++) { + struct max_des_link_hw hw; + + ret =3D 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 =3D max_ser_set_mode(hw.source->sd, context->mode); + if (ret) + return ret; + } + + des->mode =3D 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 =3D priv->des; + unsigned int i; + int ret; + + if (des->ops->set_pipe_vc_remap) + return 0; + + for (i =3D 0; i < des->ops->num_links; i++) { + struct max_serdes_vc_remap vc_remaps[MAX_SERDES_VC_ID_NUM]; + struct max_des_link_hw hw; + unsigned int num_vc_remaps; + + ret =3D 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 =3D max_des_get_pipe_vc_remaps(priv, context, hw.pipe, + vc_remaps, &num_vc_remaps, + state, streams_masks, false); + if (ret) + return ret; + + ret =3D 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] =3D { 0 }; + struct max_des *des =3D priv->des; + unsigned int i; + int ret; + + for (i =3D 0; i < des->ops->num_links; i++) { + struct max_des_link_hw hw; + unsigned int stream_id; + + ret =3D 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 =3D hw.pipe->stream_id; + + ret =3D max_ser_set_stream_id(hw.source->sd, stream_id); + if (ret =3D=3D -EOPNOTSUPP) { + /* + * Serializer does not support setting the stream id, + * retrieve its hardcoded stream id. + */ + ret =3D max_ser_get_stream_id(hw.source->sd, &stream_id); + } + + if (ret) + return ret; + + if (stream_id_usage[stream_id] && des->ops->needs_unique_stream_id) { + dev_err(priv->dev, "Duplicate stream id %u\n", stream_id); + return -EINVAL; + } + + ret =3D des->ops->set_pipe_stream_id(des, hw.pipe, stream_id); + if (ret) + return ret; + + stream_id_usage[stream_id] =3D true; + hw.pipe->stream_id =3D 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 =3D priv->des; + unsigned int i; + int ret; + + if (!des->ops->set_pipe_phy && !des->ops->set_pipe_tunnel_phy) + return 0; + + for (i =3D 0; i < des->ops->num_pipes; i++) { + struct max_des_pipe *pipe =3D &des->pipes[i]; + struct max_des_phy *phy; + unsigned int phy_id; + + phy_id =3D find_first_bit(&context->pipe_phy_masks[pipe->index], + des->ops->num_phys); + + if (priv->unused_phy && + (context->mode !=3D MAX_SERDES_GMSL_TUNNEL_MODE || + phy_id =3D=3D des->ops->num_phys)) + phy_id =3D priv->unused_phy->index; + + if (phy_id !=3D des->ops->num_phys) { + phy =3D &des->phys[phy_id]; + + if (context->mode =3D=3D MAX_SERDES_GMSL_PIXEL_MODE && + des->ops->set_pipe_phy) + ret =3D des->ops->set_pipe_phy(des, pipe, phy); + else if (context->mode =3D=3D MAX_SERDES_GMSL_TUNNEL_MODE && + des->ops->set_pipe_tunnel_phy) + ret =3D des->ops->set_pipe_tunnel_phy(des, pipe, phy); + else + ret =3D 0; + + if (ret) + return ret; + } + + pipe->phy_id =3D phy_id; + } + + return 0; +} + +static int max_des_add_remap(struct max_des *des, struct max_des_remap *re= maps, + 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 =3D 0; i < *num_remaps; i++) { + remap =3D &remaps[i]; + + if (remap->from_dt =3D=3D dt && remap->to_dt =3D=3D dt && + remap->from_vc =3D=3D src_vc_id && remap->to_vc =3D=3D dst_vc_id && + remap->phy =3D=3D phy_id) + return 0; + } + + if (*num_remaps =3D=3D des->ops->num_remaps_per_pipe) + return -E2BIG; + + remap =3D &remaps[*num_remaps]; + remap->from_dt =3D dt; + remap->from_vc =3D src_vc_id; + remap->to_dt =3D dt; + remap->to_vc =3D dst_vc_id; + remap->phy =3D phy_id; + + (*num_remaps)++; + + return 0; +} + +static int max_des_add_remaps(struct max_des *des, struct max_des_remap *r= emaps, + unsigned int *num_remaps, unsigned int phy_id, + unsigned int src_vc_id, unsigned int dst_vc_id, + unsigned int dt) +{ + int ret; + + ret =3D max_des_add_remap(des, remaps, num_remaps, phy_id, + src_vc_id, dst_vc_id, dt); + if (ret) + return ret; + + ret =3D 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 =3D 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 =3D { 0 }; + struct max_des *des =3D priv->des; + struct v4l2_subdev_route *route; + bool is_tpg_pipe =3D true; + int ret; + + *num_remaps =3D 0; + + if (context->mode !=3D 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 =3D max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + if (hw.is_tpg && hw.pipe !=3D pipe) { + is_tpg_pipe =3D false; + tpg_entry =3D hw.entry; + } + + if (hw.pipe !=3D pipe) + continue; + + src_vc_id =3D hw.entry.bus.csi2.vc; + + ret =3D max_des_get_src_dst_vc_id(context, pipe->index, hw.phy->index, + src_vc_id, &dst_vc_id); + if (ret) + return ret; + + ret =3D 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 =3D 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 =3D 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 =3D devm_kcalloc(priv->dev, MAX_SERDES_VC_ID_NUM, + sizeof(*vc_remaps), GFP_KERNEL); + if (!vc_remaps) + return -ENOMEM; + + ret =3D max_des_get_pipe_vc_remaps(priv, context, pipe, vc_remaps, &num_v= c_remaps, + state, streams_masks, true); + if (ret) + goto err_free_new_vc_remaps; + + ret =3D max_des_set_pipe_vc_remaps(priv, pipe, vc_remaps, num_vc_remaps); + if (ret) + goto err_free_new_vc_remaps; + + if (pipe->num_vc_remaps) + devm_kfree(priv->dev, pipe->vc_remaps); + + pipe->vc_remaps =3D vc_remaps; + pipe->num_vc_remaps =3D 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 =3D priv->des; + struct max_des_remap *remaps; + unsigned int num_remaps; + int ret; + + if (!des->ops->set_pipe_remap) + return 0; + + remaps =3D devm_kcalloc(priv->dev, des->ops->num_remaps_per_pipe, + sizeof(*remaps), GFP_KERNEL); + if (!remaps) + return -ENOMEM; + + ret =3D max_des_get_pipe_remaps(priv, context, pipe, remaps, &num_remaps, + state, streams_masks); + if (ret) + goto err_free_new_remaps; + + ret =3D 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 =3D remaps; + pipe->num_remaps =3D 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 =3D priv->des; + struct v4l2_subdev_route *route; + bool enable =3D 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 =3D max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + if (hw.pipe !=3D pipe) + continue; + + enable =3D true; + break; + } + + if (enable =3D=3D pipe->enabled) + return 0; + + ret =3D des->ops->set_pipe_enable(des, pipe, enable); + if (ret) + return ret; + + pipe->enabled =3D 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 =3D max_des_update_pipe_remaps(priv, context, pipe, + state, streams_masks); + if (ret) + return ret; + + ret =3D max_des_update_pipe_vc_remaps(priv, context, pipe, state, + streams_masks); + if (ret) + goto err_revert_update_pipe_remaps; + + ret =3D 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 =3D priv->des; + u8 addrs[] =3D { power_up_addr, new_addr }; + u8 current_addr; + int ret; + + ret =3D des->ops->select_links(des, BIT(link->index)); + if (ret) + return ret; + + ret =3D max_ser_wait_for_multiple(adapter, addrs, ARRAY_SIZE(addrs), + ¤t_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 =3D max_ser_reset(adapter, current_addr); + if (ret) { + dev_err(priv->dev, "Failed to reset serializer: %d\n", ret); + return ret; + } + + ret =3D 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 =3D 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 =3D max_ser_wait(adapter, new_addr); + if (ret) { + dev_err(priv->dev, + "Failed to wait for serializer at 0x%02x: %d\n", + new_addr, ret); + return ret; + } + + if (des->ops->fix_tx_ids) { + ret =3D 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 =3D priv->des; + unsigned int i; + int ret; + + if (des->ops->init) { + ret =3D des->ops->init(des); + if (ret) + return ret; + } + + if (des->ops->set_enable) { + ret =3D des->ops->set_enable(des, false); + if (ret) + return ret; + } + + for (i =3D 0; i < des->ops->num_phys; i++) { + struct max_des_phy *phy =3D &des->phys[i]; + + if (phy->enabled) { + ret =3D des->ops->init_phy(des, phy); + if (ret) + return ret; + } + + ret =3D des->ops->set_phy_enable(des, phy, phy->enabled); + if (ret) + return ret; + } + + for (i =3D 0; i < des->ops->num_pipes; i++) { + struct max_des_pipe *pipe =3D &des->pipes[i]; + struct max_des_link *link =3D &des->links[pipe->link_id]; + + ret =3D des->ops->set_pipe_enable(des, pipe, false); + if (ret) + return ret; + + if (des->ops->set_pipe_tunnel_enable) { + ret =3D des->ops->set_pipe_tunnel_enable(des, pipe, false); + if (ret) + return ret; + } + + if (des->ops->set_pipe_stream_id) { + ret =3D des->ops->set_pipe_stream_id(des, pipe, pipe->stream_id); + if (ret) + return ret; + } + + if (des->ops->set_pipe_link) { + ret =3D des->ops->set_pipe_link(des, pipe, link); + if (ret) + return ret; + } + + ret =3D 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 =3D 0; i < des->ops->num_links; i++) { + struct max_des_link *link =3D &des->links[i]; + + if (!link->enabled) + continue; + + ret =3D 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 =3D MAX_SERDES_GMSL_MIN; + *max =3D MAX_SERDES_GMSL_MAX; + + if (!des->ops->needs_single_link_version) + return; + + for (i =3D 0; i < des->ops->num_links; i++) { + struct max_des_link *link =3D &des->links[i]; + + if (!link->enabled) + continue; + + if (!link->ser_xlate.en) + continue; + + *min =3D *max =3D 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 =3D priv->des; + struct max_des_link *link =3D &des->links[chan_id]; + int i, min, max; + int ret =3D 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 =3D max; i >=3D min; i--) { + if (!(des->ops->versions & BIT(i))) + continue; + + if (des->ops->set_link_version) { + ret =3D des->ops->set_link_version(des, link, i); + if (ret) + return ret; + } + + ret =3D 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 =3D i; + link->ser_xlate.src =3D alias; + link->ser_xlate.dst =3D addr; + link->ser_xlate.en =3D 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 =3D 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 =3D { + .attach_addr =3D max_des_ser_atr_attach_addr, + .detach_addr =3D max_des_ser_atr_detach_addr, +}; + +static void max_des_i2c_atr_deinit(struct max_des_priv *priv) +{ + struct max_des *des =3D priv->des; + unsigned int i; + + for (i =3D 0; i < des->ops->num_links; i++) { + struct max_des_link *link =3D &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 =3D priv->des; + unsigned int mask =3D 0; + unsigned int i; + int ret; + + if (!i2c_check_functionality(priv->client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) + return -ENODEV; + + priv->atr =3D i2c_atr_new(priv->client->adapter, priv->dev, + &max_des_i2c_atr_ops, des->ops->num_links, + I2C_ATR_F_STATIC | I2C_ATR_F_PASSTHROUGH); + if (IS_ERR(priv->atr)) + return PTR_ERR(priv->atr); + + i2c_atr_set_driver_data(priv->atr, priv); + + for (i =3D 0; i < des->ops->num_links; i++) { + struct max_des_link *link =3D &des->links[i]; + struct i2c_atr_adap_desc desc =3D { + .chan_id =3D i, + }; + + if (!link->enabled) + continue; + + ret =3D i2c_atr_add_adapter(priv->atr, &desc); + if (ret) + goto err_add_adapters; + } + + for (i =3D 0; i < des->ops->num_links; i++) { + struct max_des_link *link =3D &des->links[i]; + + if (!link->enabled) + continue; + + mask |=3D BIT(link->index); + } + + return des->ops->select_links(des, mask); + +err_add_adapters: + max_des_i2c_atr_deinit(priv); + + return ret; +} + +static void max_des_i2c_mux_deinit(struct max_des_priv *priv) +{ + i2c_mux_del_adapters(priv->mux); + bus_unregister_notifier(&i2c_bus_type, &priv->i2c_nb); +} + +static int max_des_i2c_mux_bus_notifier_call(struct notifier_block *nb, + unsigned long event, void *device) +{ + struct max_des_priv *priv =3D container_of(nb, struct max_des_priv, i2c_n= b); + struct device *dev =3D device; + struct i2c_client *client; + u32 chan_id; + + /* + * Ideally, we would want to negotiate the GMSL version on + * BUS_NOTIFY_ADD_DEVICE, but the adapters list is only populated with + * the new adapter after BUS_NOTIFY_ADD_DEVICE is issued. + */ + if (event !=3D BUS_NOTIFY_BIND_DRIVER) + return NOTIFY_DONE; + + client =3D i2c_verify_client(dev); + if (!client) + return NOTIFY_DONE; + + for (chan_id =3D 0; chan_id < priv->mux->max_adapters; ++chan_id) { + if (client->adapter =3D=3D priv->mux->adapter[chan_id]) + break; + } + + if (chan_id =3D=3D priv->mux->max_adapters) + return NOTIFY_DONE; + + max_des_ser_attach_addr(priv, chan_id, client->addr, client->addr); + + return NOTIFY_DONE; +} + +static int max_des_i2c_mux_select(struct i2c_mux_core *muxc, u32 chan) +{ + struct max_des_priv *priv =3D i2c_mux_priv(muxc); + struct max_des *des =3D 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 =3D priv->des; + u32 flags =3D I2C_MUX_LOCKED; + unsigned int i; + int ret; + + if (des->ops->num_links =3D=3D 1) + flags |=3D I2C_MUX_GATE; + + priv->mux =3D i2c_mux_alloc(priv->client->adapter, priv->dev, + des->ops->num_links, 0, flags, + max_des_i2c_mux_select, NULL); + if (!priv->mux) + return -ENOMEM; + + priv->mux->priv =3D priv; + + priv->i2c_nb.notifier_call =3D max_des_i2c_mux_bus_notifier_call; + ret =3D bus_register_notifier(&i2c_bus_type, &priv->i2c_nb); + if (ret) + return ret; + + for (i =3D 0; i < des->ops->num_links; i++) { + struct max_des_link *link =3D &des->links[i]; + + if (!link->enabled) + continue; + + ret =3D i2c_mux_add_adapter(priv->mux, 0, i); + if (ret) + goto err_add_adapters; + } + + return 0; + +err_add_adapters: + max_des_i2c_mux_deinit(priv); + + return ret; +} + +static void max_des_i2c_adapter_deinit(struct max_des_priv *priv) +{ + struct max_des *des =3D priv->des; + + if (des->ops->use_atr) + return max_des_i2c_atr_deinit(priv); + else + return max_des_i2c_mux_deinit(priv); +} + +static int max_des_i2c_adapter_init(struct max_des_priv *priv) +{ + struct max_des *des =3D priv->des; + + if (des->ops->use_atr) + return max_des_i2c_atr_init(priv); + else + return max_des_i2c_mux_init(priv); + + return 0; +} + +static int max_des_set_tpg_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *fmt =3D &format->format; + struct max_des_priv *priv =3D v4l2_get_subdevdata(sd); + struct max_des *des =3D priv->des; + const struct max_serdes_tpg_entry *entry; + struct v4l2_fract *in; + + if (format->stream !=3D MAX_SERDES_TPG_STREAM) + return -EINVAL; + + entry =3D max_des_find_tpg_entry(des, 0, fmt->width, fmt->height, + fmt->code, 0, 0); + if (!entry) + return -EINVAL; + + in =3D v4l2_subdev_state_get_interval(state, format->pad, format->stream); + if (!in) + return -EINVAL; + + in->numerator =3D entry->interval.numerator; + in->denominator =3D 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 =3D v4l2_get_subdevdata(sd); + struct max_des *des =3D priv->des; + struct v4l2_mbus_framefmt *fmt; + int ret; + + if (format->which =3D=3D 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 =3D max_des_set_tpg_fmt(sd, state, format); + if (ret) + return ret; + } + + fmt =3D v4l2_subdev_state_get_format(state, format->pad, format->stream); + if (!fmt) + return -EINVAL; + + *fmt =3D format->format; + + fmt =3D v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt =3D 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 =3D v4l2_get_subdevdata(sd); + struct max_des *des =3D priv->des; + const struct max_serdes_tpg_entry *entry; + + if (!max_des_pad_is_tpg(des, fie->pad) || + fie->stream !=3D MAX_SERDES_TPG_STREAM) + return -EINVAL; + + entry =3D 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 =3D entry->interval.numerator; + fie->interval.denominator =3D entry->interval.denominator; + + return 0; +} + +static int max_des_set_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_interval *fi) +{ + struct max_des_priv *priv =3D v4l2_get_subdevdata(sd); + struct max_des *des =3D 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 !=3D MAX_SERDES_TPG_STREAM) + return -EINVAL; + + if (fi->which =3D=3D V4L2_SUBDEV_FORMAT_ACTIVE && des->active) + return -EBUSY; + + fmt =3D v4l2_subdev_state_get_format(state, fi->pad, fi->stream); + if (!fmt) + return -EINVAL; + + entry =3D max_des_find_tpg_entry(des, 0, fmt->width, fmt->height, + fmt->code, fi->interval.denominator, + fi->interval.numerator); + if (!entry) + return -EINVAL; + + in =3D v4l2_subdev_state_get_interval(state, fi->pad, fi->stream); + if (!in) + return -EINVAL; + + in->numerator =3D fi->interval.numerator; + in->denominator =3D fi->interval.denominator; + + return 0; +} + +static int max_des_log_status(struct v4l2_subdev *sd) +{ + struct max_des_priv *priv =3D v4l2_get_subdevdata(sd); + struct max_des *des =3D 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 =3D 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 =3D des->ops->log_status(des); + if (ret) + return ret; + } + v4l2_info(sd, "\n"); + + for (i =3D 0; i < des->ops->num_links; i++) { + struct max_des_link *link =3D &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->versi= on)); + 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 =3D 0; i < des->ops->num_pipes; i++) { + struct max_des_pipe *pipe =3D &des->pipes[i]; + + v4l2_info(sd, "pipe: %u\n", pipe->index); + v4l2_info(sd, "\tenabled: %u\n", pipe->enabled); + if (pipe->phy_id =3D=3D des->ops->num_phys || + (priv->unused_phy && pipe->phy_id =3D=3D 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 =3D 0; j < pipe->num_remaps; j++) { + struct max_des_remap *remap =3D &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 =3D 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 =3D des->ops->log_pipe_status(des, pipe); + if (ret) + return ret; + } + v4l2_info(sd, "\n"); + } + + for (i =3D 0; i < des->ops->num_phys; i++) { + struct max_des_phy *phy =3D &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 =3D 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 =3D ctrl_to_priv(ctrl->handler); + struct max_des *des =3D priv->des; + + switch (ctrl->id) { + case V4L2_CID_TEST_PATTERN: + des->tpg_pattern =3D 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 =3D { 0 }; + struct max_des_priv *priv =3D sd_to_priv(sd); + struct v4l2_subdev_route *route; + int ret; + + fd->type =3D V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + + ret =3D 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 !=3D route->source_pad) + continue; + + ret =3D max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + ret =3D max_des_get_src_dst_vc_id(&context, hw.pipe->index, hw.phy->inde= x, + hw.entry.bus.csi2.vc, &dst_vc_id); + if (ret) + return ret; + + hw.entry.bus.csi2.vc =3D dst_vc_id; + hw.entry.stream =3D route->source_stream; + + fd->entry[fd->num_entries++] =3D 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 =3D sd_to_priv(sd); + struct v4l2_subdev_state *state; + int ret; + + state =3D v4l2_subdev_lock_and_get_active_state(&priv->sd); + + ret =3D 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 pa= d, + struct v4l2_mbus_config *cfg) +{ + struct max_des_priv *priv =3D sd_to_priv(sd); + struct max_des *des =3D priv->des; + struct max_des_phy *phy; + + phy =3D max_des_pad_to_phy(des, pad); + if (!phy) + return -EINVAL; + + cfg->type =3D phy->bus_type; + cfg->bus.mipi_csi2 =3D phy->mipi; + cfg->link_freq =3D 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 =3D sd_to_priv(sd); + struct max_des *des =3D priv->des; + const struct max_serdes_tpg_entry *entry; + struct v4l2_mbus_framefmt fmt =3D { 0 }; + int ret; + + ret =3D max_serdes_validate_tpg_routing(routing); + if (ret) + return ret; + + entry =3D &des->ops->tpg_entries.entries[0]; + + fmt.width =3D entry->width; + fmt.height =3D entry->height; + fmt.code =3D 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 =3D sd_to_priv(sd); + struct max_des *des =3D priv->des; + struct v4l2_subdev_route *route; + bool is_tpg =3D false; + int ret; + + ret =3D 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 =3D true; + break; + } + } + + if (is_tpg) + return max_des_set_tpg_routing(sd, state, routing); + + return v4l2_subdev_set_routing(sd, state, routing); +} + +static int max_des_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + struct max_des_priv *priv =3D sd_to_priv(sd); + struct max_des *des =3D priv->des; + + if (which =3D=3D 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 =3D priv->des; + struct max_des_pipe *pipe; + int ret; + + pipe =3D max_des_find_link_pipe(des, link); + if (!pipe) + return -ENOENT; + + ret =3D 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 =3D NULL; + struct max_des *des =3D 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 =3D max_des_route_to_hw(priv, state, route, &hw); + if (ret) + return ret; + + if (!hw.is_tpg) + continue; + + entry =3D max_des_find_state_tpg_entry(des, state, route->sink_pad); + break; + } + + if (entry =3D=3D des->tpg_entry) + return 0; + + ret =3D des->ops->set_tpg(des, entry); + if (ret) + return ret; + + des->tpg_entry =3D entry; + + return 0; +} + +static int max_des_update_active(struct max_des_priv *priv, u64 *streams_m= asks, + bool expected_active) +{ + struct max_des *des =3D priv->des; + bool active =3D false; + unsigned int i; + int ret; + + for (i =3D 0; i < des->ops->num_phys; i++) { + struct max_des_phy *phy =3D &des->phys[i]; + u32 pad =3D max_des_phy_to_pad(des, phy); + + if (streams_masks[pad]) { + active =3D true; + break; + } + } + + if (active !=3D expected_active || des->active =3D=3D active) + return 0; + + if (des->ops->set_enable) { + ret =3D des->ops->set_enable(des, active); + if (ret) + return ret; + } + + des->active =3D 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 =3D priv->des; + unsigned int failed_update_link_id =3D des->ops->num_links; + unsigned int i; + int ret; + + for (i =3D 0; i < des->ops->num_links; i++) { + struct max_des_link *link =3D &des->links[i]; + + ret =3D max_des_update_link(priv, context, link, state, + streams_masks); + if (ret) { + failed_update_link_id =3D i; + goto err; + } + } + + return 0; + +err: + for (i =3D 0; i < failed_update_link_id; i++) { + struct max_des_link *link =3D &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 =3D priv->des; + + return max_serdes_xlate_enable_disable_streams(priv->sources, 0, state, + pad, updated_streams_mask, 0, + des->ops->num_links, enable); +} + +static int max_des_update_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 updated_streams_mask, bool enable) +{ + struct max_des_priv *priv =3D v4l2_get_subdevdata(sd); + struct max_des_remap_context context =3D { 0 }; + struct max_des_mode_context mode_context =3D { 0 }; + struct max_des *des =3D priv->des; + unsigned int num_pads =3D max_des_num_pads(des); + u64 *streams_masks; + int ret; + + ret =3D max_des_populate_remap_context(priv, &context, state); + if (ret) + return ret; + + ret =3D max_des_populate_mode_context(priv, &mode_context, state, context= .mode); + if (ret) + return ret; + + ret =3D max_serdes_get_streams_masks(priv->dev, state, pad, updated_strea= ms_mask, + num_pads, priv->streams_masks, &streams_masks, + enable); + if (ret) + return ret; + + ret =3D max_des_set_pipes_phy(priv, &context); + if (ret) + goto err_free_streams_masks; + + ret =3D max_des_set_tunnel(priv, &context); + if (ret) + goto err_free_streams_masks; + + ret =3D max_des_set_modes(priv, &mode_context); + if (ret) + goto err_free_streams_masks; + + ret =3D max_des_set_vc_remaps(priv, &context, state, streams_masks); + if (ret) + return ret; + + ret =3D max_des_set_pipes_stream_id(priv); + if (ret) + goto err_free_streams_masks; + + if (!enable) { + ret =3D max_des_enable_disable_streams(priv, state, pad, + updated_streams_mask, enable); + if (ret) + goto err_free_streams_masks; + } + + ret =3D max_des_update_active(priv, streams_masks, false); + if (ret) + goto err_revert_streams_disable; + + ret =3D max_des_update_links(priv, &context, state, streams_masks); + if (ret) + goto err_revert_active_disable; + + ret =3D max_des_update_tpg(priv, state, streams_masks); + if (ret) + goto err_revert_links_update; + + ret =3D max_des_update_active(priv, streams_masks, true); + if (ret) + goto err_revert_tpg_update; + + if (enable) { + ret =3D 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 =3D 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] =3D { 0 }; + struct v4l2_subdev_krouting routing =3D { + .routes =3D routes, + }; + struct max_des_priv *priv =3D v4l2_get_subdevdata(sd); + struct max_des *des =3D priv->des; + struct max_des_phy *phy =3D NULL; + unsigned int stream =3D 0; + unsigned int i; + + for (i =3D 0; i < des->ops->num_phys; i++) { + if (des->phys[i].enabled) { + phy =3D &des->phys[i]; + break; + } + } + + if (!phy) + return 0; + + for (i =3D 0; i < des->ops->num_links; i++) { + struct max_des_link *link =3D &des->links[i]; + + if (!link->enabled) + continue; + + routing.routes[routing.num_routes++] =3D (struct v4l2_subdev_route) { + .sink_pad =3D max_des_link_to_pad(des, link), + .sink_stream =3D 0, + .source_pad =3D max_des_phy_to_pad(des, phy), + .source_stream =3D stream, + .flags =3D 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 =3D v4l2_get_subdevdata(sd); + struct max_des *des =3D priv->des; + unsigned int val; + int ret; + + ret =3D des->ops->reg_read(des, reg->reg, &val); + if (ret) + return ret; + + reg->val =3D val; + reg->size =3D 1; + + return 0; +} + +static int max_des_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct max_des_priv *priv =3D v4l2_get_subdevdata(sd); + struct max_des *des =3D priv->des; + + return des->ops->reg_write(des, reg->reg, reg->val); +} +#endif + +static const struct v4l2_subdev_core_ops max_des_core_ops =3D { + .log_status =3D max_des_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register =3D max_des_g_register, + .s_register =3D max_des_s_register, +#endif +}; + +static const struct v4l2_ctrl_ops max_des_ctrl_ops =3D { + .s_ctrl =3D max_des_s_ctrl, +}; + +static const struct v4l2_subdev_pad_ops max_des_pad_ops =3D { + .enable_streams =3D max_des_enable_streams, + .disable_streams =3D max_des_disable_streams, + + .set_routing =3D max_des_set_routing, + .get_frame_desc =3D max_des_get_frame_desc, + + .get_mbus_config =3D max_des_get_mbus_config, + + .get_fmt =3D v4l2_subdev_get_fmt, + .set_fmt =3D max_des_set_fmt, + + .enum_frame_interval =3D max_des_enum_frame_interval, + .get_frame_interval =3D v4l2_subdev_get_frame_interval, + .set_frame_interval =3D max_des_set_frame_interval, +}; + +static const struct v4l2_subdev_ops max_des_subdev_ops =3D { + .core =3D &max_des_core_ops, + .pad =3D &max_des_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops max_des_internal_ops =3D { + .init_state =3D &max_des_init_state, +}; + +static const struct media_entity_operations max_des_media_ops =3D { + .get_fwnode_pad =3D v4l2_subdev_get_fwnode_pad_1_to_1, + .has_pad_interdep =3D v4l2_subdev_has_pad_interdep, + .link_validate =3D 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 =3D nf_to_priv(nf); + struct max_serdes_asc *asc =3D asc_to_max(base_asc); + struct max_serdes_source *source =3D asc->source; + struct max_des *des =3D priv->des; + struct max_des_link *link =3D &des->links[source->index]; + u32 pad =3D max_des_link_to_pad(des, link); + int ret; + + ret =3D 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 =3D subdev; + source->pad =3D ret; + + ret =3D media_create_pad_link(&source->sd->entity, source->pad, + &priv->sd.entity, pad, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(priv->dev, "Unable to link %s:%u -> %s:%u\n", + source->sd->name, source->pad, priv->sd.name, pad); + return ret; + } + + return 0; +} + +static void max_des_notify_unbind(struct v4l2_async_notifier *nf, + struct v4l2_subdev *subdev, + struct v4l2_async_connection *base_asc) +{ + struct max_serdes_asc *asc =3D asc_to_max(base_asc); + struct max_serdes_source *source =3D asc->source; + + source->sd =3D NULL; +} + +static const struct v4l2_async_notifier_operations max_des_notify_ops =3D { + .bound =3D max_des_notify_bound, + .unbind =3D max_des_notify_unbind, +}; + +static int max_des_v4l2_notifier_register(struct max_des_priv *priv) +{ + struct max_des *des =3D priv->des; + unsigned int i; + int ret; + + v4l2_async_subdev_nf_init(&priv->nf, &priv->sd); + + for (i =3D 0; i < des->ops->num_links; i++) { + struct max_des_link *link =3D &des->links[i]; + struct max_serdes_source *source; + struct max_serdes_asc *asc; + + if (!link->enabled) + continue; + + source =3D max_des_get_link_source(priv, link); + if (!source->ep_fwnode) + continue; + + asc =3D 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 =3D source; + } + + priv->nf.ops =3D &max_des_notify_ops; + + ret =3D 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 =3D &priv->sd; + struct max_des *des =3D priv->des; + void *data =3D i2c_get_clientdata(priv->client); + unsigned int num_pads =3D 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 =3D &max_des_internal_ops; + sd->entity.function =3D MEDIA_ENT_F_VID_IF_BRIDGE; + sd->entity.ops =3D &max_des_media_ops; + sd->flags |=3D V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; + + for (i =3D 0; i < num_pads; i++) { + if (max_des_pad_is_sink(des, i)) + priv->pads[i].flags =3D MEDIA_PAD_FL_SINK; + else if (max_des_pad_is_source(des, i)) + priv->pads[i].flags =3D MEDIA_PAD_FL_SOURCE; + else if (max_des_pad_is_tpg(des, i)) + priv->pads[i].flags =3D MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_INTERNAL; + else + return -EINVAL; + } + + v4l2_set_subdevdata(sd, priv); + + if (des->ops->tpg_patterns) { + v4l2_ctrl_handler_init(&priv->ctrl_handler, 1); + priv->sd.ctrl_handler =3D &priv->ctrl_handler; + + v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler, + &max_des_ctrl_ops, + V4L2_CID_TEST_PATTERN, + MAX_SERDES_TPG_PATTERN_MAX, + ~des->ops->tpg_patterns, + __ffs(des->ops->tpg_patterns), + max_serdes_tpg_patterns); + if (priv->ctrl_handler.error) { + ret =3D priv->ctrl_handler.error; + goto err_free_ctrl; + } + } + + ret =3D media_entity_pads_init(&sd->entity, num_pads, priv->pads); + if (ret) + goto err_free_ctrl; + + ret =3D max_des_v4l2_notifier_register(priv); + if (ret) + goto err_media_entity_cleanup; + + ret =3D v4l2_subdev_init_finalize(sd); + if (ret) + goto err_nf_cleanup; + + ret =3D 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 =3D &priv->sd; + + v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(sd); + max_des_v4l2_notifier_unregister(priv); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&priv->ctrl_handler); +} + +static int max_des_update_pocs(struct max_des_priv *priv, bool enable) +{ + struct max_des *des =3D priv->des; + unsigned int i; + int ret; + + for (i =3D 0; i < des->ops->num_links; i++) { + struct max_des_link *link =3D &des->links[i]; + + if (!link->enabled) + continue; + + if (!priv->pocs[i]) + continue; + + if (enable) + ret =3D regulator_enable(priv->pocs[i]); + else + ret =3D regulator_disable(priv->pocs[i]); + + if (ret) { + dev_err(priv->dev, + "Failed to set POC supply to %u: %u\n", + enable, ret); + return ret; + } + } + + return 0; +} + +static int max_des_parse_sink_dt_endpoint(struct max_des_priv *priv, + struct max_des_link *link, + struct max_serdes_source *source, + struct fwnode_handle *fwnode) +{ + struct max_des *des =3D priv->des; + u32 pad =3D max_des_link_to_pad(des, link); + unsigned int index =3D link->index; + struct fwnode_handle *ep; + char poc_name[10]; + int ret; + + ep =3D fwnode_graph_get_endpoint_by_id(fwnode, pad, 0, 0); + if (!ep) + return 0; + + source->ep_fwnode =3D 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] =3D devm_regulator_get_optional(priv->dev, poc_name); + if (IS_ERR(priv->pocs[index])) { + ret =3D PTR_ERR(priv->pocs[index]); + if (ret !=3D -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] =3D NULL; + } + + link->enabled =3D 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 =3D priv->des; + u32 pad =3D max_des_phy_to_pad(des, phy); + struct v4l2_fwnode_endpoint v4l2_ep =3D { .bus_type =3D V4L2_MBUS_UNKNOWN= }; + struct v4l2_mbus_config_mipi_csi2 *mipi =3D &v4l2_ep.bus.mipi_csi2; + enum v4l2_mbus_type bus_type; + struct fwnode_handle *ep; + u64 link_frequency; + unsigned int i; + int ret; + + ep =3D fwnode_graph_get_endpoint_by_id(fwnode, pad, 0, 0); + if (!ep) + return 0; + + ret =3D 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 =3D v4l2_ep.bus_type; + if (bus_type !=3D V4L2_MBUS_CSI2_DPHY && + bus_type !=3D 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 =3D=3D 0) + link_frequency =3D MAX_DES_LINK_FREQUENCY_DEFAULT; + else if (v4l2_ep.nr_of_link_frequencies =3D=3D 1) + link_frequency =3D v4l2_ep.link_frequencies[0]; + else + ret =3D -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 =3D 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 =3D bus_type; + phy->mipi =3D *mipi; + phy->link_frequency =3D link_frequency; + phy->enabled =3D 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 =3D &des->ops->phys_configs; + const struct max_serdes_phys_config *config =3D + &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 =3D priv->des; + const struct max_serdes_phys_configs *configs =3D &des->ops->phys_configs; + struct max_des_phy *phy; + unsigned int i, j; + + if (!configs->num_configs) + return 0; + + for (i =3D 0; i < configs->num_configs; i++) { + const struct max_serdes_phys_config *config =3D &configs->configs[i]; + bool matching =3D true; + + for (j =3D 0; j < des->ops->num_phys; j++) { + phy =3D &des->phys[j]; + + if (!phy->enabled) + continue; + + if (phy->mipi.num_data_lanes <=3D config->lanes[j] && + phy->mipi.clock_lane =3D=3D config->clock_lane[j]) + continue; + + matching =3D false; + + break; + } + + if (matching) + break; + } + + if (i =3D=3D configs->num_configs) { + dev_err(priv->dev, "Invalid lane configuration\n"); + return -EINVAL; + } + + des->phys_config =3D i; + + return 0; +} + +static int max_des_parse_dt(struct max_des_priv *priv) +{ + struct fwnode_handle *fwnode =3D dev_fwnode(priv->dev); + struct max_des *des =3D priv->des; + struct max_des_link *link; + struct max_des_pipe *pipe; + struct max_des_phy *phy; + unsigned int i; + int ret; + + for (i =3D 0; i < des->ops->num_phys; i++) { + phy =3D &des->phys[i]; + phy->index =3D i; + + ret =3D max_des_parse_src_dt_endpoint(priv, phy, fwnode); + if (ret) + return ret; + } + + ret =3D max_des_find_phys_config(priv); + if (ret) + return ret; + + /* Find an unsed PHY to send unampped data to. */ + for (i =3D 0; i < des->ops->num_phys; i++) { + phy =3D &des->phys[i]; + + if (!phy->enabled) { + priv->unused_phy =3D phy; + break; + } + } + + for (i =3D 0; i < des->ops->num_pipes; i++) { + pipe =3D &des->pipes[i]; + pipe->index =3D i; + + /* + * Serializers can send data on different stream ids over the + * same link, and some deserializers support stream id autoselect + * allowing them to receive data from all stream ids. + * Deserializers that support that feature should enable it. + * Deserializers that support per-link stream ids do not need + * to assign unique stream ids to each serializer. + */ + if (des->ops->needs_unique_stream_id) + pipe->stream_id =3D i; + else + pipe->stream_id =3D 0; + + /* + * We already checked that num_pipes >=3D 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 =3D i % des->ops->num_links; + } + + for (i =3D 0; i < des->ops->num_links; i++) { + link =3D &des->links[i]; + link->index =3D i; + } + + for (i =3D 0; i < des->ops->num_links; i++) { + struct max_des_link *link =3D &des->links[i]; + struct max_serdes_source *source; + + source =3D max_des_get_link_source(priv, link); + source->index =3D i; + + ret =3D 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 =3D priv->des; + unsigned int num_pads =3D max_des_num_pads(des); + + des->phys =3D devm_kcalloc(priv->dev, des->ops->num_phys, + sizeof(*des->phys), GFP_KERNEL); + if (!des->phys) + return -ENOMEM; + + des->pipes =3D devm_kcalloc(priv->dev, des->ops->num_pipes, + sizeof(*des->pipes), GFP_KERNEL); + if (!des->pipes) + return -ENOMEM; + + des->links =3D devm_kcalloc(priv->dev, des->ops->num_links, + sizeof(*des->links), GFP_KERNEL); + if (!des->links) + return -ENOMEM; + + priv->sources =3D devm_kcalloc(priv->dev, des->ops->num_links, + sizeof(*priv->sources), GFP_KERNEL); + if (!priv->sources) + return -ENOMEM; + + priv->pocs =3D devm_kcalloc(priv->dev, des->ops->num_links, + sizeof(*priv->pocs), GFP_KERNEL); + if (!priv->pocs) + return -ENOMEM; + + priv->pads =3D devm_kcalloc(priv->dev, num_pads, + sizeof(*priv->pads), GFP_KERNEL); + if (!priv->pads) + return -ENOMEM; + + priv->streams_masks =3D 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 =3D &client->dev; + struct max_des_priv *priv; + int ret; + + if (des->ops->num_phys > MAX_DES_NUM_PHYS) + return -E2BIG; + + if (des->ops->num_pipes > MAX_DES_NUM_PIPES) + return -E2BIG; + + if (des->ops->num_links > MAX_DES_NUM_LINKS) + return -E2BIG; + + if (des->ops->num_links > des->ops->num_pipes) + return -E2BIG; + + priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (des->ops->set_link_version && !des->ops->select_links) { + dev_err(dev, + "Cannot implement .select_link_version() without .select_links()\n"); + return -EINVAL; + } + + if (hweight_long(des->ops->versions) >=3D 1 && + !des->ops->set_link_version) { + dev_err(dev, "Multiple version without .select_link_version()\n"); + return -EINVAL; + } + + priv->client =3D client; + priv->dev =3D dev; + priv->des =3D des; + des->priv =3D priv; + + ret =3D max_des_allocate(priv); + if (ret) + return ret; + + ret =3D max_des_parse_dt(priv); + if (ret) + return ret; + + ret =3D max_des_init(priv); + if (ret) + return ret; + + ret =3D max_des_update_pocs(priv, true); + if (ret) + return ret; + + ret =3D max_des_i2c_adapter_init(priv); + if (ret) + goto err_disable_pocs; + + ret =3D 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 =3D 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/m= axim-serdes/max_des.h new file mode 100644 index 0000000000000..dd5d37575710b --- /dev/null +++ b/drivers/media/i2c/maxim-serdes/max_des.h @@ -0,0 +1,153 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2025 Analog Devices Inc. + */ + +#ifndef MAX_DES_H +#define MAX_DES_H + +#include + +#include "max_serdes.h" + +#define MAX_DES_DT_VC(dt, vc) (((vc) & 0x3) << 6 | ((dt) & 0x3f)) + +struct max_des_remap { + u8 from_dt; + u8 from_vc; + u8 to_dt; + u8 to_vc; + u8 phy; +}; + +struct max_des_link { + unsigned int index; + bool enabled; + enum max_serdes_gmsl_version version; + struct max_serdes_i2c_xlate ser_xlate; +}; + +struct max_des_pipe_mode { + bool dbl8; + bool dbl10; + bool dbl12; + bool dbl8mode; + bool dbl10mode; +}; + +struct max_des_pipe { + unsigned int index; + unsigned int stream_id; + unsigned int link_id; + unsigned int phy_id; + struct max_des_remap *remaps; + unsigned int num_remaps; + struct max_serdes_vc_remap *vc_remaps; + unsigned int num_vc_remaps; + struct max_des_pipe_mode mode; + bool enabled; +}; + +struct max_des_phy_mode { + bool alt_mem_map8; + bool alt2_mem_map8; + bool alt_mem_map10; + bool alt_mem_map12; +}; + +struct max_des_phy { + unsigned int index; + s64 link_frequency; + struct v4l2_mbus_config_mipi_csi2 mipi; + enum v4l2_mbus_type bus_type; + struct max_des_phy_mode mode; + bool enabled; +}; + +struct max_des; + +struct max_des_ops { + unsigned int num_phys; + unsigned int num_pipes; + unsigned int num_links; + unsigned int num_remaps_per_pipe; + unsigned int versions; + unsigned int modes; + bool fix_tx_ids; + bool use_atr; + bool needs_single_link_version; + bool needs_unique_stream_id; + + struct max_serdes_phys_configs phys_configs; + struct max_serdes_tpg_entries tpg_entries; + enum max_serdes_gmsl_mode tpg_mode; + unsigned int tpg_patterns; + +#ifdef CONFIG_VIDEO_ADV_DEBUG + int (*reg_read)(struct max_des *des, unsigned int reg, unsigned int *val); + int (*reg_write)(struct max_des *des, unsigned int reg, unsigned int val); +#endif + int (*log_status)(struct max_des *des); + int (*log_pipe_status)(struct max_des *des, struct max_des_pipe *pipe); + int (*log_phy_status)(struct max_des *des, struct max_des_phy *phy); + int (*set_enable)(struct max_des *des, bool enable); + int (*set_tpg)(struct max_des *des, const struct max_serdes_tpg_entry *en= try); + 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 *p= ipe, + 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 *p= ipe, + bool enable); + int (*init_link)(struct max_des *des, struct max_des_link *link); + int (*select_links)(struct max_des *des, unsigned int mask); + int (*set_link_version)(struct max_des *des, struct max_des_link *link, + enum max_serdes_gmsl_version version); +}; + +struct max_des_priv; + +struct max_des { + struct max_des_priv *priv; + + const struct max_des_ops *ops; + + struct max_des_phy *phys; + struct max_des_pipe *pipes; + struct max_des_link *links; + const struct max_serdes_tpg_entry *tpg_entry; + enum max_serdes_tpg_pattern tpg_pattern; + + unsigned int phys_config; + enum max_serdes_gmsl_mode mode; + bool active; +}; + +int max_des_probe(struct i2c_client *client, struct max_des *des); + +int max_des_remove(struct max_des *des); + +int max_des_phy_hw_data_lanes(struct max_des *des, struct max_des_phy *phy= ); + +#endif // MAX_DES_H --=20 2.50.1