From: Kartik Rajput <kkartik@nvidia.com>
To: Alim Akhtar <alim.akhtar@samsung.com>,
Avri Altman <avri.altman@sandisk.com>,
Bart Van Assche <bvanassche@acm.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>,
"James E.J. Bottomley" <James.Bottomley@HansenPartnership.com>,
"Martin K. Petersen" <mkp@kernel.org>,
Philipp Zabel <p.zabel@pengutronix.de>
Cc: Thierry Reding <treding@nvidia.com>, <linux-scsi@vger.kernel.org>,
<devicetree@vger.kernel.org>, <linux-tegra@vger.kernel.org>,
<linux-kernel@vger.kernel.org>,
Kartik Rajput <kkartik@nvidia.com>
Subject: [PATCH 4/4] scsi: ufs: tegra: Add Tegra264 UFS host controller driver
Date: Wed, 9 Sep 2026 16:11:22 +0530 [thread overview]
Message-ID: <20260909-tegra264-ufs-v1-4-e4a9831702a0@nvidia.com> (raw)
In-Reply-To: <20260909-tegra264-ufs-v1-0-e4a9831702a0@nvidia.com>
Add a driver for the UFS host controller found on NVIDIA Tegra264 SoCs.
The controller has Tegra-specific auxiliary registers, clocks and resets,
and it drives the four M-PHY lane directions exposed by the Tegra264
M-PHY driver.
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/ufs/host/Kconfig | 13 +
drivers/ufs/host/Makefile | 1 +
drivers/ufs/host/ufs-tegra.c | 687 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 701 insertions(+)
diff --git a/drivers/ufs/host/Kconfig b/drivers/ufs/host/Kconfig
index ff170c0b6da0..e4c565fce53c 100644
--- a/drivers/ufs/host/Kconfig
+++ b/drivers/ufs/host/Kconfig
@@ -168,3 +168,16 @@ config SCSI_UFS_AMD_VERSAL2
Select this if you have UFS controller on AMD Versal Gen 2 SoC.
If unsure, say N.
+
+config SCSI_UFS_TEGRA
+ tristate "NVIDIA Tegra264 UFS controller platform driver"
+ depends on SCSI_UFSHCD_PLATFORM && (ARCH_TEGRA_264_SOC || COMPILE_TEST)
+ select PHY_TEGRA_MPHY
+ help
+ Enable support for the UFS host controller on NVIDIA Tegra264
+ SoCs. The driver relies on the Tegra264 M-PHY driver
+ (PHY_TEGRA_MPHY) for the two data lanes and implements the
+ vendor register programming required for HS-mode transitions
+ and system suspend/resume.
+
+ If unsure, say N.
diff --git a/drivers/ufs/host/Makefile b/drivers/ufs/host/Makefile
index 7d8db67eb23c..1459a081cc9d 100644
--- a/drivers/ufs/host/Makefile
+++ b/drivers/ufs/host/Makefile
@@ -14,5 +14,6 @@ obj-$(CONFIG_SCSI_UFS_MEDIATEK) += ufs-mediatek.o
obj-$(CONFIG_SCSI_UFS_RENESAS) += ufs-renesas.o
obj-$(CONFIG_SCSI_UFS_ROCKCHIP) += ufs-rockchip.o
obj-$(CONFIG_SCSI_UFS_SPRD) += ufs-sprd.o
+obj-$(CONFIG_SCSI_UFS_TEGRA) += ufs-tegra.o ufshcd-dwc.o
obj-$(CONFIG_SCSI_UFS_TI_J721E) += ti-j721e-ufs.o
obj-$(CONFIG_SCSI_UFS_AMD_VERSAL2) += ufs-amd-versal2.o ufshcd-dwc.o
diff --git a/drivers/ufs/host/ufs-tegra.c b/drivers/ufs/host/ufs-tegra.c
new file mode 100644
index 000000000000..859fe0af66e5
--- /dev/null
+++ b/drivers/ufs/host/ufs-tegra.c
@@ -0,0 +1,687 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+// NVIDIA Tegra264 UFS host controller driver.
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/mod_devicetable.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#include <ufs/ufshcd.h>
+#include <ufs/ufshci.h>
+#include <ufs/unipro.h>
+
+#include "ufshcd-dwc.h"
+#include "ufshcd-pltfrm.h"
+#include "ufshci-dwc.h"
+
+#define UFSHC_DWC_BLOCK_BUSTHRTL 0xc0
+#define MBL(x) (((x) & 0x7) << 13)
+#define MBL_MASK (0x7 << 13)
+
+#define HCLKDIV(x) ((x) & 0xff)
+
+#define UFSHC_AUX_SW_EN_CLK_SLCG 0x08
+#define CLK_OVR_ON BIT(0)
+
+#define UFSHC_AUX_DEV_CTRL 0x14
+#define DEV_CTRL_CLK_EN BIT(0)
+#define DEV_CTRL_RESET BIT(1)
+
+#define UFS_TEGRA_PHY_CLK_HS_RATE_HZ 5840000000UL
+
+/* DWC UFSHC BUSTHRTL max AXI burst length. */
+#define UFS_TEGRA_MBL_MAX 0x5
+
+/* DWC UniPro save-config-time reference and value fields. */
+#define UFS_TEGRA_SAVECONFIGTIME_TREF 0x6
+#define UFS_TEGRA_SAVECONFIGTIME_SCT 0x3
+
+/* HS G1..G4 sync burst length advertised to the peer. */
+#define UFS_TEGRA_HSG_SYNC_LEN 0x4f
+
+/* Local UniPro DME timeout values sent to the peer via PA_PWRMODEUSERDATA. */
+#define UFS_TEGRA_FC0_TIMEOUT 0x1fff
+#define UFS_TEGRA_TC0_TIMEOUT 0xffff
+#define UFS_TEGRA_AFC0_TIMEOUT 0x7fff
+
+struct ufs_tegra {
+ void __iomem *aux;
+
+ struct reset_control_bulk_data *resets;
+ unsigned int num_resets;
+
+ struct clk *ufs_clk;
+ struct clk *phy_clk;
+
+ struct phy *mphy_l0_rx;
+ struct phy *mphy_l0_tx;
+ struct phy *mphy_l1_rx;
+ struct phy *mphy_l1_tx;
+
+ bool mphy_on;
+
+ struct ufs_host_params host_params;
+};
+
+static int ufs_tegra_init_resources(struct ufs_hba *hba)
+{
+ struct platform_device *pdev = to_platform_device(hba->dev);
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+
+ ufs->aux = devm_platform_ioremap_resource_byname(pdev, "aux");
+ if (IS_ERR(ufs->aux))
+ return PTR_ERR(ufs->aux);
+
+ return 0;
+}
+
+static const struct reset_control_bulk_data ufs_tegra_resets[] = {
+ { .id = "ufs" },
+ { .id = "axi" },
+ { .id = "lp" },
+};
+
+static int ufs_tegra_parse_reset_info(struct ufs_hba *hba)
+{
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+ struct device *dev = hba->dev;
+
+ ufs->num_resets = ARRAY_SIZE(ufs_tegra_resets);
+
+ ufs->resets = devm_kmemdup(dev, ufs_tegra_resets, sizeof(ufs_tegra_resets), GFP_KERNEL);
+ if (!ufs->resets)
+ return -ENOMEM;
+
+ return devm_reset_control_bulk_get_exclusive(dev, ufs->num_resets, ufs->resets);
+}
+
+static int ufs_tegra_parse_clock_info(struct ufs_hba *hba)
+{
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+ struct ufs_clk_info *clki;
+
+ list_for_each_entry(clki, &hba->clk_list_head, list) {
+ if (!strcmp(clki->name, "ufs"))
+ ufs->ufs_clk = clki->clk;
+
+ if (!strcmp(clki->name, "phy"))
+ ufs->phy_clk = clki->clk;
+ }
+
+ if (!ufs->ufs_clk || !ufs->phy_clk)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int ufs_tegra_parse_phy_info(struct ufs_hba *hba)
+{
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+ struct device *dev = hba->dev;
+
+ ufs->mphy_l0_rx = devm_phy_get(dev, "mphy-l0-rx");
+ if (IS_ERR(ufs->mphy_l0_rx))
+ return PTR_ERR(ufs->mphy_l0_rx);
+
+ ufs->mphy_l0_tx = devm_phy_get(dev, "mphy-l0-tx");
+ if (IS_ERR(ufs->mphy_l0_tx))
+ return PTR_ERR(ufs->mphy_l0_tx);
+
+ ufs->mphy_l1_rx = devm_phy_get(dev, "mphy-l1-rx");
+ if (IS_ERR(ufs->mphy_l1_rx))
+ return PTR_ERR(ufs->mphy_l1_rx);
+
+ ufs->mphy_l1_tx = devm_phy_get(dev, "mphy-l1-tx");
+ if (IS_ERR(ufs->mphy_l1_tx))
+ return PTR_ERR(ufs->mphy_l1_tx);
+
+ return 0;
+}
+
+static void ufs_tegra_set_caps(struct ufs_hba *hba)
+{
+ hba->caps |= UFSHCD_CAP_INTR_AGGR;
+ hba->caps |= UFSHCD_CAP_WB_EN;
+
+ hba->spm_lvl = UFS_PM_LVL_5;
+}
+
+static void ufs_tegra_configure_aux(struct ufs_hba *hba)
+{
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+ u32 value;
+
+ value = readl(ufs->aux + UFSHC_AUX_DEV_CTRL);
+ value |= DEV_CTRL_CLK_EN;
+ writel(value, ufs->aux + UFSHC_AUX_DEV_CTRL);
+
+ /* Let the reference clock stabilise before deasserting device reset. */
+ usleep_range(1000, 2000);
+
+ value |= DEV_CTRL_RESET;
+ writel(value, ufs->aux + UFSHC_AUX_DEV_CTRL);
+}
+
+static void ufs_tegra_aux_init(struct ufs_hba *hba)
+{
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+ u32 value;
+
+ value = readl(ufs->aux + UFSHC_AUX_DEV_CTRL);
+ value &= ~(DEV_CTRL_CLK_EN | DEV_CTRL_RESET);
+ writel(value, ufs->aux + UFSHC_AUX_DEV_CTRL);
+
+ /* Ensure the clear is observed before we re-enable. */
+ usleep_range(100, 200);
+
+ ufs_tegra_configure_aux(hba);
+}
+
+static void ufs_tegra_update_hclk_div(struct ufs_hba *hba)
+{
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+ u32 ufs_clk_mhz;
+
+ ufs_clk_mhz = clk_get_rate(ufs->ufs_clk) / USEC_PER_SEC;
+ ufshcd_writel(hba, HCLKDIV(ufs_clk_mhz), DWC_UFS_REG_HCLKDIV);
+}
+
+static int ufs_tegra_init(struct ufs_hba *hba)
+{
+ struct platform_device *pdev = to_platform_device(hba->dev);
+ struct device *dev = &pdev->dev;
+ struct ufs_tegra *ufs;
+ int err;
+
+ ufs = devm_kzalloc(dev, sizeof(*ufs), GFP_KERNEL);
+ if (!ufs)
+ return -ENOMEM;
+
+ ufshcd_set_variant(hba, ufs);
+
+ err = ufs_tegra_init_resources(hba);
+ if (err)
+ return err;
+
+ err = ufs_tegra_parse_clock_info(hba);
+ if (err)
+ return err;
+
+ err = ufs_tegra_parse_reset_info(hba);
+ if (err)
+ return err;
+
+ err = ufs_tegra_parse_phy_info(hba);
+ if (err)
+ return err;
+
+ ufs_tegra_set_caps(hba);
+
+ ufshcd_init_host_params(&ufs->host_params);
+ ufs->host_params.hs_rx_gear = UFS_HS_G4;
+ ufs->host_params.hs_tx_gear = UFS_HS_G4;
+
+ err = reset_control_bulk_deassert(ufs->num_resets, ufs->resets);
+ if (err)
+ return err;
+
+ ufs_tegra_aux_init(hba);
+ ufs_tegra_update_hclk_div(hba);
+
+ return 0;
+}
+
+static int ufs_tegra_mphy_init(struct ufs_hba *hba)
+{
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+ int err;
+
+ err = phy_init(ufs->mphy_l0_rx);
+ if (err) {
+ dev_err(hba->dev, "failed to initialize mphy-l0-rx\n");
+ return err;
+ }
+
+ err = phy_init(ufs->mphy_l0_tx);
+ if (err) {
+ dev_err(hba->dev, "failed to initialize mphy-l0-tx\n");
+ goto out_exit_l0_rx;
+ }
+
+ err = phy_init(ufs->mphy_l1_rx);
+ if (err) {
+ dev_err(hba->dev, "failed to initialize mphy-l1-rx\n");
+ goto out_exit_l0_tx;
+ }
+
+ err = phy_init(ufs->mphy_l1_tx);
+ if (err) {
+ dev_err(hba->dev, "failed to initialize mphy-l1-tx\n");
+ goto out_exit_l1_rx;
+ }
+
+ return 0;
+
+out_exit_l1_rx:
+ phy_exit(ufs->mphy_l1_rx);
+out_exit_l0_tx:
+ phy_exit(ufs->mphy_l0_tx);
+out_exit_l0_rx:
+ phy_exit(ufs->mphy_l0_rx);
+
+ return err;
+}
+
+static void ufs_tegra_mphy_exit(struct ufs_hba *hba)
+{
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+
+ phy_exit(ufs->mphy_l0_rx);
+ phy_exit(ufs->mphy_l0_tx);
+ phy_exit(ufs->mphy_l1_rx);
+ phy_exit(ufs->mphy_l1_tx);
+}
+
+static int ufs_tegra_mphy_power_on(struct ufs_hba *hba)
+{
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+ int err;
+
+ err = phy_power_on(ufs->mphy_l0_rx);
+ if (err) {
+ dev_err(hba->dev, "failed to power on mphy-l0-rx\n");
+ return err;
+ }
+
+ err = phy_power_on(ufs->mphy_l0_tx);
+ if (err) {
+ dev_err(hba->dev, "failed to power on mphy-l0-tx\n");
+ goto out_power_off_l0_rx;
+ }
+
+ err = phy_power_on(ufs->mphy_l1_rx);
+ if (err) {
+ dev_err(hba->dev, "failed to power on mphy-l1-rx\n");
+ goto out_power_off_l0_tx;
+ }
+
+ err = phy_power_on(ufs->mphy_l1_tx);
+ if (err) {
+ dev_err(hba->dev, "failed to power on mphy-l1-tx\n");
+ goto out_power_off_l1_rx;
+ }
+
+ return 0;
+
+out_power_off_l1_rx:
+ phy_power_off(ufs->mphy_l1_rx);
+out_power_off_l0_tx:
+ phy_power_off(ufs->mphy_l0_tx);
+out_power_off_l0_rx:
+ phy_power_off(ufs->mphy_l0_rx);
+
+ return err;
+}
+
+static void ufs_tegra_mphy_power_off(struct ufs_hba *hba)
+{
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+
+ phy_power_off(ufs->mphy_l0_rx);
+ phy_power_off(ufs->mphy_l0_tx);
+ phy_power_off(ufs->mphy_l1_rx);
+ phy_power_off(ufs->mphy_l1_tx);
+}
+
+static int ufs_tegra_mphy_enable(struct ufs_hba *hba)
+{
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+ int err;
+
+ if (ufs->mphy_on)
+ return 0;
+
+ err = ufs_tegra_mphy_init(hba);
+ if (err)
+ return err;
+
+ err = ufs_tegra_mphy_power_on(hba);
+ if (err) {
+ ufs_tegra_mphy_exit(hba);
+ return err;
+ }
+
+ ufs->mphy_on = true;
+
+ /* Allow M-PHY to settle after power-on. */
+ usleep_range(500, 600);
+
+ return 0;
+}
+
+static void ufs_tegra_mphy_disable(struct ufs_hba *hba)
+{
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+
+ if (!ufs->mphy_on)
+ return;
+
+ ufs_tegra_mphy_power_off(hba);
+ ufs_tegra_mphy_exit(hba);
+
+ ufs->mphy_on = false;
+}
+
+static int ufs_tegra_mphy_configure_tx(struct ufs_hba *hba)
+{
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+ int err;
+
+ err = phy_configure(ufs->mphy_l0_tx, NULL);
+ if (err)
+ return err;
+
+ return phy_configure(ufs->mphy_l1_tx, NULL);
+}
+
+static int ufs_tegra_mphy_calibrate_tx(struct ufs_hba *hba)
+{
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+ int err;
+
+ err = phy_calibrate(ufs->mphy_l0_tx);
+ if (err)
+ return err;
+
+ return phy_calibrate(ufs->mphy_l1_tx);
+}
+
+static int ufs_tegra_mphy_calibrate_rx(struct ufs_hba *hba)
+{
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+ int err;
+
+ err = phy_configure(ufs->mphy_l1_rx, NULL);
+ if (err)
+ return err;
+
+ err = phy_configure(ufs->mphy_l0_rx, NULL);
+ if (err)
+ return err;
+
+ err = phy_calibrate(ufs->mphy_l1_rx);
+ if (err)
+ return err;
+
+ return phy_calibrate(ufs->mphy_l0_rx);
+}
+
+static void ufs_tegra_exit(struct ufs_hba *hba)
+{
+ ufs_tegra_mphy_disable(hba);
+}
+
+static int ufs_tegra_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op,
+ enum ufs_notify_change_status status)
+{
+ if (status == PRE_CHANGE)
+ return 0;
+
+ /* Runtime H8 park keeps the M-PHY powered; only tear it down
+ * on the full-teardown paths (system suspend, shutdown, or
+ * runtime PM with LINK_OFF).
+ */
+ if (pm_op == UFS_RUNTIME_PM && !ufshcd_is_link_off(hba))
+ return 0;
+
+ ufs_tegra_mphy_disable(hba);
+
+ if (pm_op != UFS_RUNTIME_PM)
+ ufshcd_set_link_off(hba);
+
+ return 0;
+}
+
+static int ufs_tegra_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
+{
+ /* Skip when the M-PHY was left powered by the suspend path. */
+ if (ufshcd_is_link_active(hba) || ufshcd_is_link_hibern8(hba))
+ return 0;
+
+ return ufs_tegra_mphy_enable(hba);
+}
+
+static int ufs_tegra_reset(struct ufs_tegra *ufs)
+{
+ int err;
+
+ err = reset_control_bulk_assert(ufs->num_resets, ufs->resets);
+ if (err)
+ return err;
+
+ usleep_range(100, 200);
+
+ err = reset_control_bulk_deassert(ufs->num_resets, ufs->resets);
+ if (err)
+ return err;
+
+ usleep_range(500, 600);
+
+ return 0;
+}
+
+static int ufs_tegra_power_up_sequence(struct ufs_hba *hba)
+{
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+ int err;
+
+ err = ufs_tegra_reset(ufs);
+ if (err)
+ return err;
+
+ return ufs_tegra_mphy_enable(hba);
+}
+
+static void ufs_tegra_setup(struct ufs_hba *hba)
+{
+ ufs_tegra_configure_aux(hba);
+ ufs_tegra_update_hclk_div(hba);
+ ufshcd_rmwl(hba, MBL_MASK, MBL(UFS_TEGRA_MBL_MAX), UFSHC_DWC_BLOCK_BUSTHRTL);
+}
+
+static int ufs_tegra_hce_enable_notify(struct ufs_hba *hba, enum ufs_notify_change_status status)
+{
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+ u32 value;
+ int err;
+
+ switch (status) {
+ case PRE_CHANGE:
+ err = ufs_tegra_power_up_sequence(hba);
+ if (err)
+ return err;
+
+ /* Assert device reset; POST_CHANGE releases it via
+ * ufs_tegra_configure_aux() so the HCE toggle pulses it.
+ */
+ value = readl(ufs->aux + UFSHC_AUX_DEV_CTRL);
+ value &= ~DEV_CTRL_RESET;
+ writel(value, ufs->aux + UFSHC_AUX_DEV_CTRL);
+ break;
+ case POST_CHANGE:
+ value = readl(ufs->aux + UFSHC_AUX_SW_EN_CLK_SLCG);
+ value &= ~CLK_OVR_ON;
+ writel(value, ufs->aux + UFSHC_AUX_SW_EN_CLK_SLCG);
+
+ ufs_tegra_setup(hba);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ufs_tegra_link_startup_notify(struct ufs_hba *hba, enum ufs_notify_change_status status)
+{
+ static const struct ufshcd_dme_attr_val post_change_attrs[] = {
+ { UIC_ARG_MIB(T_CONNECTIONSTATE), 0x1, DME_LOCAL },
+ { UIC_ARG_MIB(PA_TXHSG1SYNCLENGTH), UFS_TEGRA_HSG_SYNC_LEN, DME_LOCAL },
+ { UIC_ARG_MIB(PA_TXHSG2SYNCLENGTH), UFS_TEGRA_HSG_SYNC_LEN, DME_LOCAL },
+ { UIC_ARG_MIB(PA_TXHSG3SYNCLENGTH), UFS_TEGRA_HSG_SYNC_LEN, DME_LOCAL },
+ { UIC_ARG_MIB(PA_TXHSG4SYNCLENGTH), UFS_TEGRA_HSG_SYNC_LEN, DME_LOCAL },
+ { UIC_ARG_MIB(DME_LocalFC0ProtectionTimeOutVal), UFS_TEGRA_FC0_TIMEOUT, DME_LOCAL },
+ { UIC_ARG_MIB(DME_LocalTC0ReplayTimeOutVal), UFS_TEGRA_TC0_TIMEOUT, DME_LOCAL },
+ { UIC_ARG_MIB(DME_LocalAFC0ReqTimeOutVal), UFS_TEGRA_AFC0_TIMEOUT, DME_LOCAL },
+ { UIC_ARG_MIB(PA_PWRMODEUSERDATA0), UFS_TEGRA_FC0_TIMEOUT, DME_LOCAL },
+ { UIC_ARG_MIB(PA_PWRMODEUSERDATA1), UFS_TEGRA_TC0_TIMEOUT, DME_LOCAL },
+ { UIC_ARG_MIB(PA_PWRMODEUSERDATA2), UFS_TEGRA_AFC0_TIMEOUT, DME_LOCAL },
+ };
+ int err;
+
+ switch (status) {
+ case PRE_CHANGE:
+ /* Arm TX calibration; RX capabilities are advertised in
+ * tegra_mphy_rx_power_on().
+ */
+ err = ufs_tegra_mphy_configure_tx(hba);
+ if (err)
+ return err;
+ break;
+
+ case POST_CHANGE:
+ err = ufs_tegra_mphy_calibrate_tx(hba);
+ if (err)
+ return err;
+
+ err = ufshcd_dwc_dme_set_attrs(hba, post_change_attrs,
+ ARRAY_SIZE(post_change_attrs));
+ if (err)
+ return err;
+
+ err = ufs_tegra_mphy_calibrate_rx(hba);
+ if (err)
+ return err;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ufs_tegra_negotiate_pwr_mode(struct ufs_hba *hba,
+ const struct ufs_pa_layer_attr *dev_max_params,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+
+ return ufshcd_negotiate_pwr_params(&ufs->host_params, dev_max_params, dev_req_params);
+}
+
+static int ufs_tegra_pwr_change_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ struct ufs_tegra *ufs = ufshcd_get_variant(hba);
+ u32 value;
+ int err;
+
+ if (status != PRE_CHANGE)
+ return 0;
+
+ err = ufshcd_dme_get(hba, UIC_ARG_MIB(VS_DEBUGSAVECONFIGTIME), &value);
+ if (err)
+ return err;
+
+ value &= ~VS_DEBUGSAVECONFIGTIME_TREF;
+ value |= FIELD_PREP(VS_DEBUGSAVECONFIGTIME_TREF, UFS_TEGRA_SAVECONFIGTIME_TREF);
+ value &= ~VS_DEBUGSAVECONFIGTIME_SCT;
+ value |= FIELD_PREP(VS_DEBUGSAVECONFIGTIME_SCT, UFS_TEGRA_SAVECONFIGTIME_SCT);
+
+ err = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_DEBUGSAVECONFIGTIME), value);
+ if (err)
+ return err;
+
+ if (dev_req_params->hs_rate == PA_HS_MODE_A ||
+ dev_req_params->hs_rate == PA_HS_MODE_B) {
+ err = clk_set_rate(ufs->phy_clk, UFS_TEGRA_PHY_CLK_HS_RATE_HZ);
+ if (err)
+ return err;
+ }
+
+ err = ufshcd_dme_configure_adapt(hba, dev_req_params->gear_tx, PA_INITIAL_ADAPT);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int ufs_tegra_set_dma_mask(struct ufs_hba *hba)
+{
+ return dma_set_mask_and_coherent(hba->dev, DMA_BIT_MASK(32));
+}
+
+static const struct ufs_hba_variant_ops ufs_tegra_vops = {
+ .name = "ufs-tegra",
+ .init = ufs_tegra_init,
+ .exit = ufs_tegra_exit,
+ .suspend = ufs_tegra_suspend,
+ .resume = ufs_tegra_resume,
+ .hce_enable_notify = ufs_tegra_hce_enable_notify,
+ .negotiate_pwr_mode = ufs_tegra_negotiate_pwr_mode,
+ .pwr_change_notify = ufs_tegra_pwr_change_notify,
+ .link_startup_notify = ufs_tegra_link_startup_notify,
+ .set_dma_mask = ufs_tegra_set_dma_mask,
+};
+
+static int ufs_tegra_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int err;
+
+ err = ufshcd_pltfrm_init(pdev, &ufs_tegra_vops);
+ if (err)
+ return dev_err_probe(dev, err, "ufshcd platform init failed\n");
+
+ return 0;
+}
+
+static void ufs_tegra_remove(struct platform_device *pdev)
+{
+ ufshcd_pltfrm_remove(pdev);
+}
+
+static const struct dev_pm_ops ufs_tegra_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume)
+ SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
+};
+
+static const struct of_device_id ufs_tegra_of_match[] = {
+ { .compatible = "nvidia,tegra264-ufs", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ufs_tegra_of_match);
+
+static struct platform_driver ufs_tegra_driver = {
+ .probe = ufs_tegra_probe,
+ .remove = ufs_tegra_remove,
+ .driver = {
+ .name = "ufs-tegra",
+ .pm = &ufs_tegra_pm_ops,
+ .of_match_table = ufs_tegra_of_match,
+ },
+};
+module_platform_driver(ufs_tegra_driver);
+
+MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
+MODULE_AUTHOR("Kartik Rajput <kkartik@nvidia.com>");
+MODULE_DESCRIPTION("NVIDIA Tegra UFS host controller driver");
+MODULE_LICENSE("GPL");
--
2.43.0
next prev parent reply other threads:[~2026-09-09 10:42 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-09 10:41 [PATCH 0/4] Add UFS host controller support for NVIDIA Tegra264 Kartik Rajput
2026-09-09 10:41 ` [PATCH 1/4] dt-bindings: ufs: Add nvidia,tegra264-ufs Kartik Rajput
2026-09-09 10:51 ` sashiko-bot
2026-09-13 8:40 ` Krzysztof Kozlowski
2026-09-09 10:41 ` [PATCH 2/4] scsi: ufs: Add UIC DEBUGSAVECONFIGTIME attribute and its field masks Kartik Rajput
2026-09-09 10:41 ` [PATCH 3/4] scsi: ufs: hisi: Use VS_DEBUGSAVECONFIGTIME instead of a literal address Kartik Rajput
2026-09-09 10:41 ` Kartik Rajput [this message]
2026-09-09 11:02 ` [PATCH 4/4] scsi: ufs: tegra: Add Tegra264 UFS host controller driver sashiko-bot
2026-09-09 14:20 ` Uwe Kleine-König
2026-09-09 16:10 ` Bart Van Assche
2026-09-10 11:21 ` Uwe Kleine-König
2026-09-10 13:09 ` Bart Van Assche
2026-09-10 13:52 ` Uwe Kleine-König
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-ufs-v1-4-e4a9831702a0@nvidia.com \
--to=kkartik@nvidia.com \
--cc=James.Bottomley@HansenPartnership.com \
--cc=alim.akhtar@samsung.com \
--cc=avri.altman@sandisk.com \
--cc=bvanassche@acm.org \
--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-scsi@vger.kernel.org \
--cc=linux-tegra@vger.kernel.org \
--cc=mkp@kernel.org \
--cc=p.zabel@pengutronix.de \
--cc=robh@kernel.org \
--cc=thierry.reding@kernel.org \
--cc=treding@nvidia.com \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox