Devicetree
 help / color / mirror / Atom feed
* [PATCH 0/2] Add M-PHY support for NVIDIA Tegra264
@ 2026-09-09  9:43 Kartik Rajput
  2026-09-09  9:43 ` [PATCH 1/2] dt-bindings: phy: Add nvidia,tegra264-mphy Kartik Rajput
  2026-09-09  9:43 ` [PATCH 2/2] phy: tegra: Add Tegra264 MPHY driver Kartik Rajput
  0 siblings, 2 replies; 5+ messages in thread
From: Kartik Rajput @ 2026-09-09  9:43 UTC (permalink / raw)
  To: Vinod Koul, Neil Armstrong, Manivannan Sadhasivam, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
	Jonathan Hunter, Philipp Zabel
  Cc: Thierry Reding, linux-phy, devicetree, linux-tegra, linux-kernel,
	Kartik Rajput

Add MIPI M-PHY support for NVIDIA Tegra264 SoCs. The M-PHY is a hardware
block with two lanes, l0 and l1, used by the Tegra264 UFS host
controller.

Signed-off-by: Kartik Rajput <kkartik@nvidia.com>
---
Kartik Rajput (2):
      dt-bindings: phy: Add nvidia,tegra264-mphy
      phy: tegra: Add Tegra264 MPHY driver

 .../bindings/phy/nvidia,tegra264-mphy.yaml         | 108 +++++
 drivers/phy/tegra/Kconfig                          |  11 +
 drivers/phy/tegra/Makefile                         |   1 +
 drivers/phy/tegra/phy-tegra-mphy.c                 | 459 +++++++++++++++++++++
 include/dt-bindings/phy/nvidia,tegra264-mphy.h     |  12 +
 5 files changed, 591 insertions(+)
---
base-commit: a9d7ced84989ec05be09b4b8428759ef60450a0f
change-id: 20260909-tegra264-mphy-f3edf6fa4321

Best regards,
--  
Kartik Rajput <kkartik@nvidia.com>


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

* [PATCH 1/2] dt-bindings: phy: Add nvidia,tegra264-mphy
  2026-09-09  9:43 [PATCH 0/2] Add M-PHY support for NVIDIA Tegra264 Kartik Rajput
@ 2026-09-09  9:43 ` Kartik Rajput
  2026-09-13  8:38   ` Krzysztof Kozlowski
  2026-09-09  9:43 ` [PATCH 2/2] phy: tegra: Add Tegra264 MPHY driver Kartik Rajput
  1 sibling, 1 reply; 5+ messages in thread
From: Kartik Rajput @ 2026-09-09  9:43 UTC (permalink / raw)
  To: Vinod Koul, Neil Armstrong, Manivannan Sadhasivam, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
	Jonathan Hunter, Philipp Zabel
  Cc: Thierry Reding, linux-phy, devicetree, linux-tegra, linux-kernel,
	Kartik Rajput

Add binding for the MIPI M-PHY on NVIDIA Tegra264 SoCs. The M-PHY is
a hardware block with two lanes, l0 and l1, used by the Tegra264 UFS
host controller.

Signed-off-by: Kartik Rajput <kkartik@nvidia.com>
---
 .../bindings/phy/nvidia,tegra264-mphy.yaml         | 108 +++++++++++++++++++++
 include/dt-bindings/phy/nvidia,tegra264-mphy.h     |  12 +++
 2 files changed, 120 insertions(+)

diff --git a/Documentation/devicetree/bindings/phy/nvidia,tegra264-mphy.yaml b/Documentation/devicetree/bindings/phy/nvidia,tegra264-mphy.yaml
new file mode 100644
index 000000000000..e2fb368e4a39
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/nvidia,tegra264-mphy.yaml
@@ -0,0 +1,108 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/phy/nvidia,tegra264-mphy.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NVIDIA Tegra264 MIPI M-PHY
+
+maintainers:
+  - Kartik Rajput <kkartik@nvidia.com>
+  - Thierry Reding <treding@nvidia.com>
+
+properties:
+  compatible:
+    const: nvidia,tegra264-mphy
+
+  reg:
+    items:
+      - description: Lane 0 register window
+      - description: Lane 1 register window
+
+  reg-names:
+    items:
+      - const: l0
+      - const: l1
+
+  clocks:
+    maxItems: 11
+
+  clock-names:
+    items:
+      - const: pll
+      - const: tx-ref
+      - const: l0-tx-sym
+      - const: l0-tx-ls
+      - const: l0-tx-hs
+      - const: l0-tx-fifo
+      - const: l0-rx-sym
+      - const: l0-rx-ana
+      - const: l0-rx-ls
+      - const: l0-rx-hs
+      - const: l1-rx-ana
+
+  resets:
+    maxItems: 5
+
+  reset-names:
+    items:
+      - const: clk-ctl
+      - const: l0-rx
+      - const: l0-tx
+      - const: l1-rx
+      - const: l1-tx
+
+  '#phy-cells':
+    description: |
+      The cell selects one M-PHY lane direction. Valid values are defined in
+      <dt-bindings/phy/nvidia,tegra264-mphy.h>: TEGRA_MPHY_L0_TX, TEGRA_MPHY_L0_RX,
+      TEGRA_MPHY_L1_TX and TEGRA_MPHY_L1_RX.
+    const: 1
+
+required:
+  - compatible
+  - reg
+  - reg-names
+  - clocks
+  - clock-names
+  - resets
+  - reset-names
+  - '#phy-cells'
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/nvidia,tegra264.h>
+    #include <dt-bindings/reset/nvidia,tegra264.h>
+    #include <dt-bindings/phy/nvidia,tegra264-mphy.h>
+
+    mphy: phy@b910000 {
+        compatible = "nvidia,tegra264-mphy";
+        reg = <0xb910000 0x10000>,
+              <0xb920000 0x10000>;
+        reg-names = "l0", "l1";
+        clocks = <&bpmp TEGRA264_CLK_MPHY_CORE_PLL_FIXED>,
+                 <&bpmp TEGRA264_CLK_MPHY_TX_1MHZ_REF>,
+                 <&bpmp TEGRA264_CLK_MPHY_L0_TX_SYMB>,
+                 <&bpmp TEGRA264_CLK_MPHY_L0_TX_LS_3XBIT>,
+                 <&bpmp TEGRA264_CLK_MPHY_L0_TX_HS_SYMB_DIV>,
+                 <&bpmp TEGRA264_CLK_MPHY_L0_UPHY_TX_FIFO>,
+                 <&bpmp TEGRA264_CLK_MPHY_L0_RX_SYMB>,
+                 <&bpmp TEGRA264_CLK_MPHY_L0_RX_ANA>,
+                 <&bpmp TEGRA264_CLK_MPHY_L0_RX_LS_BIT>,
+                 <&bpmp TEGRA264_CLK_MPHY_L0_RX_HS_SYMB_DIV>,
+                 <&bpmp TEGRA264_CLK_MPHY_L1_RX_ANA>;
+        clock-names = "pll", "tx-ref",
+                      "l0-tx-sym", "l0-tx-ls", "l0-tx-hs", "l0-tx-fifo",
+                      "l0-rx-sym", "l0-rx-ana", "l0-rx-ls", "l0-rx-hs",
+                      "l1-rx-ana";
+        resets = <&bpmp TEGRA264_RESET_MPHY_CLK_CTL>,
+                 <&bpmp TEGRA264_RESET_MPHY_L0_RX>,
+                 <&bpmp TEGRA264_RESET_MPHY_L0_TX>,
+                 <&bpmp TEGRA264_RESET_MPHY_L1_RX>,
+                 <&bpmp TEGRA264_RESET_MPHY_L1_TX>;
+        reset-names = "clk-ctl", "l0-rx", "l0-tx", "l1-rx", "l1-tx";
+        #phy-cells = <1>;
+    };
+...
diff --git a/include/dt-bindings/phy/nvidia,tegra264-mphy.h b/include/dt-bindings/phy/nvidia,tegra264-mphy.h
new file mode 100644
index 000000000000..e9e253cdf9ae
--- /dev/null
+++ b/include/dt-bindings/phy/nvidia,tegra264-mphy.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause */
+/* Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. */
+
+#ifndef DT_BINDINGS_PHY_NVIDIA_TEGRA264_MPHY_H
+#define DT_BINDINGS_PHY_NVIDIA_TEGRA264_MPHY_H
+
+#define TEGRA_MPHY_L0_TX	0
+#define TEGRA_MPHY_L0_RX	1
+#define TEGRA_MPHY_L1_TX	2
+#define TEGRA_MPHY_L1_RX	3
+
+#endif /* DT_BINDINGS_PHY_NVIDIA_TEGRA264_MPHY_H */

-- 
2.43.0


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

* [PATCH 2/2] phy: tegra: Add Tegra264 MPHY driver
  2026-09-09  9:43 [PATCH 0/2] Add M-PHY support for NVIDIA Tegra264 Kartik Rajput
  2026-09-09  9:43 ` [PATCH 1/2] dt-bindings: phy: Add nvidia,tegra264-mphy Kartik Rajput
@ 2026-09-09  9:43 ` Kartik Rajput
  2026-09-09  9:54   ` sashiko-bot
  1 sibling, 1 reply; 5+ messages in thread
From: Kartik Rajput @ 2026-09-09  9:43 UTC (permalink / raw)
  To: Vinod Koul, Neil Armstrong, Manivannan Sadhasivam, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
	Jonathan Hunter, Philipp Zabel
  Cc: Thierry Reding, linux-phy, devicetree, linux-tegra, linux-kernel,
	Kartik Rajput

Add a driver for the MIPI M-PHY found on NVIDIA Tegra264 SoCs. The
M-PHY has two lanes, each with a transmit and a receive direction,
exposed as four PHYs to the UFS host controller.

Co-developed-by: Thierry Reding <treding@nvidia.com>
Signed-off-by: Thierry Reding <treding@nvidia.com>
Signed-off-by: Kartik Rajput <kkartik@nvidia.com>
---
 drivers/phy/tegra/Kconfig          |  11 +
 drivers/phy/tegra/Makefile         |   1 +
 drivers/phy/tegra/phy-tegra-mphy.c | 459 +++++++++++++++++++++++++++++++++++++
 3 files changed, 471 insertions(+)

