* [PATCH 0/2] Add M-PHY support for NVIDIA Tegra264
@ 2026-09-09 9:43 ` Kartik Rajput
0 siblings, 0 replies; 10+ 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] 10+ messages in thread* [PATCH 0/2] Add M-PHY support for NVIDIA Tegra264 @ 2026-09-09 9:43 ` Kartik Rajput 0 siblings, 0 replies; 10+ 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> -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 1/2] dt-bindings: phy: Add nvidia,tegra264-mphy 2026-09-09 9:43 ` Kartik Rajput @ 2026-09-09 9:43 ` Kartik Rajput -1 siblings, 0 replies; 10+ 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] 10+ messages in thread
* [PATCH 1/2] dt-bindings: phy: Add nvidia,tegra264-mphy @ 2026-09-09 9:43 ` Kartik Rajput 0 siblings, 0 replies; 10+ 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 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 1/2] dt-bindings: phy: Add nvidia,tegra264-mphy 2026-09-09 9:43 ` Kartik Rajput @ 2026-09-13 8:38 ` Krzysztof Kozlowski -1 siblings, 0 replies; 10+ 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 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 1/2] dt-bindings: phy: Add nvidia,tegra264-mphy @ 2026-09-13 8:38 ` Krzysztof Kozlowski 0 siblings, 0 replies; 10+ 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] 10+ messages in thread
* [PATCH 2/2] phy: tegra: Add Tegra264 MPHY driver 2026-09-09 9:43 ` Kartik Rajput @ 2026-09-09 9:43 ` Kartik Rajput -1 siblings, 0 replies; 10+ 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] 10+ messages in thread
* [PATCH 2/2] phy: tegra: Add Tegra264 MPHY driver @ 2026-09-09 9:43 ` Kartik Rajput 0 siblings, 0 replies; 10+ 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 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 2/2] phy: tegra: Add Tegra264 MPHY driver 2026-09-09 9:43 ` Kartik Rajput @ 2026-09-09 9:54 ` sashiko-bot -1 siblings, 0 replies; 10+ 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 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 2/2] phy: tegra: Add Tegra264 MPHY driver @ 2026-09-09 9:54 ` sashiko-bot 0 siblings, 0 replies; 10+ 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] 10+ messages in thread
end of thread, other threads:[~2026-09-13 8:38 UTC | newest] Thread overview: 10+ 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 ` 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-13 8:38 ` Krzysztof Kozlowski 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:43 ` Kartik Rajput 2026-09-09 9:54 ` sashiko-bot 2026-09-09 9:54 ` sashiko-bot
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.