From patchwork Mon Mar 25 13:16:31 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Julien Massot X-Patchwork-Id: 782441 Received: from madrid.collaboradmins.com (madrid.collaboradmins.com [46.235.227.194]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B3AFE16F82C; Mon, 25 Mar 2024 13:17:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=46.235.227.194 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711372672; cv=none; b=uEtC+Fgz9oKuKu6rXhbHRBxI7B1v+DKxDYqWPNwTO/7aaOa89/gkEMutT4/tYMR5Lm8NX5su+aHYyjKe3gUhvMztmxPZxkACoH/thg11za149ZjYc8Z7ZoOLzfqTOkxjYszL4GJeVaei/VuwIuK+kUQoZtcHJBwmJ3Xi+qQl/aY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711372672; c=relaxed/simple; bh=tqhRn7aY4jXwTtjVre34DbkQtJfAQfE1eebK6HWRAjQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=sJ0kBRdr2XS/e2ZLGHgvd6W8vGYiG5xsLU5/JrUsT/5Qn6VAK0E7ZpIZ7diiUwhFuMTlsmf7yXoC0aHVRpP0ipRmTy5oo/dHR96FnEaGUvzER9PIiuFRhSc/vKm74wiyLj6G0ZuiZZGqnnzgtBC1SJ7/sqkGp2Vf2nc+XbaMpl4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=collabora.com; spf=pass smtp.mailfrom=collabora.com; dkim=pass (2048-bit key) header.d=collabora.com header.i=@collabora.com header.b=p5VO5/S9; arc=none smtp.client-ip=46.235.227.194 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=collabora.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=collabora.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=collabora.com header.i=@collabora.com header.b="p5VO5/S9" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; s=mail; t=1711372669; bh=tqhRn7aY4jXwTtjVre34DbkQtJfAQfE1eebK6HWRAjQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=p5VO5/S98kgWG4J+B9iaOIFpbiAho9gMeU7gRpOxAyvafKmbUGnSh0DAwV6gZT7sg X6FdRSUwAJUGTjAPpD3t2eRyVFH4EoYmPwS/mvh+J2tHSQBUUqFj0h+kyOU9GN8HBk epnjuUTrZ3cYn//5qnJV9oHRGKfjzkPpu7zeUOT6CktLmYomMLq5Wj93Iy5SiBaGOP KlZsOaUoPywCCWA+gCEBXmxvAs9FsYd2bepNE/61MFPjXEHrbuqGMq5MEt+lyrzarD olYZeEQQHklx3qRn1DPM/5t/pg3EpZ+GIrBzd6pH4BpkEqndM8opItkgcrNNKDPED6 0YyXqyP1s6DMw== Received: from stla-brain-8255-1.home (cola.collaboradmins.com [195.201.22.229]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: jmassot) by madrid.collaboradmins.com (Postfix) with ESMTPSA id 6460D3782039; Mon, 25 Mar 2024 13:17:48 +0000 (UTC) From: Julien Massot To: linux-media@vger.kernel.org Cc: devicetree@vger.kernel.org, kernel@collabora.com, linux-kernel@vger.kernel.org, mchehab@kernel.org, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org, conor+dt@kernel.org, sakari.ailus@iki.fi, Julien Massot Subject: [PATCH v6 1/4] dt-bindings: media: add Maxim MAX96717 GMSL2 Serializer Date: Mon, 25 Mar 2024 14:16:31 +0100 Message-ID: <20240325131634.165361-2-julien.massot@collabora.com> X-Mailer: git-send-email 2.44.0 In-Reply-To: <20240325131634.165361-1-julien.massot@collabora.com> References: <20240325131634.165361-1-julien.massot@collabora.com> Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Add DT bindings for Maxim MAX96717 GMSL2 Serializer. Signed-off-by: Julien Massot Reviewed-by: Conor Dooley --- Change since v5: - Reverse the fallback MAX96717 can fallback to MAX96717F - Use const instead of enum for MAX96717F compatible Change since v4: - Add compatible for MAX96717 and use it as a fallback for MAX96717F - Remove extra '|' for decriptions - Reference 'i2c-gate' instead of 'i2c-controller' Change since v3: - Renamed file to maxim,max96717.yaml dropped the 'f' suffix - Added lane-polarities and bus type properties to the CSI endpoint Change since v2: - remove reg description - add data lanes min/maxItems - Use generic node name --- .../bindings/media/i2c/maxim,max96717.yaml | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml new file mode 100644 index 000000000000..ac8bf11a6fa5 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml @@ -0,0 +1,164 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (C) 2024 Collabora Ltd. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/maxim,max96717.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MAX96717 CSI-2 to GMSL2 Serializer + +maintainers: + - Julien Massot + +description: + The MAX96717 serializer converts MIPI CSI-2 D-PHY or C-PHY formatted input + into GMSL2 serial outputs. The device allows the GMSL2 link to + simultaneously transmit bidirectional control-channel data while forward + video transmissions are in progress. The MAX96717 can connect to one + remotely located deserializer using industry-standard coax or STP + interconnects. The device cans operate in pixel or tunnel mode. In pixel mode + the MAX96717 can select the MIPI datatype, while the tunnel mode forward all the MIPI + data received by the serializer. + The MAX96717 supports Reference Over Reverse (channel), + to generate a clock output for the sensor from the GMSL reverse channel. + + The GMSL2 serial link operates at a fixed rate of 3Gbps or 6Gbps in the + forward direction and 187.5Mbps in the reverse direction. + MAX96717F only supports a fixed rate of 3Gbps in the forward direction. + +properties: + compatible: + oneOf: + - const: maxim,max96717f + - items: + - enum: + - maxim,max96717 + - const: maxim,max96717f + + '#gpio-cells': + const: 2 + description: + First cell is the GPIO pin number, second cell is the flags. The GPIO pin + number must be in range of [0, 10]. + + gpio-controller: true + + '#clock-cells': + const: 0 + + reg: + maxItems: 1 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: CSI-2 Input port + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + + lane-polarities: + minItems: 1 + maxItems: 5 + + bus-type: + enum: + - 1 # MEDIA_BUS_TYPE_CSI2_CPHY + - 4 # MEDIA_BUS_TYPE_CSI2_DPHY + + required: + - data-lanes + - bus-type + + port@1: + $ref: /schemas/graph.yaml#/properties/port + unevaluatedProperties: false + description: GMSL Output port + + required: + - port@1 + + i2c-gate: + $ref: /schemas/i2c/i2c-gate.yaml + unevaluatedProperties: false + description: + The MAX96717 will forward the I2C requests from the + incoming GMSL2 link. Therefore, it supports an i2c-gate + subnode to configure a sensor. + +required: + - compatible + - reg + - ports + +additionalProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + serializer: serializer@40 { + compatible = "maxim,max96717f"; + reg = <0x40>; + gpio-controller; + #gpio-cells = <2>; + #clock-cells = <0>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + max96717f_csi_in: endpoint { + bus-type = ; + data-lanes = <1 2 3 4>; + remote-endpoint = <&sensor_out>; + }; + }; + + port@1 { + reg = <1>; + max96917f_gmsl_out: endpoint { + remote-endpoint = <&deser_gmsl_in>; + }; + }; + }; + + i2c-gate { + #address-cells = <1>; + #size-cells = <0>; + sensor@10 { + compatible = "st,st-vgxy61"; + reg = <0x10>; + reset-gpios = <&serializer 0 GPIO_ACTIVE_LOW>; + clocks = <&serializer>; + VCORE-supply = <&v1v2>; + VDDIO-supply = <&v1v8>; + VANA-supply = <&v2v8>; + port { + sensor_out: endpoint { + data-lanes = <1 2 3 4>; + remote-endpoint = <&max96717f_csi_in>; + }; + }; + }; + }; + }; + }; +... From patchwork Mon Mar 25 13:16:32 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Julien Massot X-Patchwork-Id: 783904 Received: from madrid.collaboradmins.com (madrid.collaboradmins.com [46.235.227.194]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 74E0216F831; Mon, 25 Mar 2024 13:17:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=46.235.227.194 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711372673; cv=none; b=bSl6GSVO2s5UeS7tUPlEL/NeO5xFKCEpMUg1HwUl1LlA3HyUKiTvhNjmp4xA8FXCQ4pWNY/rSHAeQmjPRm3tBvS73/jIuiV5+0KBS4lAEl2rjqDdURTj8wfegsw4UJrIkN208J0daQHMjsfcnx91qoDpq4gb5f8WkGwunKcmeFw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711372673; c=relaxed/simple; bh=5iWGjHxt8ZWFZhQnPk2ARczEXPRNBEdu+RGl4jbmpo4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=KCJrMTNOSmXecfbTdFjN8wVFzSnRyyJLj4wDcVp4QzwRlcLt69L37Jr7aSja43kcCzcJ3/PoQyCyyG6K7DP1IsUv8oBvmucNT3og9LhKiVwYsiQqg1bJyPdYEj7YE4s0uKL8C6xEhexJn2llqxtwrIebJQfOZYVsuFNEx6WMKtw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=collabora.com; spf=pass smtp.mailfrom=collabora.com; dkim=pass (2048-bit key) header.d=collabora.com header.i=@collabora.com header.b=0RUMwNi8; arc=none smtp.client-ip=46.235.227.194 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=collabora.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=collabora.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=collabora.com header.i=@collabora.com header.b="0RUMwNi8" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; s=mail; t=1711372669; bh=5iWGjHxt8ZWFZhQnPk2ARczEXPRNBEdu+RGl4jbmpo4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=0RUMwNi8/BvQNGbk71yOcZ/nVKGyVFTlwGJQBoKkXlHIVktKsQeuH+/xG7kwA4naC ba+9qKQ5dgnBw512mluBNTSQquTgeVz+Bo6ZoL9f2nwn2PaPyFQNsYOxyKp6c8n2uJ +V6/Qub+NYRCnR+v7Qx1DnhLiSsYTZ11aTOfjlgXBiJr9eKCKkmUid6qABpm+m21m9 m1UvXn2KbyelBGS84GXFPHBatCSB+z/iGgFQeiRekevZqx4v5+LuMWaLluy19qjvkD aDO20MABFOuNy/23xKwXyuykku0n+inXTK8Gt2rTWqHsRSoCVGZUd40VORYCI0lnNG bIaL/e4VwHzlQ== Received: from stla-brain-8255-1.home (cola.collaboradmins.com [195.201.22.229]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: jmassot) by madrid.collaboradmins.com (Postfix) with ESMTPSA id 3D059378209E; Mon, 25 Mar 2024 13:17:49 +0000 (UTC) From: Julien Massot To: linux-media@vger.kernel.org Cc: devicetree@vger.kernel.org, kernel@collabora.com, linux-kernel@vger.kernel.org, mchehab@kernel.org, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org, conor+dt@kernel.org, sakari.ailus@iki.fi, Julien Massot Subject: [PATCH v6 2/4] dt-bindings: media: add Maxim MAX96714 GMSL2 Deserializer Date: Mon, 25 Mar 2024 14:16:32 +0100 Message-ID: <20240325131634.165361-3-julien.massot@collabora.com> X-Mailer: git-send-email 2.44.0 In-Reply-To: <20240325131634.165361-1-julien.massot@collabora.com> References: <20240325131634.165361-1-julien.massot@collabora.com> Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Add DT bindings for Maxim MAX96714 GMSL2 Deserializer. Signed-off-by: Julien Massot --- Change since v5: - Reverse the fallback MAX96714 can fallback to MAX96714F - Use const instead of enum for MAX96714F compatible Change since v4: - Add compatible for MAX96714 and use it as a fallback for MAX96714F - Remove extra '|' for decriptions - Reference 'i2c-gate' instead of 'i2c-controller' Change since v3: - Renamed file to maxim,max96714.yaml dropped the 'f' suffix - Removed mention to C-PHY since it's not supported by MAX96714 deserializers - Removed bus-type requirement on CSI endpoint since the device only support D-PHY - Removed the clock-lanes property in the dt example Change since v2: - remove reg description - rename enable gpio to powerdown - use generic node name: i2c, serializer, deserializer --- .../bindings/media/i2c/maxim,max96714.yaml | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml new file mode 100644 index 000000000000..fdcba14fcde9 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml @@ -0,0 +1,175 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (C) 2024 Collabora Ltd. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/maxim,max96714.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Maxim MAX96714 GMSL2 to CSI-2 Deserializer + +maintainers: + - Julien Massot + +description: + The MAX96714 deserializer converts GMSL2 serial inputs into MIPI + CSI-2 D-PHY formatted output. The device allows the GMSL2 link to + simultaneously transmit bidirectional control-channel data while forward + video transmissions are in progress. The MAX96714 can connect to one + remotely located serializer using industry-standard coax or STP + interconnects. The device cans operate in pixel or tunnel mode. In pixel mode + the MAX96714 can select individual video stream, while the tunnel mode forward all + the MIPI data received by the serializer. + + The GMSL2 serial link operates at a fixed rate of 3Gbps or 6Gbps in the + forward direction and 187.5Mbps in the reverse direction. + MAX96714F only supports a fixed rate of 3Gbps in the forward direction. + +properties: + compatible: + oneOf: + - const: maxim,max96714f + - items: + - enum: + - maxim,max96714 + - const: maxim,max96714f + + reg: + maxItems: 1 + + powerdown-gpios: + maxItems: 1 + description: + Specifier for the GPIO connected to the PWDNB pin. + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + unevaluatedProperties: false + description: GMSL Input + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + description: + Endpoint for GMSL2-Link port. + + port@1: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: CSI-2 Output port + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + + lane-polarities: + minItems: 1 + maxItems: 5 + + link-frequencies: + maxItems: 1 + + required: + - data-lanes + + required: + - port@1 + + i2c-gate: + $ref: /schemas/i2c/i2c-gate.yaml + unevaluatedProperties: false + description: + The MAX96714 will pass through and forward the I2C requests from the + incoming I2C bus over the GMSL2 link. Therefore it supports an i2c-gate + subnode to configure a serializer. + + port0-poc-supply: + description: Regulator providing Power over Coax for the GMSL port + +required: + - compatible + - reg + - ports + +additionalProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + deserializer@28 { + compatible = "maxim,max96714f"; + reg = <0x28>; + powerdown-gpios = <&main_gpio0 37 GPIO_ACTIVE_LOW>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + max96714_gmsl_in: endpoint { + remote-endpoint = <&max96917f_gmsl_out>; + }; + }; + + port@1 { + reg = <1>; + max96714_csi_out: endpoint { + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <400000000>; + remote-endpoint = <&csi_in>; + }; + }; + }; + + i2c-gate { + #address-cells = <1>; + #size-cells = <0>; + + serializer@40 { + compatible = "maxim,max96717f"; + reg = <0x40>; + gpio-controller; + #gpio-cells = <2>; + #clock-cells = <0>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + max96717f_csi_in: endpoint { + bus-type = ; + data-lanes = <1 2>; + lane-polarities = <1 0 1>; + remote-endpoint = <&sensor_out>; + }; + }; + + port@1 { + reg = <1>; + max96917f_gmsl_out: endpoint { + remote-endpoint = <&max96714_gmsl_in>; + }; + }; + }; + }; + }; + }; + }; +... From patchwork Mon Mar 25 13:16:33 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Julien Massot X-Patchwork-Id: 782440 Received: from madrid.collaboradmins.com (madrid.collaboradmins.com [46.235.227.194]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A2E0816FF26; Mon, 25 Mar 2024 13:17:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=46.235.227.194 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711372675; cv=none; b=sxImZl5CXX3w3hnq9mcSzsAqjgcufmw61AsIYe9DgB0oahgZ79jJs1qld97+goKPDBjmHsJ2j05BTHr+zozrsQOqYORYb7/dh7M1Ok0nqPepdV47FBldbcumx6uOwfk6nYIq/b/StY4QSyOtOHPtkNSz5UIRFmvzPKmXxCps0/c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711372675; c=relaxed/simple; bh=46qant1vv0esyX7bPh8krebGdK6xuoIgdU74/6tJ8pw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=vBG9tik7iB/WErSpIsqPtbIC3xrDXR8Ew3pf2LJDADfYiaQvHcvDKqJ4jR1Sag+kCgOMEx5mZjIJJ2/HTf7Devn+cz40HCaN1o+qsXwiT8oJrs/110ExO4t3uDHEHKKx0Rjkpt5d4qHDgm3PYR9xpSEftzIiV5cj1fYHsVqG1u8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=collabora.com; spf=pass smtp.mailfrom=collabora.com; dkim=pass (2048-bit key) header.d=collabora.com header.i=@collabora.com header.b=2Dp9rjY6; arc=none smtp.client-ip=46.235.227.194 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=collabora.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=collabora.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=collabora.com header.i=@collabora.com header.b="2Dp9rjY6" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; s=mail; t=1711372671; bh=46qant1vv0esyX7bPh8krebGdK6xuoIgdU74/6tJ8pw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=2Dp9rjY6K5I78JNT+X0BMUrA4hEphIv26+b4tWEyzxrYV2adMSwxRHLtNJeCbd999 Zs97t1zJ0E5aQ/tK69zKpM/hVwq5ao8GlH7YoAnMGlN9841fF7TufXnSIP5UPMyQeh ksfMNfVuFSXvZU3IrUvaqjupt4I2cgiNaxbxb3YinVYq7nKH9s+ePX8Uj/ihzsQOAR SDv5BprT9WaYr8xNL04hnrMHzYQtYRBOdfJrDeymxyrhz/4sjA3XdMmP3j8EjJNX1m d79ehq2EaI2+CNb40sfIiedL6IXxBKW4ySeTNVNpS7PDiNpk8ZhPj9geIzBuM8JPx2 gw7xO23MzK0eQ== Received: from stla-brain-8255-1.home (cola.collaboradmins.com [195.201.22.229]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: jmassot) by madrid.collaboradmins.com (Postfix) with ESMTPSA id 101AC37820BB; Mon, 25 Mar 2024 13:17:50 +0000 (UTC) From: Julien Massot To: linux-media@vger.kernel.org Cc: devicetree@vger.kernel.org, kernel@collabora.com, linux-kernel@vger.kernel.org, mchehab@kernel.org, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org, conor+dt@kernel.org, sakari.ailus@iki.fi, Julien Massot Subject: [PATCH v6 3/4] media: i2c: add MAX96717 driver Date: Mon, 25 Mar 2024 14:16:33 +0100 Message-ID: <20240325131634.165361-4-julien.massot@collabora.com> X-Mailer: git-send-email 2.44.0 In-Reply-To: <20240325131634.165361-1-julien.massot@collabora.com> References: <20240325131634.165361-1-julien.massot@collabora.com> Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 This driver handles the MAX96717 serializer in tunnel mode. All incoming CSI traffic will be tunneled through the GMSL2 link. The MAX96717 driver can handle MAX96717 and MAX96717F variants with the same "maxim,max96717f" compatible. Signed-off-by: Julien Massot --- Change since v5: - set the driver compatible back to MAX96717F that can be used as a fallback for MAX96717 Change since v4: - make the driver compatible with MAX96717 instead of MAX96717F - Add the device id for the MAX96717 - remove hw_data structure for now, it can be usefull later for handling different serializers e.g max9295 Change since v3: - Maintainers: align to the new binding path - Kconfig: better describe the symbol - store the v4l2_mbus_config_mipi_csi2 structure instead of the full endpoint in the driver private structure - use MAX96717_PAD_SINK/SOURCE instead of 0/1 for pad intialization - Removed incorrect call to fwnode_handle_put(priv->sd.fwnode) - Use unsigned int instead of u8 - Allocate clk name out of the clk struct initialization - fixed multiline comment - Removed one unnecessary goto at the end of the probe function Change since v2: - Use CCI helpers instead of recoding register access - add missing bitfield header --- MAINTAINERS | 7 + drivers/media/i2c/Kconfig | 14 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/max96717.c | 934 +++++++++++++++++++++++++++++++++++ 4 files changed, 956 insertions(+) create mode 100644 drivers/media/i2c/max96717.c diff --git a/MAINTAINERS b/MAINTAINERS index b43102ca365d..c43088157f6d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13277,6 +13277,13 @@ S: Maintained F: Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml F: drivers/staging/media/max96712/max96712.c +MAX96717 GMSL2 SERIALIZER DRIVER +M: Julien Massot +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/i2c/maxim,max96717.yaml +F: drivers/media/i2c/max96717.c + MAX9860 MONO AUDIO VOICE CODEC DRIVER M: Peter Rosin L: alsa-devel@alsa-project.org (moderated for non-subscribers) diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 56f276b920ab..1a99396edbcf 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -1573,6 +1573,20 @@ config VIDEO_DS90UB960 Device driver for the Texas Instruments DS90UB960 FPD-Link III Deserializer and DS90UB9702 FPD-Link IV Deserializer. +config VIDEO_MAX96717 + tristate "Maxim MAX96717 GMSL2 Serializer support" + depends on OF && I2C && VIDEO_DEV && COMMON_CLK + select I2C_MUX + select GPIOLIB + select V4L2_CCI_I2C + help + Device driver for the Maxim MAX96717 GMSL2 Serializer. + MAX96717 serializers convert video on a MIPI CSI-2 + input to a GMSL2 output. + + To compile this driver as a module, choose M here: the + module will be called max96717. + endmenu endif # VIDEO_DEV diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index dfbe6448b549..9e007116f929 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -64,6 +64,7 @@ obj-$(CONFIG_VIDEO_LM3646) += lm3646.o obj-$(CONFIG_VIDEO_M52790) += m52790.o obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o obj-$(CONFIG_VIDEO_MAX9286) += max9286.o +obj-$(CONFIG_VIDEO_MAX96717) += max96717.o obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o obj-$(CONFIG_VIDEO_MT9M001) += mt9m001.o diff --git a/drivers/media/i2c/max96717.c b/drivers/media/i2c/max96717.c new file mode 100644 index 000000000000..9ff0bc58b6a9 --- /dev/null +++ b/drivers/media/i2c/max96717.c @@ -0,0 +1,934 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Maxim GMSL2 Serializer Driver + * + * Copyright (C) 2024 Collabora Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define MAX96717_DEVICE_ID 0xbf +#define MAX96717F_DEVICE_ID 0xc8 +#define MAX96717_PORTS 2 +#define MAX96717_PAD_SINK 0 +#define MAX96717_PAD_SOURCE 1 + +#define MAX96717_DEFAULT_CLKOUT_RATE 24000000UL + +/* DEV */ +#define REG3 CCI_REG8(0x3) +#define MAX96717_RCLKSEL GENMASK(1, 0) +#define RCLKSEL_REF_PLL CCI_REG8(0x3) +#define MAX96717_REG6 CCI_REG8(0x6) +#define RCLKEN BIT(5) +#define MAX96717_DEV_ID CCI_REG8(0xd) +#define MAX96717_DEV_REV CCI_REG8(0xe) +#define MAX96717_DEV_REV_MASK GENMASK(3, 0) + +/* VID_TX Z */ +#define MAX96717_VIDEO_TX2 CCI_REG8(0x112) +#define MAX96717_VIDEO_PCLKDET BIT(7) + +/* GPIO */ +#define MAX96717_NUM_GPIO 11 +#define MAX96717_GPIO_REG_A(gpio) CCI_REG8(0x2be + (gpio) * 3) +#define MAX96717_GPIO_OUT BIT(4) +#define MAX96717_GPIO_IN BIT(3) +#define MAX96717_GPIO_RX_EN BIT(2) +#define MAX96717_GPIO_TX_EN BIT(1) +#define MAX96717_GPIO_OUT_DIS BIT(0) + +/* FRONTTOP */ +/* MAX96717 only have CSI port 'B' */ +#define MAX96717_FRONTOP0 CCI_REG8(0x308) +#define MAX96717_START_PORT_B BIT(5) + +/* MIPI_RX */ +#define MAX96717_MIPI_RX1 CCI_REG8(0x331) +#define MAX96717_MIPI_LANES_CNT GENMASK(5, 4) +#define MAX96717_MIPI_RX2 CCI_REG8(0x332) /* phy1 Lanes map */ +#define MAX96717_PHY2_LANES_MAP GENMASK(7, 4) +#define MAX96717_MIPI_RX3 CCI_REG8(0x333) /* phy2 Lanes map */ +#define MAX96717_PHY1_LANES_MAP GENMASK(3, 0) +#define MAX96717_MIPI_RX4 CCI_REG8(0x334) /* phy1 lane polarities */ +#define MAX96717_PHY1_LANES_POL GENMASK(6, 4) +#define MAX96717_MIPI_RX5 CCI_REG8(0x335) /* phy2 lane polarities */ +#define MAX96717_PHY2_LANES_POL GENMASK(2, 0) + +/* MIPI_RX_EXT */ +#define MAX96717_MIPI_RX_EXT11 CCI_REG8(0x383) +#define MAX96717_TUN_MODE BIT(7) + +/* REF_VTG */ +#define REF_VTG0 CCI_REG8(0x3f0) +#define REFGEN_PREDEF_EN BIT(6) +#define REFGEN_PREDEF_FREQ_MASK GENMASK(5, 4) +#define REFGEN_PREDEF_FREQ_ALT BIT(3) +#define REFGEN_RST BIT(1) +#define REFGEN_EN BIT(0) + +/* MISC */ +#define PIO_SLEW_1 CCI_REG8(0x570) + +struct max96717_priv { + struct i2c_client *client; + struct regmap *regmap; + struct i2c_mux_core *mux; + struct v4l2_mbus_config_mipi_csi2 mipi_csi2; + struct v4l2_subdev sd; + struct media_pad pads[MAX96717_PORTS]; + struct v4l2_async_notifier notifier; + struct v4l2_subdev *source_sd; + u16 source_sd_pad; + u64 enabled_source_streams; + u8 pll_predef_index; + struct clk_hw clk_hw; + struct gpio_chip gpio_chip; +}; + +static inline struct max96717_priv *sd_to_max96717(struct v4l2_subdev *sd) +{ + return container_of(sd, struct max96717_priv, sd); +} + +static inline struct max96717_priv *clk_hw_to_max96717(struct clk_hw *hw) +{ + return container_of(hw, struct max96717_priv, clk_hw); +} + +static int max96717_i2c_mux_select(struct i2c_mux_core *mux, u32 chan) +{ + return 0; +} + +static int max96717_i2c_mux_init(struct max96717_priv *priv) +{ + priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev, + 1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE, + max96717_i2c_mux_select, NULL); + if (!priv->mux) + return -ENOMEM; + + return i2c_mux_add_adapter(priv->mux, 0, 0, 0); +} + +static inline int max96717_start_csi(struct max96717_priv *priv, bool start) +{ + return cci_update_bits(priv->regmap, MAX96717_FRONTOP0, + MAX96717_START_PORT_B, + start ? MAX96717_START_PORT_B : 0, NULL); +} + +static int max96717_gpiochip_get(struct gpio_chip *gpiochip, + unsigned int offset) +{ + struct max96717_priv *priv = gpiochip_get_data(gpiochip); + u64 val; + int ret; + + ret = cci_read(priv->regmap, MAX96717_GPIO_REG_A(offset), + &val, NULL); + if (ret) + return ret; + + if (val & MAX96717_GPIO_OUT_DIS) + return !!(val & MAX96717_GPIO_IN); + else + return !!(val & MAX96717_GPIO_OUT); +} + +static void max96717_gpiochip_set(struct gpio_chip *gpiochip, + unsigned int offset, int value) +{ + struct max96717_priv *priv = gpiochip_get_data(gpiochip); + + cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset), + MAX96717_GPIO_OUT, MAX96717_GPIO_OUT, NULL); +} + +static int max96717_gpio_get_direction(struct gpio_chip *gpiochip, + unsigned int offset) +{ + struct max96717_priv *priv = gpiochip_get_data(gpiochip); + u64 val; + int ret; + + ret = cci_read(priv->regmap, MAX96717_GPIO_REG_A(offset), &val, NULL); + if (ret < 0) + return ret; + + return !!(val & MAX96717_GPIO_OUT_DIS); +} + +static int max96717_gpio_direction_out(struct gpio_chip *gpiochip, + unsigned int offset, int value) +{ + struct max96717_priv *priv = gpiochip_get_data(gpiochip); + + return cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset), + MAX96717_GPIO_OUT_DIS | MAX96717_GPIO_OUT, + value ? MAX96717_GPIO_OUT : 0, NULL); +} + +static int max96717_gpio_direction_in(struct gpio_chip *gpiochip, + unsigned int offset) +{ + struct max96717_priv *priv = gpiochip_get_data(gpiochip); + + return cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset), + MAX96717_GPIO_OUT_DIS, MAX96717_GPIO_OUT_DIS, + NULL); +} + +static int max96717_gpiochip_probe(struct max96717_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct gpio_chip *gc = &priv->gpio_chip; + int ret, i; + + gc->label = dev_name(dev); + gc->parent = dev; + gc->owner = THIS_MODULE; + gc->ngpio = MAX96717_NUM_GPIO; + gc->base = -1; + gc->can_sleep = true; + gc->get_direction = max96717_gpio_get_direction; + gc->direction_input = max96717_gpio_direction_in; + gc->direction_output = max96717_gpio_direction_out; + gc->set = max96717_gpiochip_set; + gc->get = max96717_gpiochip_get; + gc->of_gpio_n_cells = 2; + + /* Disable GPIO forwarding */ + for (i = 0; i < gc->ngpio; i++) + cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(i), + MAX96717_GPIO_RX_EN | MAX96717_GPIO_TX_EN, + 0, &ret); + + if (ret) + return ret; + + ret = devm_gpiochip_add_data(dev, gc, priv); + if (ret) { + dev_err(dev, "Unable to create gpio_chip\n"); + return ret; + } + + return 0; +} + +static int _max96717_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + static const struct v4l2_mbus_framefmt format = { + .width = 1280, + .height = 1080, + .code = MEDIA_BUS_FMT_Y8_1X8, + .field = V4L2_FIELD_NONE, + }; + int ret; + + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + if (ret) + return ret; + + ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); + if (ret) + return ret; + + return 0; +} + +static int max96717_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + struct max96717_priv *priv = sd_to_max96717(sd); + + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams) + return -EBUSY; + + return _max96717_set_routing(sd, state, routing); +} + +static int max96717_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct max96717_priv *priv = sd_to_max96717(sd); + struct v4l2_mbus_framefmt *fmt; + u64 stream_source_mask; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && + priv->enabled_source_streams) + return -EBUSY; + + /* No transcoding, source and sink formats must match. */ + if (format->pad == MAX96717_PAD_SOURCE) + return v4l2_subdev_get_fmt(sd, state, format); + + /* Set sink format */ + fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + /* Propagate to source format */ + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + *fmt = format->format; + + stream_source_mask = BIT(format->stream); + + return v4l2_subdev_state_xlate_streams(state, MAX96717_PAD_SOURCE, + MAX96717_PAD_SINK, + &stream_source_mask); +} + +static int max96717_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route routes[] = { + { + .sink_pad = MAX96717_PAD_SINK, + .sink_stream = 0, + .source_pad = MAX96717_PAD_SOURCE, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + struct v4l2_subdev_krouting routing = { + .num_routes = ARRAY_SIZE(routes), + .routes = routes, + }; + + return _max96717_set_routing(sd, state, &routing); +} + +static bool max96717_pipe_pclkdet(struct max96717_priv *priv) +{ + u64 val = 0; + + cci_read(priv->regmap, MAX96717_VIDEO_TX2, &val, NULL); + + return val & MAX96717_VIDEO_PCLKDET; +} + +static int max96717_log_status(struct v4l2_subdev *sd) +{ + struct max96717_priv *priv = sd_to_max96717(sd); + struct device *dev = &priv->client->dev; + + dev_info(dev, "Serializer: max96717\n"); + dev_info(dev, "Pipe: pclkdet:%d\n", max96717_pipe_pclkdet(priv)); + + return 0; +} + +static int max96717_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct max96717_priv *priv = sd_to_max96717(sd); + struct device *dev = &priv->client->dev; + u64 sink_streams; + int ret; + + sink_streams = v4l2_subdev_state_xlate_streams(state, + MAX96717_PAD_SOURCE, + MAX96717_PAD_SINK, + &streams_mask); + + if (!priv->enabled_source_streams) + max96717_start_csi(priv, true); + + ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad, + sink_streams); + if (ret) { + dev_err(dev, "Fail to start streams:%llu on remote subdev\n", + sink_streams); + goto stop_csi; + } + + priv->enabled_source_streams |= streams_mask; + + return 0; + +stop_csi: + if (!priv->enabled_source_streams) + max96717_start_csi(priv, false); + return ret; +} + +static int max96717_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct max96717_priv *priv = sd_to_max96717(sd); + u64 sink_streams; + int ret; + + sink_streams = v4l2_subdev_state_xlate_streams(state, + MAX96717_PAD_SOURCE, + MAX96717_PAD_SINK, + &streams_mask); + + ret = v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad, + sink_streams); + if (ret) + return ret; + + priv->enabled_source_streams &= ~streams_mask; + + if (!priv->enabled_source_streams) + max96717_start_csi(priv, false); + + return 0; +} + +static const struct v4l2_subdev_pad_ops max96717_pad_ops = { + .enable_streams = max96717_enable_streams, + .disable_streams = max96717_disable_streams, + .set_routing = max96717_set_routing, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = max96717_set_fmt, +}; + +static const struct v4l2_subdev_core_ops max96717_subdev_core_ops = { + .log_status = max96717_log_status, +}; + +static const struct v4l2_subdev_internal_ops max96717_internal_ops = { + .init_state = max96717_init_state, +}; + +static const struct v4l2_subdev_ops max96717_subdev_ops = { + .core = &max96717_subdev_core_ops, + .pad = &max96717_pad_ops, +}; + +static const struct media_entity_operations max96717_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int max96717_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *source_subdev, + struct v4l2_async_connection *asd) +{ + struct max96717_priv *priv = sd_to_max96717(notifier->sd); + struct device *dev = &priv->client->dev; + int ret; + + ret = media_entity_get_fwnode_pad(&source_subdev->entity, + source_subdev->fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(dev, "Failed to find pad for %s\n", + source_subdev->name); + return ret; + } + + priv->source_sd = source_subdev; + priv->source_sd_pad = ret; + + ret = media_create_pad_link(&source_subdev->entity, priv->source_sd_pad, + &priv->sd.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(dev, "Unable to link %s:%u -> %s:0\n", + source_subdev->name, priv->source_sd_pad, + priv->sd.name); + return ret; + } + + return 0; +} + +static const struct v4l2_async_notifier_operations max96717_notify_ops = { + .bound = max96717_notify_bound, +}; + +static int max96717_v4l2_notifier_register(struct max96717_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct v4l2_async_connection *asd; + struct fwnode_handle *ep_fwnode; + int ret; + + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + MAX96717_PAD_SINK, 0, 0); + if (!ep_fwnode) { + dev_err(dev, "No graph endpoint\n"); + return -ENODEV; + } + + v4l2_async_subdev_nf_init(&priv->notifier, &priv->sd); + + asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, ep_fwnode, + struct v4l2_async_connection); + + fwnode_handle_put(ep_fwnode); + + if (IS_ERR(asd)) { + dev_err(dev, "Failed to add subdev: %ld", PTR_ERR(asd)); + v4l2_async_nf_cleanup(&priv->notifier); + return PTR_ERR(asd); + } + + priv->notifier.ops = &max96717_notify_ops; + + ret = v4l2_async_nf_register(&priv->notifier); + if (ret) { + dev_err(dev, "Failed to register subdev_notifier"); + v4l2_async_nf_cleanup(&priv->notifier); + return ret; + } + + return 0; +} + +static void max96717_v4l2_notifier_unregister(struct max96717_priv *priv) +{ + v4l2_async_nf_unregister(&priv->notifier); + v4l2_async_nf_cleanup(&priv->notifier); +} + +static int max96717_subdev_init(struct max96717_priv *priv) +{ + struct device *dev = &priv->client->dev; + int ret; + + v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96717_subdev_ops); + priv->sd.internal_ops = &max96717_internal_ops; + + priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; + priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + priv->sd.entity.ops = &max96717_entity_ops; + + priv->pads[MAX96717_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + priv->pads[MAX96717_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&priv->sd.entity, 2, priv->pads); + if (ret) + return dev_err_probe(dev, ret, "Failed to init pads\n"); + + ret = v4l2_subdev_init_finalize(&priv->sd); + if (ret) { + dev_err_probe(dev, ret, + "v4l2 subdev init finalized failed\n"); + goto err_entity_cleanup; + } + ret = max96717_v4l2_notifier_register(priv); + if (ret) { + dev_err_probe(dev, ret, + "v4l2 subdev notifier register failed\n"); + goto err_free_state; + } + + ret = v4l2_async_register_subdev(&priv->sd); + if (ret) { + dev_err_probe(dev, ret, "v4l2_async_register_subdev error\n"); + goto err_unreg_notif; + } + + return 0; + +err_unreg_notif: + max96717_v4l2_notifier_unregister(priv); +err_free_state: + v4l2_subdev_cleanup(&priv->sd); +err_entity_cleanup: + media_entity_cleanup(&priv->sd.entity); + + return ret; +} + +static void max96717_subdev_uninit(struct max96717_priv *priv) +{ + v4l2_async_unregister_subdev(&priv->sd); + max96717_v4l2_notifier_unregister(priv); + v4l2_subdev_cleanup(&priv->sd); + media_entity_cleanup(&priv->sd.entity); +} + +struct max96717_pll_predef_freq { + unsigned long freq; + bool is_alt; + u8 val; +}; + +static const struct max96717_pll_predef_freq max96717_predef_freqs[] = { + { 13500000, true, 0 }, { 19200000, false, 0 }, + { 24000000, true, 1 }, { 27000000, false, 1 }, + { 37125000, false, 2 }, { 74250000, false, 3 }, +}; + +static unsigned long +max96717_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct max96717_priv *priv = clk_hw_to_max96717(hw); + + return max96717_predef_freqs[priv->pll_predef_index].freq; +} + +static unsigned int max96717_clk_find_best_index(struct max96717_priv *priv, + unsigned long rate) +{ + unsigned int i, idx; + unsigned long diff_new, diff_old; + + diff_old = U32_MAX; + idx = 0; + + for (i = 0; i < ARRAY_SIZE(max96717_predef_freqs); i++) { + diff_new = abs(rate - max96717_predef_freqs[i].freq); + if (diff_new < diff_old) { + diff_old = diff_new; + idx = i; + } + } + + return idx; +} + +static long max96717_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct max96717_priv *priv = clk_hw_to_max96717(hw); + struct device *dev = &priv->client->dev; + unsigned int idx; + + idx = max96717_clk_find_best_index(priv, rate); + + if (rate != max96717_predef_freqs[idx].freq) { + dev_warn(dev, "Request CLK freq:%lu, found CLK freq:%lu\n", + rate, max96717_predef_freqs[idx].freq); + } + + return max96717_predef_freqs[idx].freq; +} + +static int max96717_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct max96717_priv *priv = clk_hw_to_max96717(hw); + unsigned int val, idx; + int ret = 0; + + idx = max96717_clk_find_best_index(priv, rate); + + val = FIELD_PREP(REFGEN_PREDEF_FREQ_MASK, + max96717_predef_freqs[idx].val); + + if (max96717_predef_freqs[idx].is_alt) + val |= REFGEN_PREDEF_FREQ_ALT; + + val |= REFGEN_RST | REFGEN_PREDEF_EN; + + cci_write(priv->regmap, REF_VTG0, val, &ret); + cci_update_bits(priv->regmap, REF_VTG0, REFGEN_RST | REFGEN_EN, + REFGEN_EN, &ret); + if (ret) + return ret; + + priv->pll_predef_index = idx; + + return 0; +} + +static int max96717_clk_prepare(struct clk_hw *hw) +{ + struct max96717_priv *priv = clk_hw_to_max96717(hw); + + return cci_update_bits(priv->regmap, MAX96717_REG6, RCLKEN, + RCLKEN, NULL); +} + +static void max96717_clk_unprepare(struct clk_hw *hw) +{ + struct max96717_priv *priv = clk_hw_to_max96717(hw); + + cci_update_bits(priv->regmap, MAX96717_REG6, RCLKEN, 0, NULL); +} + +static const struct clk_ops max96717_clk_ops = { + .prepare = max96717_clk_prepare, + .unprepare = max96717_clk_unprepare, + .set_rate = max96717_clk_set_rate, + .recalc_rate = max96717_clk_recalc_rate, + .round_rate = max96717_clk_round_rate, +}; + +static int max96717_register_clkout(struct max96717_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct clk_init_data init = { .ops = &max96717_clk_ops }; + int ret; + + init.name = kasprintf(GFP_KERNEL, "max96717.%s.clk_out", + dev_name(dev)); + if (!init.name) + return -ENOMEM; + + /* RCLKSEL Reference PLL output */ + ret = cci_update_bits(priv->regmap, REG3, MAX96717_RCLKSEL, + RCLKSEL_REF_PLL, NULL); + /* MFP4 fastest slew rate */ + cci_update_bits(priv->regmap, PIO_SLEW_1, BIT(5) | BIT(4), 0, &ret); + if (ret) + goto free_init_name; + + priv->clk_hw.init = &init; + + /* Initialize to 24 MHz */ + ret = max96717_clk_set_rate(&priv->clk_hw, + MAX96717_DEFAULT_CLKOUT_RATE, 0); + if (ret < 0) + goto free_init_name; + + ret = devm_clk_hw_register(dev, &priv->clk_hw); + kfree(init.name); + if (ret) + return dev_err_probe(dev, ret, "Cannot register clock HW\n"); + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + &priv->clk_hw); + if (ret) + return dev_err_probe(dev, ret, + "Cannot add OF clock provider\n"); + + return 0; + +free_init_name: + kfree(init.name); + return ret; +} + +static int max96717_init_csi_lanes(struct max96717_priv *priv) +{ + struct v4l2_mbus_config_mipi_csi2 *mipi = &priv->mipi_csi2; + unsigned long lanes_used = 0; + unsigned int nlanes, lane, val = 0; + int ret; + + nlanes = mipi->num_data_lanes; + + ret = cci_update_bits(priv->regmap, MAX96717_MIPI_RX1, + MAX96717_MIPI_LANES_CNT, + FIELD_PREP(MAX96717_MIPI_LANES_CNT, + nlanes - 1), NULL); + + /* lanes polarity */ + for (lane = 0; lane < nlanes + 1; lane++) { + if (!mipi->lane_polarities[lane]) + continue; + /* Clock lane */ + if (lane == 0) + val |= BIT(2); + else if (lane < 3) + val |= BIT(lane - 1); + else + val |= BIT(lane); + } + + cci_update_bits(priv->regmap, MAX96717_MIPI_RX5, + MAX96717_PHY2_LANES_POL, + FIELD_PREP(MAX96717_PHY2_LANES_POL, val), &ret); + + cci_update_bits(priv->regmap, MAX96717_MIPI_RX4, + MAX96717_PHY1_LANES_POL, + FIELD_PREP(MAX96717_PHY1_LANES_POL, + val >> 3), &ret); + /* lanes mapping */ + for (lane = 0, val = 0; lane < nlanes; lane++) { + val |= (mipi->data_lanes[lane] - 1) << (lane * 2); + lanes_used |= BIT(mipi->data_lanes[lane] - 1); + } + + /* + * Unused lanes need to be mapped as well to not have + * the same lanes mapped twice. + */ + for (; lane < 4; lane++) { + unsigned int idx = find_first_zero_bit(&lanes_used, 4); + + val |= idx << (lane * 2); + lanes_used |= BIT(idx); + } + + cci_update_bits(priv->regmap, MAX96717_MIPI_RX3, + MAX96717_PHY1_LANES_MAP, + FIELD_PREP(MAX96717_PHY1_LANES_MAP, val), &ret); + + return cci_update_bits(priv->regmap, MAX96717_MIPI_RX2, + MAX96717_PHY2_LANES_MAP, + FIELD_PREP(MAX96717_PHY2_LANES_MAP, val >> 4), + &ret); +} + +static int max96717_hw_init(struct max96717_priv *priv) +{ + struct device *dev = &priv->client->dev; + u64 dev_id, val; + int ret; + + ret = cci_read(priv->regmap, MAX96717_DEV_ID, &dev_id, NULL); + if (ret) + return dev_err_probe(dev, ret, + "Fail to read the device id\n"); + + if (dev_id != MAX96717_DEVICE_ID && dev_id != MAX96717F_DEVICE_ID) + return dev_err_probe(dev, -EOPNOTSUPP, + "Unsupported device id got %x\n", (u8)dev_id); + + ret = cci_read(priv->regmap, MAX96717_DEV_REV, &val, NULL); + if (ret) + return dev_err_probe(dev, ret, + "Fail to read device revision"); + + dev_dbg(dev, "Found %x (rev %lx)\n", (u8)dev_id, + (u8)val & MAX96717_DEV_REV_MASK); + + ret = cci_read(priv->regmap, MAX96717_MIPI_RX_EXT11, &val, NULL); + if (ret) + return dev_err_probe(dev, ret, + "Fail to read mipi rx extension"); + + if (!(val & MAX96717_TUN_MODE)) + return dev_err_probe(dev, -EOPNOTSUPP, + "Only supporting tunnel mode"); + + return max96717_init_csi_lanes(priv); +} + +static int max96717_parse_dt(struct max96717_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + struct fwnode_handle *ep_fwnode; + unsigned char num_data_lanes; + int ret; + + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + MAX96717_PAD_SINK, 0, 0); + if (!ep_fwnode) + return dev_err_probe(dev, -ENOENT, "no endpoint found\n"); + + ret = v4l2_fwnode_endpoint_parse(ep_fwnode, &vep); + + fwnode_handle_put(ep_fwnode); + + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to parse sink endpoint"); + + num_data_lanes = vep.bus.mipi_csi2.num_data_lanes; + if (num_data_lanes < 1 || num_data_lanes > 4) + return dev_err_probe(dev, -EINVAL, + "Invalid data lanes must be 1 to 4\n"); + + memcpy(&priv->mipi_csi2, &vep.bus.mipi_csi2, sizeof(priv->mipi_csi2)); + + return 0; +} + +static int max96717_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct max96717_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + i2c_set_clientdata(client, priv); + + priv->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(priv->regmap)) { + ret = PTR_ERR(priv->regmap); + return dev_err_probe(dev, ret, "Failed to init regmap\n"); + } + + ret = max96717_parse_dt(priv); + if (ret) + return dev_err_probe(dev, ret, "Failed to parse the dt\n"); + + ret = max96717_hw_init(priv); + if (ret) + return dev_err_probe(dev, ret, + "Failed to initialize the hardware\n"); + + ret = max96717_gpiochip_probe(priv); + if (ret) { + dev_err(&client->dev, "Failed to init gpiochip\n"); + return ret; + } + + ret = max96717_register_clkout(priv); + if (ret) + return dev_err_probe(dev, ret, "Failed to register clkout\n"); + + ret = max96717_subdev_init(priv); + if (ret) + return dev_err_probe(dev, ret, + "Failed to initialize v4l2 subdev\n"); + + ret = max96717_i2c_mux_init(priv); + if (ret) { + dev_err_probe(dev, ret, "failed to add remote i2c adapter\n"); + max96717_subdev_uninit(priv); + } + + return ret; +} + +static void max96717_remove(struct i2c_client *client) +{ + struct max96717_priv *priv = i2c_get_clientdata(client); + + max96717_subdev_uninit(priv); + i2c_mux_del_adapters(priv->mux); +} + +static const struct of_device_id max96717_of_ids[] = { + { .compatible = "maxim,max96717f" }, + { } +}; +MODULE_DEVICE_TABLE(of, max96717_of_ids); + +static struct i2c_driver max96717_i2c_driver = { + .driver = { + .name = "max96717", + .of_match_table = max96717_of_ids, + }, + .probe = max96717_probe, + .remove = max96717_remove, +}; + +module_i2c_driver(max96717_i2c_driver); + +MODULE_DESCRIPTION("Maxim GMSL2 MAX96717 Serializer Driver"); +MODULE_AUTHOR("Julien Massot "); +MODULE_LICENSE("GPL"); From patchwork Mon Mar 25 13:16:34 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Julien Massot X-Patchwork-Id: 783903 Received: from madrid.collaboradmins.com (madrid.collaboradmins.com [46.235.227.194]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8F027170A2E; Mon, 25 Mar 2024 13:17:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=46.235.227.194 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711372676; cv=none; b=upO8qVjqU70bq84BggIBPRxf9fhr6PTPYu7hbfWHlCytkqhtY5YkmCPLlxIgpB/+NN0AYyu8sccwhBSkUUONooHzm4a0cUjWt+yOaTb5CSevfOKwxNiG1clDE/u97KejZlZBMpYoblNwq/qwjR49ulqg9cbTxwn1ndvxVMrFL8I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711372676; c=relaxed/simple; bh=u30cxwsM1PXKR1yd2ri211SHxS3hJLaNPG7PQbRuVZc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=vAj3ToDPD30tDQMBaHKaAmiitDcvvQHXuG4fLuM0rzNtVUXPhRMW/hKcQKLYjC7Dendeq2EgaEcaGwxeV65KU3EPPOR4ANwJICXp2nCGmLKFYofz91UQcA8P3cNAtzQLPfw/omCIrgHA2CMGERMLGjiU2H5yGIYigEyFFMih1wg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=collabora.com; spf=pass smtp.mailfrom=collabora.com; dkim=pass (2048-bit key) header.d=collabora.com header.i=@collabora.com header.b=xK2lWXwe; arc=none smtp.client-ip=46.235.227.194 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=collabora.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=collabora.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=collabora.com header.i=@collabora.com header.b="xK2lWXwe" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; s=mail; t=1711372672; bh=u30cxwsM1PXKR1yd2ri211SHxS3hJLaNPG7PQbRuVZc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=xK2lWXwepSvxHGxUccECEBVZYzNDfiyfNZ6i3NFXHWUm8aF8XwDD86/jhdC1ReCYa pP2OuzxVupBfp/r5Oi5xYekzF7csdG0eqWat6aa634gqqQ+9xjpjKURXOZsadzOBqa DcoW+psSG4I8e0KStxORGGLxGV0Xi0qFjYsc71nHVFxibdFX12gCzw/09YoSTmj5Pc Zl6W5d+Jepec2brtF0PPN7hO/71Sa8sTN4+7CuMpIATsHsq8lFcV0nY614FVK1p0SS DoAQRea5GUvtUqTvBcwyMD6wMRqsaFfJBRuRwCvdJrdML23LwMSUZCNXQ3xPU5FTmE UjISMEa1ugmHg== Received: from stla-brain-8255-1.home (cola.collaboradmins.com [195.201.22.229]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: jmassot) by madrid.collaboradmins.com (Postfix) with ESMTPSA id 2785037820E1; Mon, 25 Mar 2024 13:17:51 +0000 (UTC) From: Julien Massot To: linux-media@vger.kernel.org Cc: devicetree@vger.kernel.org, kernel@collabora.com, linux-kernel@vger.kernel.org, mchehab@kernel.org, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org, conor+dt@kernel.org, sakari.ailus@iki.fi, Julien Massot Subject: [PATCH v6 4/4] media: i2c: add MAX96714 driver Date: Mon, 25 Mar 2024 14:16:34 +0100 Message-ID: <20240325131634.165361-5-julien.massot@collabora.com> X-Mailer: git-send-email 2.44.0 In-Reply-To: <20240325131634.165361-1-julien.massot@collabora.com> References: <20240325131634.165361-1-julien.massot@collabora.com> Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 This driver handles the MAX96714 deserializer in tunnel mode. The CSI output will replicate all the CSI traffic forwarded by the remote serializer. The MAX96714 driver can handle MAX96714 and MAX96714F variants with the same "maxim,max96714f" compatible. Signed-off-by: Julien Massot --- Change since v5: - set the driver compatible back to MAX96714F that can be used as a fallback for MAX96714 Change since v4: - make the driver compatible with MAX96714 instead of MAX96714F - Add the device id for the MAX96714 Change since v3: - Maintainers: align to the new binding path - Kconfig: better describe the symbol - Aligned the macro - Store the v4l2_mbus_config_mipi_csi2 structure instead of the full endpoint in the driver private structure - moved ret variables declaration at last - Removed extra new lines - Return the v4l2_subdev_set_routing_with_fmt result in _max96714_set_routing - Use v4l2_subdev_s_stream_helper instead of the custom max96714_s_stream function - Removed unnecessary check 'if (priv->tx_link_freq < 0)' in create_subdev since dt is already validated in max96714_parse_dt_txport - Use div_u64 to fix compilation on 32 bits platforms - Specify D-PHY for fwnode endpoint parsing since the MAX96714 only supports D-PHY - Simplify parse_dt function by parsing first the tx_port so that we no longer have to call fwnode_handle_put(priv->rxport.source.ep_fwnode); - Do not initialize regmap twice - Use unsigned int instead of u8 Change since v2: - Use CCI helpers instead of recoding register access - add missing bitfield header - Add pattern generator so the deserializer can be tested without a serializer/sensor --- MAINTAINERS | 7 + drivers/media/i2c/Kconfig | 14 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/max96714.c | 1029 ++++++++++++++++++++++++++++++++++ 4 files changed, 1051 insertions(+) create mode 100644 drivers/media/i2c/max96714.c diff --git a/MAINTAINERS b/MAINTAINERS index c43088157f6d..6423782f04b6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13277,6 +13277,13 @@ S: Maintained F: Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml F: drivers/staging/media/max96712/max96712.c +MAX96714 GMSL2 DESERIALIZER DRIVER +M: Julien Massot +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/i2c/maxim,max96714.yaml +F: drivers/media/i2c/max96714.c + MAX96717 GMSL2 SERIALIZER DRIVER M: Julien Massot L: linux-media@vger.kernel.org diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 1a99396edbcf..64fc4c1c1fc1 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -1573,6 +1573,20 @@ config VIDEO_DS90UB960 Device driver for the Texas Instruments DS90UB960 FPD-Link III Deserializer and DS90UB9702 FPD-Link IV Deserializer. +config VIDEO_MAX96714 + tristate "Maxim MAX96714 GMSL2 deserializer" + depends on OF && I2C && VIDEO_DEV + select I2C_MUX + select GPIOLIB + select V4L2_CCI_I2C + help + Device driver for the Maxim MAX96714 GMSL2 Deserializer. + MAX96714 deserializers convert a GMSL2 input to MIPI CSI-2 + output. + + To compile this driver as a module, choose M here: the + module will be called max96714. + config VIDEO_MAX96717 tristate "Maxim MAX96717 GMSL2 Serializer support" depends on OF && I2C && VIDEO_DEV && COMMON_CLK diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 9e007116f929..7c794441eaff 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -64,6 +64,7 @@ obj-$(CONFIG_VIDEO_LM3646) += lm3646.o obj-$(CONFIG_VIDEO_M52790) += m52790.o obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o obj-$(CONFIG_VIDEO_MAX9286) += max9286.o +obj-$(CONFIG_VIDEO_MAX96714) += max96714.o obj-$(CONFIG_VIDEO_MAX96717) += max96717.o obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o diff --git a/drivers/media/i2c/max96714.c b/drivers/media/i2c/max96714.c new file mode 100644 index 000000000000..8192de389220 --- /dev/null +++ b/drivers/media/i2c/max96714.c @@ -0,0 +1,1029 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Maxim GMSL2 Deserializer Driver + * + * Copyright (C) 2024 Collabora Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define MAX96714_DEVICE_ID 0xc9 +#define MAX96714F_DEVICE_ID 0xca +#define MAX96714_NPORTS 2 +#define MAX96714_PAD_SINK 0 +#define MAX96714_PAD_SOURCE 1 + +/* DEV */ +#define MAX96714_REG13 CCI_REG8(0x0d) +#define MAX96714_DEV_REV CCI_REG8(0x0e) +#define MAX96714_DEV_REV_MASK GENMASK(3, 0) +#define MAX96714_LINK_LOCK CCI_REG8(0x13) +#define MAX96714_LINK_LOCK_BIT BIT(3) +#define MAX96714_IO_CHK0 CCI_REG8(0x38) +#define MAX96714_PATTERN_CLK_FREQ GENMASK(1, 0) +/* VID_RX */ +#define MAX96714_VIDEO_RX8 CCI_REG8(0x11a) +#define MAX96714_VID_LOCK BIT(6) + +/* VRX_PATGEN_0 */ +#define MAX96714_PATGEN_0 CCI_REG8(0x240) +#define MAX96714_PATGEN_1 CCI_REG8(0x241) +#define MAX96714_PATGEN_MODE GENMASK(5, 4) +#define MAX96714_PATGEN_VS_DLY CCI_REG24(0x242) +#define MAX96714_PATGEN_VS_HIGH CCI_REG24(0x245) +#define MAX96714_PATGEN_VS_LOW CCI_REG24(0x248) +#define MAX96714_PATGEN_V2H CCI_REG24(0x24b) +#define MAX96714_PATGEN_HS_HIGH CCI_REG16(0x24e) +#define MAX96714_PATGEN_HS_LOW CCI_REG16(0x250) +#define MAX96714_PATGEN_HS_CNT CCI_REG16(0x252) +#define MAX96714_PATGEN_V2D CCI_REG24(0x254) +#define MAX96714_PATGEN_DE_HIGH CCI_REG16(0x257) +#define MAX96714_PATGEN_DE_LOW CCI_REG16(0x259) +#define MAX96714_PATGEN_DE_CNT CCI_REG16(0x25B) +#define MAX96714_PATGEN_GRAD_INC CCI_REG8(0x25d) +#define MAX96714_PATGEN_CHKB_COLOR_A CCI_REG24(0x25E) +#define MAX96714_PATGEN_CHKB_COLOR_B CCI_REG24(0x261) +#define MAX96714_PATGEN_CHKB_RPT_CNT_A CCI_REG8(0x264) +#define MAX96714_PATGEN_CHKB_RPT_CNT_B CCI_REG8(0x265) +#define MAX96714_PATGEN_CHKB_ALT CCI_REG8(0x266) +/* BACKTOP */ +#define MAX96714_BACKTOP25 CCI_REG8(0x320) +#define CSI_DPLL_FREQ_MASK GENMASK(4, 0) + +/* MIPI_PHY */ +#define MAX96714_MIPI_PHY0 CCI_REG8(0x330) +#define MAX96714_FORCE_CSI_OUT BIT(7) +#define MAX96714_MIPI_STDBY_N CCI_REG8(0x332) +#define MAX96714_MIPI_STDBY_MASK GENMASK(5, 4) +#define MAX96714_MIPI_LANE_MAP CCI_REG8(0x333) +#define MAX96714_MIPI_POLARITY CCI_REG8(0x335) +#define MAX96714_MIPI_POLARITY_MASK GENMASK(5, 0) + +/* MIPI_TX */ +#define MAX96714_MIPI_LANE_CNT CCI_REG8(0x44a) +#define MAX96714_CSI2_LANE_CNT_MASK GENMASK(7, 6) +#define MAX96714_MIPI_TX52 CCI_REG8(0x474) +#define MAX96714_TUN_EN BIT(0) + +#define MHZ(v) ((u32)((v) * 1000000U)) + +enum max96714_vpg_mode { + MAX96714_VPG_DISABLED = 0, + MAX96714_VPG_CHECKERBOARD = 1, + MAX96714_VPG_GRADIENT = 2, +}; + +struct max96714_rxport { + struct { + struct v4l2_subdev *sd; + u16 pad; + struct fwnode_handle *ep_fwnode; + } source; + struct regulator *poc; +}; + +struct max96714_txport { + struct v4l2_fwnode_endpoint vep; +}; + +struct max96714_priv { + struct i2c_client *client; + struct regmap *regmap; + struct gpio_desc *pd_gpio; + struct max96714_rxport rxport; + struct i2c_mux_core *mux; + u64 enabled_source_streams; + struct v4l2_subdev sd; + struct media_pad pads[MAX96714_NPORTS]; + struct v4l2_mbus_config_mipi_csi2 mipi_csi2; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_async_notifier notifier; + s64 tx_link_freq; + enum max96714_vpg_mode pattern; +}; + +static inline struct max96714_priv *sd_to_max96714(struct v4l2_subdev *sd) +{ + return container_of(sd, struct max96714_priv, sd); +} + +static int max96714_enable_tx_port(struct max96714_priv *priv) +{ + return cci_update_bits(priv->regmap, MAX96714_MIPI_STDBY_N, + MAX96714_MIPI_STDBY_MASK, + MAX96714_MIPI_STDBY_MASK, NULL); +} + +static int max96714_disable_tx_port(struct max96714_priv *priv) +{ + return cci_update_bits(priv->regmap, MAX96714_MIPI_STDBY_N, + MAX96714_MIPI_STDBY_MASK, 0, NULL); +} + +static bool max96714_tx_port_enabled(struct max96714_priv *priv) +{ + u64 val; + + cci_read(priv->regmap, MAX96714_MIPI_STDBY_N, &val, NULL); + + return val & MAX96714_MIPI_STDBY_MASK; +} + +static int max96714_apply_patgen_timing(struct max96714_priv *priv, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *fmt = + v4l2_subdev_state_get_format(state, MAX96714_PAD_SOURCE); + const u32 h_active = fmt->width; + const u32 h_fp = 88; + const u32 h_sw = 44; + const u32 h_bp = 148; + u32 h_tot; + + const u32 v_active = fmt->height; + const u32 v_fp = 4; + const u32 v_sw = 5; + const u32 v_bp = 36; + u32 v_tot; + int ret = 0; + + h_tot = h_active + h_fp + h_sw + h_bp; + v_tot = v_active + v_fp + v_sw + v_bp; + + /* 75 Mhz pixel clock */ + cci_update_bits(priv->regmap, MAX96714_IO_CHK0, + MAX96714_PATTERN_CLK_FREQ, 1, &ret); + + dev_info(&priv->client->dev, "height: %d width: %d\n", fmt->height, + fmt->width); + + cci_write(priv->regmap, MAX96714_PATGEN_VS_DLY, 0, &ret); + cci_write(priv->regmap, MAX96714_PATGEN_VS_HIGH, v_sw * h_tot, &ret); + cci_write(priv->regmap, MAX96714_PATGEN_VS_LOW, + (v_active + v_fp + v_bp) * h_tot, &ret); + cci_write(priv->regmap, MAX96714_PATGEN_HS_HIGH, h_sw, &ret); + cci_write(priv->regmap, MAX96714_PATGEN_HS_LOW, h_active + h_fp + h_bp, + &ret); + cci_write(priv->regmap, MAX96714_PATGEN_V2D, + h_tot * (v_sw + v_bp) + (h_sw + h_bp), &ret); + cci_write(priv->regmap, MAX96714_PATGEN_HS_CNT, v_tot, &ret); + cci_write(priv->regmap, MAX96714_PATGEN_DE_HIGH, h_active, &ret); + cci_write(priv->regmap, MAX96714_PATGEN_DE_LOW, h_fp + h_sw + h_bp, + &ret); + cci_write(priv->regmap, MAX96714_PATGEN_DE_CNT, v_active, &ret); + /* B G R */ + cci_write(priv->regmap, MAX96714_PATGEN_CHKB_COLOR_A, 0xfecc00, &ret); + /* B G R */ + cci_write(priv->regmap, MAX96714_PATGEN_CHKB_COLOR_B, 0x006aa7, &ret); + cci_write(priv->regmap, MAX96714_PATGEN_CHKB_RPT_CNT_A, 0x3c, &ret); + cci_write(priv->regmap, MAX96714_PATGEN_CHKB_RPT_CNT_B, 0x3c, &ret); + cci_write(priv->regmap, MAX96714_PATGEN_CHKB_ALT, 0x3c, &ret); + cci_write(priv->regmap, MAX96714_PATGEN_GRAD_INC, 0x10, &ret); + + return ret; +} + +static int max96714_apply_patgen(struct max96714_priv *priv, + struct v4l2_subdev_state *state) +{ + unsigned int val; + int ret = 0; + + if (priv->pattern) + ret = max96714_apply_patgen_timing(priv, state); + + cci_write(priv->regmap, MAX96714_PATGEN_0, priv->pattern ? 0xfb : 0, + &ret); + + val = FIELD_PREP(MAX96714_PATGEN_MODE, priv->pattern); + cci_update_bits(priv->regmap, MAX96714_PATGEN_1, MAX96714_PATGEN_MODE, + val, &ret); + return ret; +} + +static int max96714_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct max96714_priv *priv = + container_of(ctrl->handler, struct max96714_priv, ctrl_handler); + int ret; + + switch (ctrl->id) { + case V4L2_CID_TEST_PATTERN: + priv->pattern = ctrl->val; + break; + default: + return -EINVAL; + } + + ret = cci_update_bits(priv->regmap, MAX96714_MIPI_PHY0, + MAX96714_FORCE_CSI_OUT, + priv->pattern ? MAX96714_FORCE_CSI_OUT : 0, NULL); + + /* Pattern generator doesn't work with tunnel mode */ + return cci_update_bits(priv->regmap, MAX96714_MIPI_TX52, + MAX96714_TUN_EN, + priv->pattern ? 0 : MAX96714_TUN_EN, &ret); +} + +static const char * const max96714_test_pattern[] = { + "Disabled", + "Checkerboard", + "Gradient" +}; + +static const struct v4l2_ctrl_ops max96714_ctrl_ops = { + .s_ctrl = max96714_s_ctrl, +}; + +static int max96714_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 source_pad, u64 streams_mask) +{ + struct max96714_priv *priv = sd_to_max96714(sd); + u64 sink_streams; + int ret; + + if (!priv->enabled_source_streams) + max96714_enable_tx_port(priv); + + ret = max96714_apply_patgen(priv, state); + if (ret) + goto err; + + if (!priv->pattern) { + if (!priv->rxport.source.sd) { + ret = -ENODEV; + goto err; + } + + sink_streams = + v4l2_subdev_state_xlate_streams(state, + MAX96714_PAD_SOURCE, + MAX96714_PAD_SINK, + &streams_mask); + + ret = v4l2_subdev_enable_streams(priv->rxport.source.sd, + priv->rxport.source.pad, + sink_streams); + if (ret) + goto err; + } + + priv->enabled_source_streams |= streams_mask; + + return 0; + +err: + if (!priv->enabled_source_streams) + max96714_disable_tx_port(priv); + + return ret; +} + +static int max96714_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 source_pad, u64 streams_mask) +{ + struct max96714_priv *priv = sd_to_max96714(sd); + u64 sink_streams; + int ret; + + if (!priv->pattern && priv->rxport.source.sd) { + sink_streams = + v4l2_subdev_state_xlate_streams(state, + MAX96714_PAD_SOURCE, + MAX96714_PAD_SINK, + &streams_mask); + + ret = v4l2_subdev_disable_streams(priv->rxport.source.sd, + priv->rxport.source.pad, + sink_streams); + if (ret) + return ret; + } + + priv->enabled_source_streams &= ~streams_mask; + + if (!priv->enabled_source_streams) + max96714_disable_tx_port(priv); + + return 0; +} + +static int max96714_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct max96714_priv *priv = sd_to_max96714(sd); + struct v4l2_mbus_framefmt *fmt; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && + priv->enabled_source_streams) + return -EBUSY; + + /* No transcoding, source and sink formats must match. */ + if (format->pad == MAX96714_PAD_SOURCE) + return v4l2_subdev_get_fmt(sd, state, format); + + fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + return 0; +} + +static int _max96714_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + static const struct v4l2_mbus_framefmt format = { + .width = 1280, + .height = 1080, + .code = MEDIA_BUS_FMT_Y8_1X8, + .field = V4L2_FIELD_NONE, + }; + int ret; + + /* + * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until + * frame desc is made dynamically allocated. + */ + if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX) + return -EINVAL; + + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + if (ret) + return ret; + + return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); +} + +static int max96714_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + struct max96714_priv *priv = sd_to_max96714(sd); + + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams) + return -EBUSY; + + return _max96714_set_routing(sd, state, which, routing); +} + +static int max96714_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route routes[] = { + { + .sink_pad = MAX96714_PAD_SINK, + .sink_stream = 0, + .source_pad = MAX96714_PAD_SOURCE, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + } + }; + struct v4l2_subdev_krouting routing = { + .num_routes = ARRAY_SIZE(routes), + .routes = routes, + }; + + return _max96714_set_routing(sd, state, V4L2_SUBDEV_FORMAT_ACTIVE, + &routing); +} + +static const struct v4l2_subdev_pad_ops max96714_pad_ops = { + .enable_streams = max96714_enable_streams, + .disable_streams = max96714_disable_streams, + + .set_routing = max96714_set_routing, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = max96714_set_fmt, +}; + +static bool max96714_link_locked(struct max96714_priv *priv) +{ + u64 val = 0; + + cci_read(priv->regmap, MAX96714_LINK_LOCK, &val, NULL); + + return val & MAX96714_LINK_LOCK_BIT; +} + +static void max96714_link_status(struct max96714_priv *priv) +{ + struct device *dev = &priv->client->dev; + + dev_info(dev, "Link locked:%d\n", max96714_link_locked(priv)); +} + +static bool max96714_pipe_locked(struct max96714_priv *priv) +{ + u64 val; + + cci_read(priv->regmap, MAX96714_VIDEO_RX8, &val, NULL); + + return val & MAX96714_VID_LOCK; +} + +static void max96714_pipe_status(struct max96714_priv *priv) +{ + struct device *dev = &priv->client->dev; + + dev_info(dev, "Pipe vidlock:%d\n", max96714_pipe_locked(priv)); +} + +static void max96714_csi_status(struct max96714_priv *priv) +{ + struct device *dev = &priv->client->dev; + u64 freq = 0; + + cci_read(priv->regmap, MAX96714_BACKTOP25, &freq, NULL); + freq = FIELD_GET(CSI_DPLL_FREQ_MASK, freq); + + dev_info(dev, "CSI controller DPLL freq:%u00MHz CSIPHY enabled:%d\n", + (u8)freq, max96714_tx_port_enabled(priv)); +} + +static int max96714_log_status(struct v4l2_subdev *sd) +{ + struct max96714_priv *priv = sd_to_max96714(sd); + struct device *dev = &priv->client->dev; + + dev_info(dev, "Deserializer: max96714\n"); + + max96714_link_status(priv); + max96714_pipe_status(priv); + max96714_csi_status(priv); + + return 0; +} + +static const struct v4l2_subdev_core_ops max96714_subdev_core_ops = { + .log_status = max96714_log_status, +}; + +static const struct v4l2_subdev_video_ops max96714_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_internal_ops max96714_internal_ops = { + .init_state = max96714_init_state, +}; + +static const struct v4l2_subdev_ops max96714_subdev_ops = { + .video = &max96714_video_ops, + .core = &max96714_subdev_core_ops, + .pad = &max96714_pad_ops, +}; + +static const struct media_entity_operations max96714_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int max96714_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_connection *asd) +{ + struct max96714_priv *priv = sd_to_max96714(notifier->sd); + struct device *dev = &priv->client->dev; + int ret; + + ret = media_entity_get_fwnode_pad(&subdev->entity, + priv->rxport.source.ep_fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(dev, "Failed to find pad for %s\n", subdev->name); + return ret; + } + + priv->rxport.source.sd = subdev; + priv->rxport.source.pad = ret; + + ret = media_create_pad_link(&priv->rxport.source.sd->entity, + priv->rxport.source.pad, &priv->sd.entity, + MAX96714_PAD_SINK, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(dev, "Unable to link %s:%u -> %s:%u\n", + priv->rxport.source.sd->name, priv->rxport.source.pad, + priv->sd.name, MAX96714_PAD_SINK); + return ret; + } + + return 0; +} + +static const struct v4l2_async_notifier_operations max96714_notify_ops = { + .bound = max96714_notify_bound, +}; + +static int max96714_v4l2_notifier_register(struct max96714_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct max96714_rxport *rxport = &priv->rxport; + struct v4l2_async_connection *asd; + int ret; + + if (!rxport->source.ep_fwnode) + return 0; + + v4l2_async_subdev_nf_init(&priv->notifier, &priv->sd); + + asd = v4l2_async_nf_add_fwnode(&priv->notifier, + rxport->source.ep_fwnode, + struct v4l2_async_connection); + if (IS_ERR(asd)) { + dev_err(dev, "Failed to add subdev: %pe", asd); + v4l2_async_nf_cleanup(&priv->notifier); + return PTR_ERR(asd); + } + + priv->notifier.ops = &max96714_notify_ops; + + ret = v4l2_async_nf_register(&priv->notifier); + if (ret) { + dev_err(dev, "Failed to register subdev_notifier"); + v4l2_async_nf_cleanup(&priv->notifier); + return ret; + } + + return 0; +} + +static void max96714_v4l2_notifier_unregister(struct max96714_priv *priv) +{ + v4l2_async_nf_unregister(&priv->notifier); + v4l2_async_nf_cleanup(&priv->notifier); +} + +static int max96714_create_subdev(struct max96714_priv *priv) +{ + struct device *dev = &priv->client->dev; + int ret; + + v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96714_subdev_ops); + priv->sd.internal_ops = &max96714_internal_ops; + + v4l2_ctrl_handler_init(&priv->ctrl_handler, 1); + priv->sd.ctrl_handler = &priv->ctrl_handler; + + v4l2_ctrl_new_int_menu(&priv->ctrl_handler, NULL, V4L2_CID_LINK_FREQ, + 0, 0, &priv->tx_link_freq); + if (priv->ctrl_handler.error) { + ret = priv->ctrl_handler.error; + goto err_free_ctrl; + } + + v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler, + &max96714_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(max96714_test_pattern) - 1, + 0, 0, max96714_test_pattern); + if (priv->ctrl_handler.error) { + ret = priv->ctrl_handler.error; + goto err_free_ctrl; + } + + priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; + priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + priv->sd.entity.ops = &max96714_entity_ops; + + priv->pads[MAX96714_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + priv->pads[MAX96714_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&priv->sd.entity, + MAX96714_NPORTS, + priv->pads); + if (ret) + goto err_free_ctrl; + + priv->sd.state_lock = priv->sd.ctrl_handler->lock; + + ret = v4l2_subdev_init_finalize(&priv->sd); + if (ret) + goto err_entity_cleanup; + + ret = max96714_v4l2_notifier_register(priv); + if (ret) { + dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret); + goto err_subdev_cleanup; + } + + ret = v4l2_async_register_subdev(&priv->sd); + if (ret) { + dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret); + goto err_unreg_notif; + } + + return 0; + +err_unreg_notif: + max96714_v4l2_notifier_unregister(priv); +err_subdev_cleanup: + v4l2_subdev_cleanup(&priv->sd); +err_entity_cleanup: + media_entity_cleanup(&priv->sd.entity); +err_free_ctrl: + v4l2_ctrl_handler_free(&priv->ctrl_handler); + + return ret; +}; + +static void max96714_destroy_subdev(struct max96714_priv *priv) +{ + max96714_v4l2_notifier_unregister(priv); + v4l2_async_unregister_subdev(&priv->sd); + + v4l2_subdev_cleanup(&priv->sd); + + media_entity_cleanup(&priv->sd.entity); + v4l2_ctrl_handler_free(&priv->ctrl_handler); +} + +static int max96714_i2c_mux_select(struct i2c_mux_core *mux, u32 chan) +{ + return 0; +} + +static int max96714_i2c_mux_init(struct max96714_priv *priv) +{ + priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev, + 1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE, + max96714_i2c_mux_select, NULL); + if (!priv->mux) + return -ENOMEM; + + return i2c_mux_add_adapter(priv->mux, 0, 0, 0); +} + +static int max96714_init_tx_port(struct max96714_priv *priv) +{ + struct v4l2_mbus_config_mipi_csi2 *mipi; + unsigned long lanes_used = 0; + unsigned int val, lane; + int ret; + + ret = max96714_disable_tx_port(priv); + + mipi = &priv->mipi_csi2; + val = div_u64(priv->tx_link_freq * 2, MHZ(100)); + + cci_update_bits(priv->regmap, MAX96714_BACKTOP25, + CSI_DPLL_FREQ_MASK, val, &ret); + + val = FIELD_PREP(MAX96714_CSI2_LANE_CNT_MASK, mipi->num_data_lanes - 1); + cci_update_bits(priv->regmap, MAX96714_MIPI_LANE_CNT, + MAX96714_CSI2_LANE_CNT_MASK, val, &ret); + + /* lanes polarity */ + val = 0; + for (lane = 0; lane < mipi->num_data_lanes + 1; lane++) { + if (!mipi->lane_polarities[lane]) + continue; + if (lane == 0) + /* clock lane */ + val |= BIT(5); + else if (lane < 3) + /* Lane D0 and D1 */ + val |= BIT(lane - 1); + else + /* D2 and D3 */ + val |= BIT(lane); + } + + cci_update_bits(priv->regmap, MAX96714_MIPI_POLARITY, + MAX96714_MIPI_POLARITY_MASK, val, &ret); + + /* lanes mapping */ + val = 0; + for (lane = 0; lane < mipi->num_data_lanes; lane++) { + val |= (mipi->data_lanes[lane] - 1) << (lane * 2); + lanes_used |= BIT(mipi->data_lanes[lane] - 1); + } + + /* Unused lanes need to be mapped as well to not have + * the same lanes mapped twice. + */ + for (; lane < 4; lane++) { + unsigned int idx = find_first_zero_bit(&lanes_used, 4); + + val |= idx << (lane * 2); + lanes_used |= BIT(idx); + } + + return cci_write(priv->regmap, MAX96714_MIPI_LANE_MAP, val, &ret); +} + +static int max96714_rxport_enable_poc(struct max96714_priv *priv) +{ + struct max96714_rxport *rxport = &priv->rxport; + + if (!rxport->poc) + return 0; + + return regulator_enable(rxport->poc); +} + +static int max96714_rxport_disable_poc(struct max96714_priv *priv) +{ + struct max96714_rxport *rxport = &priv->rxport; + + if (!rxport->poc) + return 0; + + return regulator_disable(rxport->poc); +} + +static int max96714_parse_dt_txport(struct max96714_priv *priv) +{ + struct device *dev = &priv->client->dev; + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + struct fwnode_handle *ep_fwnode; + u32 num_data_lanes; + int ret; + + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + MAX96714_PAD_SOURCE, 0, 0); + if (!ep_fwnode) + return -EINVAL; + + ret = v4l2_fwnode_endpoint_alloc_parse(ep_fwnode, &vep); + fwnode_handle_put(ep_fwnode); + if (ret) { + dev_err(dev, "tx: failed to parse endpoint data\n"); + return -EINVAL; + } + + if (vep.nr_of_link_frequencies != 1) { + ret = -EINVAL; + goto err_free_vep; + } + + priv->tx_link_freq = vep.link_frequencies[0]; + /* Min 50MHz, Max 1250MHz, 50MHz step */ + if (priv->tx_link_freq < MHZ(50) || priv->tx_link_freq > MHZ(1250) || + (u32)priv->tx_link_freq % MHZ(50)) { + dev_err(dev, "tx: invalid link frequency\n"); + ret = -EINVAL; + goto err_free_vep; + } + + num_data_lanes = vep.bus.mipi_csi2.num_data_lanes; + if (num_data_lanes < 1 || num_data_lanes > 4) { + dev_err(dev, + "tx: invalid number of data lanes must be 1 to 4\n"); + ret = -EINVAL; + goto err_free_vep; + } + + memcpy(&priv->mipi_csi2, &vep.bus.mipi_csi2, sizeof(priv->mipi_csi2)); + +err_free_vep: + v4l2_fwnode_endpoint_free(&vep); + + return ret; +}; + +static int max96714_parse_dt_rxport(struct max96714_priv *priv) +{ + static const char *poc_name = "port0-poc"; + struct max96714_rxport *rxport = &priv->rxport; + struct device *dev = &priv->client->dev; + struct fwnode_handle *ep_fwnode; + int ret; + + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + MAX96714_PAD_SINK, 0, 0); + if (!ep_fwnode) + return -ENOENT; + + rxport->source.ep_fwnode = fwnode_graph_get_remote_endpoint(ep_fwnode); + fwnode_handle_put(ep_fwnode); + + if (!rxport->source.ep_fwnode) { + dev_err(dev, "rx: no remote endpoint\n"); + return -EINVAL; + } + + rxport->poc = devm_regulator_get_optional(dev, poc_name); + if (IS_ERR(rxport->poc)) { + ret = PTR_ERR(rxport->poc); + if (ret == -ENODEV) { + rxport->poc = NULL; + } else { + dev_err(dev, "rx: failed to get POC supply: %d\n", ret); + goto err_put_source_ep_fwnode; + } + } + + return 0; + +err_put_source_ep_fwnode: + fwnode_handle_put(rxport->source.ep_fwnode); + return ret; +} + +static int max96714_parse_dt(struct max96714_priv *priv) +{ + int ret; + + ret = max96714_parse_dt_txport(priv); + if (ret) + return ret; + + ret = max96714_parse_dt_rxport(priv); + /* The deserializer can create a test pattern even if the + * rx port is not connected to a serializer. + */ + if (ret && ret == -ENOENT) + ret = 0; + + return ret; +} + +static int max96714_enable_core_hw(struct max96714_priv *priv) +{ + struct device *dev = &priv->client->dev; + u64 val; + int ret; + + if (priv->pd_gpio) { + /* wait min 2 ms for reset to complete */ + gpiod_set_value_cansleep(priv->pd_gpio, 1); + fsleep(2000); + gpiod_set_value_cansleep(priv->pd_gpio, 0); + /* wait min 2 ms for power up to finish */ + fsleep(2000); + } + + ret = cci_read(priv->regmap, MAX96714_REG13, &val, NULL); + if (ret) { + dev_err_probe(dev, ret, "Cannot read first register, abort\n"); + goto err_pd_gpio; + } + + if (val != MAX96714_DEVICE_ID && val != MAX96714F_DEVICE_ID) { + dev_err(dev, "Unsupported device id expected %x got %x\n", + MAX96714F_DEVICE_ID, (u8)val); + ret = -EOPNOTSUPP; + goto err_pd_gpio; + } + + ret = cci_read(priv->regmap, MAX96714_DEV_REV, &val, NULL); + if (ret) + goto err_pd_gpio; + + dev_dbg(dev, "Found %x (rev %lx)\n", MAX96714F_DEVICE_ID, + (u8)val & MAX96714_DEV_REV_MASK); + + ret = cci_read(priv->regmap, MAX96714_MIPI_TX52, &val, NULL); + if (ret) + goto err_pd_gpio; + + if (!(val & MAX96714_TUN_EN)) { + dev_err(dev, "Only supporting tunnel mode"); + ret = -EOPNOTSUPP; + goto err_pd_gpio; + } + + return 0; + +err_pd_gpio: + gpiod_set_value_cansleep(priv->pd_gpio, 1); + return ret; +} + +static void max96714_disable_core_hw(struct max96714_priv *priv) +{ + gpiod_set_value_cansleep(priv->pd_gpio, 1); +} + +static int max96714_get_hw_resources(struct max96714_priv *priv) +{ + struct device *dev = &priv->client->dev; + + priv->regmap = devm_cci_regmap_init_i2c(priv->client, 16); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->pd_gpio = + devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH); + if (IS_ERR(priv->pd_gpio)) + return dev_err_probe(dev, PTR_ERR(priv->pd_gpio), + "Cannot get powerdown GPIO\n"); + return 0; +} + +static int max96714_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct max96714_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + + ret = max96714_get_hw_resources(priv); + if (ret) + return ret; + + ret = max96714_enable_core_hw(priv); + if (ret) + return ret; + + ret = max96714_parse_dt(priv); + if (ret) + goto err_disable_core_hw; + + max96714_init_tx_port(priv); + + ret = max96714_rxport_enable_poc(priv); + if (ret) + goto err_free_ports; + + ret = max96714_i2c_mux_init(priv); + if (ret) + goto err_disable_poc; + + ret = max96714_create_subdev(priv); + if (ret) + goto err_del_mux; + + return 0; + +err_del_mux: + i2c_mux_del_adapters(priv->mux); +err_disable_poc: + max96714_rxport_disable_poc(priv); +err_free_ports: + fwnode_handle_put(priv->rxport.source.ep_fwnode); +err_disable_core_hw: + max96714_disable_core_hw(priv); + + return ret; +} + +static void max96714_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct max96714_priv *priv = sd_to_max96714(sd); + + max96714_destroy_subdev(priv); + i2c_mux_del_adapters(priv->mux); + max96714_rxport_disable_poc(priv); + fwnode_handle_put(priv->rxport.source.ep_fwnode); + max96714_disable_core_hw(priv); + gpiod_set_value_cansleep(priv->pd_gpio, 1); +} + +static const struct of_device_id max96714_of_ids[] = { + { .compatible = "maxim,max96714f" }, + { } +}; +MODULE_DEVICE_TABLE(of, max96714_of_ids); + +static struct i2c_driver max96714_i2c_driver = { + .driver = { + .name = "max96714", + .of_match_table = max96714_of_ids, + }, + .probe = max96714_probe, + .remove = max96714_remove, +}; + +module_i2c_driver(max96714_i2c_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Maxim Integrated GMSL2 Deserializers Driver"); +MODULE_AUTHOR("Julien Massot ");