diff --git a/drivers/phy/tegra/Kconfig b/drivers/phy/tegra/Kconfig
index f0734415fc94..b5ed5bc0c8ef 100644
--- a/drivers/phy/tegra/Kconfig
+++ b/drivers/phy/tegra/Kconfig
@@ -18,3 +18,14 @@ config PHY_TEGRA194_P2U
 	help
 	  Enable this to support the P2U (PIPE to UPHY) that is part of Tegra 19x
 	  and 234 SOCs.
+
+config PHY_TEGRA_MPHY
+	tristate "NVIDIA Tegra264 MPHY driver"
+	depends on ARCH_TEGRA_264_SOC || COMPILE_TEST
+	select GENERIC_PHY
+	help
+	  Enable this to support the MIPI M-PHY on NVIDIA Tegra264 SoCs.
+	  The M-PHY provides the physical layer for the on-die UFS host
+	  controller. The driver manages the shared clock-control block
+	  and per-lane resets, exposing four sub-phys (L0-TX, L0-RX,
+	  L1-TX, L1-RX) consumed by the UFS host driver.
diff --git a/drivers/phy/tegra/Makefile b/drivers/phy/tegra/Makefile
index eeeea72de117..3ab153264a07 100644
--- a/drivers/phy/tegra/Makefile
+++ b/drivers/phy/tegra/Makefile
@@ -9,3 +9,4 @@ phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_186_SOC) += xusb-tegra186.o
 phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_194_SOC) += xusb-tegra186.o
 phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_234_SOC) += xusb-tegra186.o
 obj-$(CONFIG_PHY_TEGRA194_P2U) += phy-tegra194-p2u.o
