* [PATCH 1/4] dt-bindings: ufs: Add nvidia,tegra264-ufs
2026-09-09 10:41 [PATCH 0/4] Add UFS host controller support for NVIDIA Tegra264 Kartik Rajput
@ 2026-09-09 10:41 ` 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
` (2 subsequent siblings)
3 siblings, 2 replies; 13+ messages in thread
From: Kartik Rajput @ 2026-09-09 10:41 UTC (permalink / raw)
To: Alim Akhtar, Avri Altman, Bart Van Assche, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
Jonathan Hunter, James E.J. Bottomley, Martin K. Petersen,
Philipp Zabel
Cc: Thierry Reding, linux-scsi, devicetree, linux-tegra, linux-kernel,
Kartik Rajput
Add binding for the UFS host controller on NVIDIA Tegra264 SoCs. The
controller supports UFS 3.1 up to HS-G4B on two data lanes.
Signed-off-by: Kartik Rajput <kkartik@nvidia.com>
---
.../bindings/ufs/nvidia,tegra264-ufs.yaml | 124 +++++++++++++++++++++
1 file changed, 124 insertions(+)
diff --git a/Documentation/devicetree/bindings/ufs/nvidia,tegra264-ufs.yaml b/Documentation/devicetree/bindings/ufs/nvidia,tegra264-ufs.yaml
new file mode 100644
index 000000000000..5a9837cb43dd
--- /dev/null
+++ b/Documentation/devicetree/bindings/ufs/nvidia,tegra264-ufs.yaml
@@ -0,0 +1,124 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/ufs/nvidia,tegra264-ufs.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NVIDIA Tegra264 UFS Host Controller
+
+maintainers:
+ - Kartik Rajput <kkartik@nvidia.com>
+ - Thierry Reding <treding@nvidia.com>
+
+properties:
+ compatible:
+ const: nvidia,tegra264-ufs
+
+ reg:
+ items:
+ - description: UFS Host Controller registers
+ - description: UFS auxiliary registers
+
+ reg-names:
+ items:
+ - const: ufs
+ - const: aux
+
+ clocks:
+ items:
+ - description: UFS host controller functional clock
+ - description: UFS host controller divider clock
+ - description: UFS reference clock (PLLREFUFS output)
+ - description: UFS device-side reference clock
+ - description: M-PHY reference clock (UPHY0 PLL4 digital output)
+
+ clock-names:
+ items:
+ - const: ufs
+ - const: div
+ - const: ref
+ - const: ref-dev
+ - const: phy
+
+ resets:
+ items:
+ - description: UFS host controller reset
+ - description: UFS host controller AXI-master reset
+ - description: UFS host controller low-power sequencer reset
+
+ reset-names:
+ items:
+ - const: ufs
+ - const: axi
+ - const: lp
+
+ phys:
+ items:
+ - description: M-PHY lane 0 transmit
+ - description: M-PHY lane 0 receive
+ - description: M-PHY lane 1 transmit
+ - description: M-PHY lane 1 receive
+
+ phy-names:
+ items:
+ - const: mphy-l0-tx
+ - const: mphy-l0-rx
+ - const: mphy-l1-tx
+ - const: mphy-l1-rx
+
+ iommus:
+ maxItems: 1
+
+ dma-coherent: true
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - clocks
+ - clock-names
+ - resets
+ - reset-names
+ - phys
+ - phy-names
+ - iommus
+ - dma-coherent
+
+allOf:
+ - $ref: ufs-common.yaml
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/nvidia,tegra264.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/phy/nvidia,tegra264-mphy.h>
+ #include <dt-bindings/reset/nvidia,tegra264.h>
+
+ ufs@b8d0000 {
+ compatible = "nvidia,tegra264-ufs";
+ reg = <0xb8d0000 0x10000>,
+ <0xb8e0000 0x8000>;
+ reg-names = "ufs", "aux";
+ interrupts = <GIC_SPI 951 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&bpmp TEGRA264_CLK_UFSHC_CG_SYS>,
+ <&bpmp TEGRA264_CLK_UFSHC_CG_SYS_DIV>,
+ <&bpmp TEGRA264_CLK_PLLREFUFS_CLKOUT624>,
+ <&bpmp TEGRA264_CLK_PLLREFUFS_UFSDEV_REFCLKOUT>,
+ <&bpmp TEGRA264_CLK_UPHY0_PLL4_XDIG>;
+ clock-names = "ufs", "div", "ref", "ref-dev", "phy";
+ resets = <&bpmp TEGRA264_RESET_UFSHC>,
+ <&bpmp TEGRA264_RESET_UFSHC_AXI_M>,
+ <&bpmp TEGRA264_RESET_UFSHC_LP_SEQ>;
+ reset-names = "ufs", "axi", "lp";
+ phys = <&mphy TEGRA_MPHY_L0_TX>,
+ <&mphy TEGRA_MPHY_L0_RX>,
+ <&mphy TEGRA_MPHY_L1_TX>,
+ <&mphy TEGRA_MPHY_L1_RX>;
+ phy-names = "mphy-l0-tx", "mphy-l0-rx",
+ "mphy-l1-tx", "mphy-l1-rx";
+ iommus = <&smmu 0x20>;
+ dma-coherent;
+ };
+...
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH 3/4] scsi: ufs: hisi: Use VS_DEBUGSAVECONFIGTIME instead of a literal address
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:41 ` [PATCH 2/4] scsi: ufs: Add UIC DEBUGSAVECONFIGTIME attribute and its field masks Kartik Rajput
@ 2026-09-09 10:41 ` Kartik Rajput
2026-09-09 10:41 ` [PATCH 4/4] scsi: ufs: tegra: Add Tegra264 UFS host controller driver Kartik Rajput
3 siblings, 0 replies; 13+ messages in thread
From: Kartik Rajput @ 2026-09-09 10:41 UTC (permalink / raw)
To: Alim Akhtar, Avri Altman, Bart Van Assche, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
Jonathan Hunter, James E.J. Bottomley, Martin K. Petersen,
Philipp Zabel
Cc: Thierry Reding, linux-scsi, devicetree, linux-tegra, linux-kernel,
Kartik Rajput
ufs-hisi writes the VS_DebugSaveConfigTime UIC attribute twice by its
literal address, with a comment naming it each time. The attribute now
has a name in the shared UniPro header, so use it and drop the comments
and the redundant (u32) cast.
No functional change.
Signed-off-by: Kartik Rajput <kkartik@nvidia.com>
---
drivers/ufs/host/ufs-hisi.c | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/drivers/ufs/host/ufs-hisi.c b/drivers/ufs/host/ufs-hisi.c
index 993e20ac211d..79417c16c627 100644
--- a/drivers/ufs/host/ufs-hisi.c
+++ b/drivers/ufs/host/ufs-hisi.c
@@ -318,8 +318,7 @@ static void ufs_hisi_pwr_change_pre_change(struct ufs_hba *hba)
* Boston platform need to set SaveConfigTime to 0x13,
* and change sync length to maximum value
*/
- /* VS_DebugSaveConfigTime */
- ufshcd_dme_set(hba, UIC_ARG_MIB((u32)0xD0A0), 0x13);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(VS_DEBUGSAVECONFIGTIME), 0x13);
/* g1 sync length */
ufshcd_dme_set(hba, UIC_ARG_MIB((u32)0x1552), 0x4f);
/* g2 sync length */
@@ -335,8 +334,7 @@ static void ufs_hisi_pwr_change_pre_change(struct ufs_hba *hba)
if (hba->dev_quirks & UFS_DEVICE_QUIRK_HOST_VS_DEBUGSAVECONFIGTIME) {
pr_info("ufs flash device must set VS_DebugSaveConfigTime 0x10\n");
- /* VS_DebugSaveConfigTime */
- ufshcd_dme_set(hba, UIC_ARG_MIB(0xD0A0), 0x10);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(VS_DEBUGSAVECONFIGTIME), 0x10);
/* sync length */
ufshcd_dme_set(hba, UIC_ARG_MIB(0x1556), 0x48);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH 4/4] scsi: ufs: tegra: Add Tegra264 UFS host controller driver
2026-09-09 10:41 [PATCH 0/4] Add UFS host controller support for NVIDIA Tegra264 Kartik Rajput
` (2 preceding siblings ...)
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
2026-09-09 11:02 ` sashiko-bot
2026-09-09 14:20 ` Uwe Kleine-König
3 siblings, 2 replies; 13+ messages in thread
From: Kartik Rajput @ 2026-09-09 10:41 UTC (permalink / raw)
To: Alim Akhtar, Avri Altman, Bart Van Assche, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
Jonathan Hunter, James E.J. Bottomley, Martin K. Petersen,
Philipp Zabel
Cc: Thierry Reding, linux-scsi, devicetree, linux-tegra, linux-kernel,
Kartik Rajput
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
^ permalink raw reply related [flat|nested] 13+ messages in thread