Devicetree
 help / color / mirror / Atom feed
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


  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