+obj-$(CONFIG_PHY_TEGRA_MPHY) += phy-tegra-mphy.o
diff --git a/drivers/phy/tegra/phy-tegra-mphy.c b/drivers/phy/tegra/phy-tegra-mphy.c
new file mode 100644
index 000000000000..01cafe1c7477
--- /dev/null
+++ b/drivers/phy/tegra/phy-tegra-mphy.c
@@ -0,0 +1,459 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+// NVIDIA Tegra264 MPHY driver.
+
+#include <dt-bindings/phy/nvidia,tegra264-mphy.h>
+#include <linux/clk.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#define MPHY_GO_BIT				BIT(0)
+
+#define MPHY_RX_APB_CAPABILITY_88_8B		0x88
+#define RX_HS_G1_SYNC_LENGTH_CAPABILITY(x)	(((x) & 0x3f) << 24)
+
+#define MPHY_RX_APB_CAPABILITY_94_97		0x94
+#define RX_HS_G3_SYNC_LENGTH_CAPABILITY(x)	(((x) & 0x3f) << 8)
+#define RX_HS_G2_SYNC_LENGTH_CAPABILITY(x)	(((x) & 0x3f) << 0)
+
+#define MPHY_TX_APB_VENDOR0			0x100
+#define MPHY_TX_APB_VENDOR2			0x108
+#define TX_CAL_DONE				BIT(19)
+#define TX_CAL_EN				BIT(15)
+
+#define MPHY_RX_APB_VENDOR2			0x184
+#define RX_CAL_DONE				BIT(19)
+#define RX_CAL_EN				BIT(15)
+
+#define MPHY_RX_APB_VENDOR3			0x188
+#define RX_MPHY2UPHY_IF_OVR_CTRL		BIT(26)
+
+#define MPHY_RX_APB_VENDOR3B			0x220
+#define MPHY_RX_APB_VENDOR49			0x254
+
+#define MPHY_EQ_TIMEOUT				0xffffffff
+#define MPHY_PWR_CHANGE_CLK_BOOST		0x0017
+
+#define MPHY_TX_OFFSET				0x1000
+#define MPHY_RX_OFFSET				0x2000
+
+struct tegra_mphy_lane {
+	void __iomem *regs;
+	void __iomem *tx_regs;
+	void __iomem *rx_regs;
+
+	struct reset_control *rst_rx;
+	struct reset_control *rst_tx;
+
+	struct phy *tx;
+	struct phy *rx;
+};
+
+struct tegra_mphy {
+	struct device *dev;
+
+	struct tegra_mphy_lane l0;
+	struct tegra_mphy_lane l1;
+
+	unsigned int num_clks;
+	struct clk_bulk_data *clks;
+
+	struct reset_control *rst_clk_ctl;
+
+	unsigned int power_count;
+};
+
+static int tegra_mphy_get_clocks(struct tegra_mphy *mphy)
+{
+	struct device *dev = mphy->dev;
+	int num_clks;
+
+	num_clks = devm_clk_bulk_get_all(dev, &mphy->clks);
+	if (num_clks < 0)
+		return dev_err_probe(dev, num_clks, "failed to get clocks\n");
+
+	mphy->num_clks = num_clks;
+
+	return 0;
+}
+
+static int tegra_mphy_get_resets(struct tegra_mphy *mphy)
+{
+	struct device *dev = mphy->dev;
+
+	mphy->rst_clk_ctl = devm_reset_control_get_exclusive(dev, "clk-ctl");
+	if (IS_ERR(mphy->rst_clk_ctl))
+		return dev_err_probe(dev, PTR_ERR(mphy->rst_clk_ctl),
+				     "failed to get clk-ctl reset\n");
+
+	mphy->l0.rst_rx = devm_reset_control_get_exclusive(dev, "l0-rx");
+	if (IS_ERR(mphy->l0.rst_rx))
+		return dev_err_probe(dev, PTR_ERR(mphy->l0.rst_rx), "failed to get l0-rx reset\n");
+
+	mphy->l0.rst_tx = devm_reset_control_get_exclusive(dev, "l0-tx");
+	if (IS_ERR(mphy->l0.rst_tx))
+		return dev_err_probe(dev, PTR_ERR(mphy->l0.rst_tx), "failed to get l0-tx reset\n");
+
+	mphy->l1.rst_rx = devm_reset_control_get_exclusive(dev, "l1-rx");
+	if (IS_ERR(mphy->l1.rst_rx))
+		return dev_err_probe(dev, PTR_ERR(mphy->l1.rst_rx), "failed to get l1-rx reset\n");
+
+	mphy->l1.rst_tx = devm_reset_control_get_exclusive(dev, "l1-tx");
+	if (IS_ERR(mphy->l1.rst_tx))
+		return dev_err_probe(dev, PTR_ERR(mphy->l1.rst_tx), "failed to get l1-tx reset\n");
+
+	return 0;
+}
+
+static int tegra_mphy_rx_write_kick_go(struct tegra_mphy_lane *lane, u32 offset, u32 value)
+{
+	u32 v;
+
+	writel(value, lane->rx_regs + offset);
+
+	v = readl(lane->rx_regs + MPHY_RX_APB_VENDOR2);
+	v |= MPHY_GO_BIT;
+	writel(v, lane->rx_regs + MPHY_RX_APB_VENDOR2);
+
+	return readl_poll_timeout(lane->rx_regs + MPHY_RX_APB_VENDOR2, v,
+				  (v & MPHY_GO_BIT) == 0, 25, 5000);
+}
+
+static int tegra_mphy_rx_power_on(struct phy *phy)
+{
+	struct tegra_mphy *mphy = dev_get_drvdata(phy->dev.parent);
+	struct tegra_mphy_lane *lane = phy_get_drvdata(phy);
+	u32 value;
+	int err;
+
+	if (mphy->power_count++ == 0) {
+		err = clk_bulk_prepare_enable(mphy->num_clks, mphy->clks);
+		if (err) {
+			mphy->power_count--;
+			dev_err(&phy->dev, "failed to enable clocks: %d\n", err);
+			return err;
+		}
+		reset_control_deassert(mphy->rst_clk_ctl);
+	}
+
+	reset_control_deassert(lane->rst_rx);
+	reset_control_deassert(lane->rst_tx);
+
+	err = tegra_mphy_rx_write_kick_go(lane, MPHY_RX_APB_VENDOR3B, MPHY_EQ_TIMEOUT);
+	if (err) {
+		dev_err(&phy->dev, "eq_timeout programming failed: %d\n", err);
+		goto err_reset;
+	}
+
+	err = tegra_mphy_rx_write_kick_go(lane, MPHY_RX_APB_VENDOR49, MPHY_PWR_CHANGE_CLK_BOOST);
+	if (err) {
+		dev_err(&phy->dev, "pwr_change_clk_boost programming failed: %d\n", err);
+		goto err_reset;
+	}
+
+	value = readl(lane->regs + MPHY_RX_APB_CAPABILITY_88_8B);
+	value &= ~RX_HS_G1_SYNC_LENGTH_CAPABILITY(~0);
+	value |= RX_HS_G1_SYNC_LENGTH_CAPABILITY(0xf);
+	writel(value, lane->regs + MPHY_RX_APB_CAPABILITY_88_8B);
+
+	value = readl(lane->regs + MPHY_RX_APB_CAPABILITY_94_97);
+	value &= ~RX_HS_G3_SYNC_LENGTH_CAPABILITY(~0);
+	value |= RX_HS_G3_SYNC_LENGTH_CAPABILITY(0xf);
+	value &= ~RX_HS_G2_SYNC_LENGTH_CAPABILITY(~0);
+	value |= RX_HS_G2_SYNC_LENGTH_CAPABILITY(0xf);
+	writel(value, lane->regs + MPHY_RX_APB_CAPABILITY_94_97);
+
+	value = readl(lane->rx_regs + MPHY_RX_APB_VENDOR3);
+	value |= RX_MPHY2UPHY_IF_OVR_CTRL;
+	writel(value, lane->rx_regs + MPHY_RX_APB_VENDOR3);
+
+	value = readl(lane->rx_regs + MPHY_RX_APB_VENDOR2);
+	value |= MPHY_GO_BIT;
+	writel(value, lane->rx_regs + MPHY_RX_APB_VENDOR2);
+
+	err = readl_poll_timeout(lane->rx_regs + MPHY_RX_APB_VENDOR2, value,
+				 (value & MPHY_GO_BIT) == 0, 25, 5000);
+	if (err) {
+		dev_err(&phy->dev, "RX cap update failed: %d\n", err);
+		goto err_reset;
+	}
+
+	return 0;
+
+err_reset:
+	reset_control_assert(lane->rst_rx);
+	reset_control_assert(lane->rst_tx);
+
+	if (--mphy->power_count == 0) {
+		reset_control_assert(mphy->rst_clk_ctl);
+		clk_bulk_disable_unprepare(mphy->num_clks, mphy->clks);
+	}
+
+	return err;
+}
+
+static int tegra_mphy_rx_power_off(struct phy *phy)
+{
+	struct tegra_mphy *mphy = dev_get_drvdata(phy->dev.parent);
+	struct tegra_mphy_lane *lane = phy_get_drvdata(phy);
+
+	if (WARN_ON(mphy->power_count == 0))
+		return -EINVAL;
+
+	reset_control_assert(lane->rst_rx);
+	reset_control_assert(lane->rst_tx);
+
+	if (--mphy->power_count == 0)
+		clk_bulk_disable_unprepare(mphy->num_clks, mphy->clks);
+
+	return 0;
+}
+
+static int tegra_mphy_rx_configure(struct phy *phy, union phy_configure_opts *opts)
+{
+	struct tegra_mphy_lane *lane = phy_get_drvdata(phy);
+	u32 value;
+	int err;
+
+	value = readl(lane->rx_regs + MPHY_RX_APB_VENDOR2);
+	value |= RX_CAL_EN;
+	writel(value, lane->rx_regs + MPHY_RX_APB_VENDOR2);
+
+	value = readl(lane->rx_regs + MPHY_RX_APB_VENDOR2);
+	value |= MPHY_GO_BIT;
+	writel(value, lane->rx_regs + MPHY_RX_APB_VENDOR2);
+
+	err = readl_poll_timeout(lane->rx_regs + MPHY_RX_APB_VENDOR2, value,
+				 (value & MPHY_GO_BIT) == 0, 25, 5000);
+	if (err)
+		dev_err(&phy->dev, "failed to arm RX calibration: %d\n", err);
+
+	return err;
+}
+
+static int tegra_mphy_rx_calibrate(struct phy *phy)
+{
+	struct tegra_mphy_lane *lane = phy_get_drvdata(phy);
+	u32 value;
+	int err;
+
+	err = readl_poll_timeout(lane->rx_regs + MPHY_RX_APB_VENDOR2, value,
+				 (value & RX_CAL_DONE) != 0, 25, 100000);
+	if (err) {
+		dev_err(&phy->dev, "RX calibration timed out: %d\n", err);
+		return err;
+	}
+
+	value = readl(lane->rx_regs + MPHY_RX_APB_VENDOR2);
+	value &= ~RX_CAL_EN;
+	writel(value, lane->rx_regs + MPHY_RX_APB_VENDOR2);
+
+	value = readl(lane->rx_regs + MPHY_RX_APB_VENDOR2);
+	value |= MPHY_GO_BIT;
+	writel(value, lane->rx_regs + MPHY_RX_APB_VENDOR2);
+
+	err = readl_poll_timeout(lane->rx_regs + MPHY_RX_APB_VENDOR2, value,
+				 (value & MPHY_GO_BIT) == 0, 25, 5000);
+	if (err) {
+		dev_err(&phy->dev, "failed to clear RX calibration: %d\n", err);
+		return err;
+	}
+
+	err = readl_poll_timeout(lane->rx_regs + MPHY_RX_APB_VENDOR2, value,
+				 (value & RX_CAL_DONE) == 0, 25, 100000);
+	if (err)
+		dev_err(&phy->dev, "RX calibration failed to clear: %d\n", err);
+
+	return err;
+}
+
+static const struct phy_ops tegra_mphy_rx_ops = {
+	.power_on = tegra_mphy_rx_power_on,
+	.power_off = tegra_mphy_rx_power_off,
+	.configure = tegra_mphy_rx_configure,
+	.calibrate = tegra_mphy_rx_calibrate,
+};
+
+static int tegra_mphy_tx_configure(struct phy *phy, union phy_configure_opts *opts)
+{
+	struct tegra_mphy_lane *lane = phy_get_drvdata(phy);
+	u32 value;
+	int err;
+
+	value = readl(lane->tx_regs + MPHY_TX_APB_VENDOR2);
+	value |= TX_CAL_EN;
+	writel(value, lane->tx_regs + MPHY_TX_APB_VENDOR2);
+
+	value = readl(lane->tx_regs + MPHY_TX_APB_VENDOR0);
+	value |= MPHY_GO_BIT;
+	writel(value, lane->tx_regs + MPHY_TX_APB_VENDOR0);
+
+	err = readl_poll_timeout(lane->tx_regs + MPHY_TX_APB_VENDOR0, value,
+				 (value & MPHY_GO_BIT) == 0, 25, 5000);
+	if (err)
+		dev_err(&phy->dev, "failed to arm TX calibration: %d\n", err);
+
+	return err;
+}
+
+static int tegra_mphy_tx_calibrate(struct phy *phy)
+{
+	struct tegra_mphy_lane *lane = phy_get_drvdata(phy);
+	u32 value;
+	int err;
+
+	err = readl_poll_timeout(lane->tx_regs + MPHY_TX_APB_VENDOR2, value,
+				 (value & TX_CAL_DONE) != 0, 25, 50000);
+	if (err) {
+		dev_err(&phy->dev, "TX calibration timed out: %d\n", err);
+		return err;
+	}
+
+	value = readl(lane->tx_regs + MPHY_TX_APB_VENDOR2);
+	value &= ~TX_CAL_EN;
+	writel(value, lane->tx_regs + MPHY_TX_APB_VENDOR2);
+
+	value = readl(lane->tx_regs + MPHY_TX_APB_VENDOR0);
+	value |= MPHY_GO_BIT;
+	writel(value, lane->tx_regs + MPHY_TX_APB_VENDOR0);
+
+	err = readl_poll_timeout(lane->tx_regs + MPHY_TX_APB_VENDOR0, value,
+				 (value & MPHY_GO_BIT) == 0, 25, 5000);
+	if (err) {
+		dev_err(&phy->dev, "GO bit failed to clear: %d\n", err);
+		return err;
+	}
+
+	err = readl_poll_timeout(lane->tx_regs + MPHY_TX_APB_VENDOR2, value,
+				 (value & TX_CAL_DONE) == 0, 25, 50000);
+	if (err)
+		dev_err(&phy->dev, "TX calibration failed: %d\n", err);
+
+	return err;
+}
+
+static const struct phy_ops tegra_mphy_tx_ops = {
+	.configure = tegra_mphy_tx_configure,
+	.calibrate = tegra_mphy_tx_calibrate,
+};
+
+static struct phy *tegra_mphy_xlate(struct device *dev, const struct of_phandle_args *args)
+{
+	struct tegra_mphy *mphy = dev_get_drvdata(dev);
+
+	if (args->args_count != 1)
+		return ERR_PTR(-EINVAL);
+
+	switch (args->args[0]) {
+	case TEGRA_MPHY_L0_TX:
+		return mphy->l0.tx;
+	case TEGRA_MPHY_L0_RX:
+		return mphy->l0.rx;
+	case TEGRA_MPHY_L1_TX:
+		return mphy->l1.tx;
+	case TEGRA_MPHY_L1_RX:
+		return mphy->l1.rx;
+	default:
+		return ERR_PTR(-EINVAL);
+	}
+}
+
+static int tegra_mphy_create_phys(struct tegra_mphy *mphy)
+{
+	struct device *dev = mphy->dev;
+
+	mphy->l0.tx = devm_phy_create(dev, dev->of_node, &tegra_mphy_tx_ops);
+	if (IS_ERR(mphy->l0.tx))
+		return dev_err_probe(dev, PTR_ERR(mphy->l0.tx), "failed to create l0-tx PHY\n");
+	phy_set_drvdata(mphy->l0.tx, &mphy->l0);
+
+	mphy->l0.rx = devm_phy_create(dev, dev->of_node, &tegra_mphy_rx_ops);
+	if (IS_ERR(mphy->l0.rx))
+		return dev_err_probe(dev, PTR_ERR(mphy->l0.rx), "failed to create l0-rx PHY\n");
+	phy_set_drvdata(mphy->l0.rx, &mphy->l0);
+
+	mphy->l1.tx = devm_phy_create(dev, dev->of_node, &tegra_mphy_tx_ops);
+	if (IS_ERR(mphy->l1.tx))
+		return dev_err_probe(dev, PTR_ERR(mphy->l1.tx), "failed to create l1-tx PHY\n");
+	phy_set_drvdata(mphy->l1.tx, &mphy->l1);
+
+	mphy->l1.rx = devm_phy_create(dev, dev->of_node, &tegra_mphy_rx_ops);
+	if (IS_ERR(mphy->l1.rx))
+		return dev_err_probe(dev, PTR_ERR(mphy->l1.rx), "failed to create l1-rx PHY\n");
+	phy_set_drvdata(mphy->l1.rx, &mphy->l1);
+
+	return 0;
+}
+
+static int tegra_mphy_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct phy_provider *provider;
+	struct tegra_mphy *mphy;
+	int err;
+
+	mphy = devm_kzalloc(dev, sizeof(*mphy), GFP_KERNEL);
+	if (!mphy)
+		return -ENOMEM;
+
+	mphy->dev = dev;
+
+	mphy->l0.regs = devm_platform_ioremap_resource_byname(pdev, "l0");
+	if (IS_ERR(mphy->l0.regs))
+		return PTR_ERR(mphy->l0.regs);
+
+	mphy->l1.regs = devm_platform_ioremap_resource_byname(pdev, "l1");
+	if (IS_ERR(mphy->l1.regs))
+		return PTR_ERR(mphy->l1.regs);
+
+	mphy->l0.tx_regs = mphy->l0.regs + MPHY_TX_OFFSET;
+	mphy->l0.rx_regs = mphy->l0.regs + MPHY_RX_OFFSET;
+
+	mphy->l1.tx_regs = mphy->l1.regs + MPHY_TX_OFFSET;
+	mphy->l1.rx_regs = mphy->l1.regs + MPHY_RX_OFFSET;
+
+	err = tegra_mphy_get_clocks(mphy);
+	if (err)
+		return err;
+
+	err = tegra_mphy_get_resets(mphy);
+	if (err)
+		return err;
+
+	err = tegra_mphy_create_phys(mphy);
+	if (err)
+		return err;
+
+	platform_set_drvdata(pdev, mphy);
+
+	provider = devm_of_phy_provider_register(dev, tegra_mphy_xlate);
+	if (IS_ERR(provider))
+		return dev_err_probe(dev, PTR_ERR(provider),
+				     "failed to register PHY\n");
+
+	return 0;
+}
+
+static const struct of_device_id tegra_mphy_of_table[] = {
+	{ .compatible = "nvidia,tegra264-mphy" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, tegra_mphy_of_table);
+
+static struct platform_driver tegra_mphy_driver = {
+	.driver = {
+		.name = "tegra-mphy",
+		.of_match_table = tegra_mphy_of_table,
+	},
+	.probe = tegra_mphy_probe,
+};
+module_platform_driver(tegra_mphy_driver);
+
+MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
+MODULE_AUTHOR("Kartik Rajput <kkartik@nvidia.com>");
+MODULE_DESCRIPTION("NVIDIA Tegra MPHY driver");
+MODULE_LICENSE("GPL");

-- 
2.43.0


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

* Re: [PATCH 2/2] phy: tegra: Add Tegra264 MPHY driver
  2026-09-09  9:43 ` [PATCH 2/2] phy: tegra: Add Tegra264 MPHY driver Kartik Rajput
@ 2026-09-09  9:54   ` sashiko-bot
  0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-09-09  9:54 UTC (permalink / raw)
  To: Kartik Rajput
  Cc: robh, olteanv, neil.armstrong, linux-phy, conor+dt, devicetree,
	vkoul

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Data race on shared `mphy->power_count` and clock state across multiple PHY instances.
- [High] TX PHY lacks power management operations, creating a fragile API contract that risks system crashes.
- [High] Wrong base address pointer used for `MPHY_RX_APB_CAPABILITY` registers.
- [Medium] Missing reset assertion for `mphy->rst_clk_ctl` in the normal `tegra_mphy_rx_power_off` path.
--

commit bceb08380e03847a80ce77db39c3ff271cbccb0c
Author: Kartik Rajput <kkartik@nvidia.com>

phy: tegra: Add Tegra264 MPHY driver

Add a driver for the MIPI M-PHY found on NVIDIA Tegra264 SoCs. The
M-PHY has two lanes, each with a transmit and a receive direction,
exposed as four PHYs to the UFS host controller.

> diff --git a/drivers/phy/tegra/phy-tegra-mphy.c b/drivers/phy/tegra/phy-tegra-mphy.c
> index 0000000000000..01cafe1c7477f
> --- /dev/null
> +++ b/drivers/phy/tegra/phy-tegra-mphy.c

[ ... ]

> +static int tegra_mphy_rx_power_on(struct phy *phy)
> +{
> +	struct tegra_mphy *mphy = dev_get_drvdata(phy->dev.parent);
> +	struct tegra_mphy_lane *lane = phy_get_drvdata(phy);
> +	u32 value;
> +	int err;
> +
> +	if (mphy->power_count++ == 0) {
            ^^^^^^^^^^^^^^^^^^^^^^^

[Severity: High]
Since l0 and l1 are distinct PHY instances exposed to the consumer, can
concurrent phy_power_on() or phy_power_off() calls race on this shared
mphy->power_count?

The PHY framework's internal mutex only serializes operations on a per-PHY
basis. An unprotected read-modify-write here could lead to corrupted
reference counts, unbalanced clock states, and hardware faults.

> +		err = clk_bulk_prepare_enable(mphy->num_clks, mphy->clks);
> +		if (err) {
> +			mphy->power_count--;
> +			dev_err(&phy->dev, "failed to enable clocks: %d\n", err);
> +			return err;
> +		}
> +		reset_control_deassert(mphy->rst_clk_ctl);
> +	}
> +
> +	reset_control_deassert(lane->rst_rx);
> +	reset_control_deassert(lane->rst_tx);
                               ^^^^^^^^^^^^^^^^^^^^

[Severity: High]
Is it intentional that the RX power-on path handles the reset logic for the
independently exposed TX PHY?

The TX PHY currently lacks .power_on and .power_off callbacks and seems to
rely implicitly on the RX PHY to enable hardware clocks. If the TX PHY is
accessed via phy_configure() before the RX PHY is fully powered on, could
this result in unclocked memory-mapped I/O and trigger a synchronous
external abort?

> +
> +	err = tegra_mphy_rx_write_kick_go(lane, MPHY_RX_APB_VENDOR3B, MPHY_EQ_TIMEOUT);
> +	if (err) {
> +		dev_err(&phy->dev, "eq_timeout programming failed: %d\n", err);
> +		goto err_reset;
> +	}
> +
> +	err = tegra_mphy_rx_write_kick_go(lane, MPHY_RX_APB_VENDOR49, MPHY_PWR_CHANGE_CLK_BOOST);
> +	if (err) {
> +		dev_err(&phy->dev, "pwr_change_clk_boost programming failed: %d\n", err);
> +		goto err_reset;
> +	}
> +
> +	value = readl(lane->regs + MPHY_RX_APB_CAPABILITY_88_8B);
                      ^^^^^^^^^^

[Severity: High]
Should this use lane->rx_regs instead of lane->regs for offset application?

Using lane->regs misdirects the read/write operations to offsets 0x88 and
0x94 instead of 0x2088 and 0x2094 (since MPHY_RX_OFFSET is 0x2000), which
may lead to memory corruption and failed configurations.

> +	value &= ~RX_HS_G1_SYNC_LENGTH_CAPABILITY(~0);
> +	value |= RX_HS_G1_SYNC_LENGTH_CAPABILITY(0xf);
> +	writel(value, lane->regs + MPHY_RX_APB_CAPABILITY_88_8B);
> +
> +	value = readl(lane->regs + MPHY_RX_APB_CAPABILITY_94_97);
                      ^^^^^^^^^^

[Severity: High]
Does this also need to use lane->rx_regs instead of lane->regs?

> +	value &= ~RX_HS_G3_SYNC_LENGTH_CAPABILITY(~0);
> +	value |= RX_HS_G3_SYNC_LENGTH_CAPABILITY(0xf);
> +	value &= ~RX_HS_G2_SYNC_LENGTH_CAPABILITY(~0);
> +	value |= RX_HS_G2_SYNC_LENGTH_CAPABILITY(0xf);
> +	writel(value, lane->regs + MPHY_RX_APB_CAPABILITY_94_97);

[ ... ]

> +static int tegra_mphy_rx_power_off(struct phy *phy)
> +{
> +	struct tegra_mphy *mphy = dev_get_drvdata(phy->dev.parent);
> +	struct tegra_mphy_lane *lane = phy_get_drvdata(phy);
> +
> +	if (WARN_ON(mphy->power_count == 0))
> +		return -EINVAL;
> +
> +	reset_control_assert(lane->rst_rx);
> +	reset_control_assert(lane->rst_tx);
> +
> +	if (--mphy->power_count == 0)
> +		clk_bulk_disable_unprepare(mphy->num_clks, mphy->clks);
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[Severity: Medium]
Should mphy->rst_clk_ctl be asserted here before or after disabling the
clocks?

The driver intentionally deasserts mphy->rst_clk_ctl in
tegra_mphy_rx_power_on() when power_count goes from 0 to 1, but omits the
corresponding reset_control_assert() in this successful power-off path,
which leaks the reset state across power cycles.

> +
> +	return 0;
> +}

[ ... ]

> +static const struct phy_ops tegra_mphy_tx_ops = {
> +	.configure = tegra_mphy_tx_configure,
> +	.calibrate = tegra_mphy_tx_calibrate,
> +};
        ^^

[Severity: High]
Are .power_on and .power_off callbacks intentionally omitted for the TX PHY
ops?

Leaving the TX PHY exposed to configuration while in an unclocked state
creates a fragile API contract that risks system crashes.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260909-tegra264-mphy-v1-0-ce61a80dc065@nvidia.com?part=2

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

* Re: [PATCH 1/2] dt-bindings: phy: Add nvidia,tegra264-mphy
  2026-09-09  9:43 ` [PATCH 1/2] dt-bindings: phy: Add nvidia,tegra264-mphy Kartik Rajput
@ 2026-09-13  8:38   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 5+ messages in thread
From: Krzysztof Kozlowski @ 2026-09-13  8:38 UTC (permalink / raw)
  To: Kartik Rajput
  Cc: Vinod Koul, Neil Armstrong, Manivannan Sadhasivam, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
	Jonathan Hunter, Philipp Zabel, Thierry Reding, linux-phy,
	devicetree, linux-tegra, linux-kernel

On Wed, Sep 09, 2026 at 03:13:58PM +0530, Kartik Rajput wrote:
> Add binding for the MIPI M-PHY on NVIDIA Tegra264 SoCs. The M-PHY is
> a hardware block with two lanes, l0 and l1, used by the Tegra264 UFS
> host controller.
> 
> Signed-off-by: Kartik Rajput <kkartik@nvidia.com>
> ---
>  .../bindings/phy/nvidia,tegra264-mphy.yaml         | 108 +++++++++++++++++++++
>  include/dt-bindings/phy/nvidia,tegra264-mphy.h     |  12 +++
>  2 files changed, 120 insertions(+)

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>

Best regards,
Krzysztof


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

end of thread, other threads:[~2026-09-13  8:38 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-09  9:43 [PATCH 0/2] Add M-PHY support for NVIDIA Tegra264 Kartik Rajput
2026-09-09  9:43 ` [PATCH 1/2] dt-bindings: phy: Add nvidia,tegra264-mphy Kartik Rajput
2026-09-13  8:38   ` Krzysztof Kozlowski
2026-09-09  9:43 ` [PATCH 2/2] phy: tegra: Add Tegra264 MPHY driver Kartik Rajput
2026-09-09  9:54   ` sashiko-bot

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