From: Kartik Rajput <kkartik@nvidia.com>
To: Vinod Koul <vkoul@kernel.org>,
Neil Armstrong <neil.armstrong@linaro.org>,
Manivannan Sadhasivam <mani@kernel.org>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Thierry Reding <thierry.reding@kernel.org>,
Jonathan Hunter <jonathanh@nvidia.com>,
Philipp Zabel <p.zabel@pengutronix.de>
Cc: Thierry Reding <treding@nvidia.com>,
<linux-phy@lists.infradead.org>, <devicetree@vger.kernel.org>,
<linux-tegra@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
Kartik Rajput <kkartik@nvidia.com>
Subject: [PATCH 2/2] phy: tegra: Add Tegra264 MPHY driver
Date: Wed, 9 Sep 2026 15:13:59 +0530 [thread overview]
Message-ID: <20260909-tegra264-mphy-v1-2-ce61a80dc065@nvidia.com> (raw)
In-Reply-To: <20260909-tegra264-mphy-v1-0-ce61a80dc065@nvidia.com>
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
WARNING: multiple messages have this Message-ID (diff)
From: Kartik Rajput <kkartik@nvidia.com>
To: Vinod Koul <vkoul@kernel.org>,
Neil Armstrong <neil.armstrong@linaro.org>,
Manivannan Sadhasivam <mani@kernel.org>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Thierry Reding <thierry.reding@kernel.org>,
Jonathan Hunter <jonathanh@nvidia.com>,
Philipp Zabel <p.zabel@pengutronix.de>
Cc: Thierry Reding <treding@nvidia.com>,
<linux-phy@lists.infradead.org>, <devicetree@vger.kernel.org>,
<linux-tegra@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
Kartik Rajput <kkartik@nvidia.com>
Subject: [PATCH 2/2] phy: tegra: Add Tegra264 MPHY driver
Date: Wed, 9 Sep 2026 15:13:59 +0530 [thread overview]
Message-ID: <20260909-tegra264-mphy-v1-2-ce61a80dc065@nvidia.com> (raw)
In-Reply-To: <20260909-tegra264-mphy-v1-0-ce61a80dc065@nvidia.com>
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
next prev parent reply other threads:[~2026-09-09 9:44 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` Kartik Rajput [this message]
2026-09-09 9:43 ` [PATCH 2/2] phy: tegra: Add Tegra264 MPHY driver Kartik Rajput
2026-09-09 9:54 ` sashiko-bot
2026-09-09 9:54 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260909-tegra264-mphy-v1-2-ce61a80dc065@nvidia.com \
--to=kkartik@nvidia.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=jonathanh@nvidia.com \
--cc=krzk+dt@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-phy@lists.infradead.org \
--cc=linux-tegra@vger.kernel.org \
--cc=mani@kernel.org \
--cc=neil.armstrong@linaro.org \
--cc=p.zabel@pengutronix.de \
--cc=robh@kernel.org \
--cc=thierry.reding@kernel.org \
--cc=treding@nvidia.com \
--cc=vkoul@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.