diff mbox series

[2/3] ufs: ufs-amd-versal2: Add support for AMD UFS controller

Message ID 20240919091731.26338-3-venkatesh.abbarapu@amd.com
State New
Headers show
Series Add AMD UFS platfrom driver support | expand

Commit Message

Abbarapu, Venkatesh Sept. 19, 2024, 9:17 a.m. UTC
Add UFS AMD platform support on top of the UFS DWC
and UFS platform driver. UFS AMD platform requires
some platform specific configurations like M-PHY/RMMI/UniPro
and vendor specific registers programming before doing the LINKSTARTUP.

Signed-off-by: Venkatesh Yadav Abbarapu <venkatesh.abbarapu@amd.com>
---
 drivers/ufs/Kconfig           |   8 +
 drivers/ufs/Makefile          |   1 +
 drivers/ufs/ufs-amd-versal2.c | 501 ++++++++++++++++++++++++++++++++++
 drivers/ufs/ufshcd-dwc.h      |  46 ++++
 drivers/ufs/unipro.h          |   1 +
 5 files changed, 557 insertions(+)
 create mode 100644 drivers/ufs/ufs-amd-versal2.c

Comments

neil.armstrong@linaro.org Sept. 19, 2024, 3:45 p.m. UTC | #1
Hi,

On 19/09/2024 11:17, Venkatesh Yadav Abbarapu wrote:
> Add UFS AMD platform support on top of the UFS DWC
> and UFS platform driver. UFS AMD platform requires
> some platform specific configurations like M-PHY/RMMI/UniPro
> and vendor specific registers programming before doing the LINKSTARTUP.
> 
> Signed-off-by: Venkatesh Yadav Abbarapu <venkatesh.abbarapu@amd.com>
> ---
>   drivers/ufs/Kconfig           |   8 +
>   drivers/ufs/Makefile          |   1 +
>   drivers/ufs/ufs-amd-versal2.c | 501 ++++++++++++++++++++++++++++++++++
>   drivers/ufs/ufshcd-dwc.h      |  46 ++++
>   drivers/ufs/unipro.h          |   1 +
>   5 files changed, 557 insertions(+)
>   create mode 100644 drivers/ufs/ufs-amd-versal2.c
> 
> diff --git a/drivers/ufs/Kconfig b/drivers/ufs/Kconfig
> index 7da46faed6..ad5d0316f6 100644
> --- a/drivers/ufs/Kconfig
> +++ b/drivers/ufs/Kconfig
> @@ -41,4 +41,12 @@ config UFS_RENESAS
>   	  UFS host on Renesas needs some vendor specific configuration before
>   	  accessing the hardware.
>   
> +config UFS_AMD_VERSAL2
> +	bool "AMD Versal Gen 2 UFS controller platform driver"
> +	depends on UFS && ZYNQMP_FIRMWARE
> +	help
> +	  This selects the AMD specific additions to UFSHCD platform driver.
> +	  UFS host on AMD needs some vendor specific configuration before accessing
> +	  the hardware.
> +
>   endmenu
> diff --git a/drivers/ufs/Makefile b/drivers/ufs/Makefile
> index 67c42621ab..745de5c3db 100644
> --- a/drivers/ufs/Makefile
> +++ b/drivers/ufs/Makefile
> @@ -8,3 +8,4 @@ obj-$(CONFIG_CADENCE_UFS) += cdns-platform.o
>   obj-$(CONFIG_TI_J721E_UFS) += ti-j721e-ufs.o
>   obj-$(CONFIG_UFS_PCI) += ufs-pci.o
>   obj-$(CONFIG_UFS_RENESAS) += ufs-renesas.o
> +obj-$(CONFIG_UFS_AMD_VERSAL2) += ufs-amd-versal2.o ufshcd-dwc.o
> diff --git a/drivers/ufs/ufs-amd-versal2.c b/drivers/ufs/ufs-amd-versal2.c
> new file mode 100644
> index 0000000000..bfd844e419
> --- /dev/null
> +++ b/drivers/ufs/ufs-amd-versal2.c
> @@ -0,0 +1,501 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2024 Advanced Micro Devices, Inc.
> + */
> +
> +#include <clk.h>
> +#include <dm.h>
> +#include <ufs.h>
> +#include <asm/io.h>
> +#include <dm/device_compat.h>
> +#include <zynqmp_firmware.h>
> +#include <linux/bitops.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/time.h>
> +#include <reset.h>
> +
> +#include "ufs.h"
> +#include "ufshcd-dwc.h"
> +#include "ufshci-dwc.h"
> +
> +#define VERSAL2_UFS_DEVICE_ID		4
> +
> +#define SRAM_CSR_INIT_DONE_MASK		BIT(0)
> +#define SRAM_CSR_EXT_LD_DONE_MASK	BIT(1)
> +#define SRAM_CSR_BYPASS_MASK		BIT(2)
> +
> +#define MPHY_FAST_RX_AFE_CAL		BIT(2)
> +#define MPHY_FW_CALIB_CFG_VAL		BIT(8)
> +
> +#define TX_RX_CFG_RDY_MASK		GENMASK(3, 0)
> +
> +#define TIMEOUT_MICROSEC		1000000L
> +
> +#define IOCTL_UFS_TXRX_CFGRDY_GET	40
> +#define IOCTL_UFS_SRAM_CSR_SEL		41
> +
> +#define PM_UFS_SRAM_CSR_WRITE		0
> +#define PM_UFS_SRAM_CSR_READ		1
> +
> +struct ufs_versal2_priv {
> +	struct ufs_hba *hba;
> +	struct reset_ctl *rstc;
> +	struct reset_ctl *rstphy;
> +	u32 phy_mode;
> +	u32 host_clk;
> +	u32 pd_dev_id;
> +	u8 attcompval0;
> +	u8 attcompval1;
> +	u8 ctlecompval0;
> +	u8 ctlecompval1;
> +};
> +
> +static int ufs_versal2_phy_reg_write(struct ufs_hba *hba, u32 addr, u32 val)
> +{
> +	static struct ufshcd_dme_attr_val phy_write_attrs[] = {
> +		{ UIC_ARG_MIB(CBCREGADDRLSB), 0, DME_LOCAL },
> +		{ UIC_ARG_MIB(CBCREGADDRMSB), 0, DME_LOCAL },
> +		{ UIC_ARG_MIB(CBCREGWRLSB), 0, DME_LOCAL },
> +		{ UIC_ARG_MIB(CBCREGWRMSB), 0, DME_LOCAL },
> +		{ UIC_ARG_MIB(CBCREGRDWRSEL), 1, DME_LOCAL },
> +		{ UIC_ARG_MIB(VS_MPHYCFGUPDT), 1, DME_LOCAL }
> +	};
> +
> +	phy_write_attrs[0].mib_val = (u8)addr;
> +	phy_write_attrs[1].mib_val = (u8)(addr >> 8);
> +	phy_write_attrs[2].mib_val = (u8)val;
> +	phy_write_attrs[3].mib_val = (u8)(val >> 8);
> +
> +	return ufshcd_dwc_dme_set_attrs(hba, phy_write_attrs, ARRAY_SIZE(phy_write_attrs));
> +}
> +
> +static int ufs_versal2_phy_reg_read(struct ufs_hba *hba, u32 addr, u32 *val)
> +{
> +	u32 mib_val;
> +	int ret;
> +	static struct ufshcd_dme_attr_val phy_read_attrs[] = {
> +		{ UIC_ARG_MIB(CBCREGADDRLSB), 0, DME_LOCAL },
> +		{ UIC_ARG_MIB(CBCREGADDRMSB), 0, DME_LOCAL },
> +		{ UIC_ARG_MIB(CBCREGRDWRSEL), 0, DME_LOCAL },
> +		{ UIC_ARG_MIB(VS_MPHYCFGUPDT), 1, DME_LOCAL }
> +	};
> +
> +	phy_read_attrs[0].mib_val = (u8)addr;
> +	phy_read_attrs[1].mib_val = (u8)(addr >> 8);
> +
> +	ret = ufshcd_dwc_dme_set_attrs(hba, phy_read_attrs, ARRAY_SIZE(phy_read_attrs));
> +	if (ret)
> +		return ret;
> +
> +	ret = ufshcd_dme_get(hba, UIC_ARG_MIB(CBCREGRDLSB), &mib_val);
> +	if (ret)
> +		return ret;
> +
> +	*val = mib_val;
> +	ret = ufshcd_dme_get(hba, UIC_ARG_MIB(CBCREGRDMSB), &mib_val);
> +	if (ret)
> +		return ret;
> +
> +	*val |= (mib_val << 8);
> +
> +	return 0;
> +}
> +
> +int versal2_pm_ufs_get_txrx_cfgrdy(u32 node_id, u32 *value)
> +{
> +	u32 ret_payload[PAYLOAD_ARG_CNT];
> +	int ret;
> +
> +	if (!value)
> +		return -EINVAL;
> +
> +	ret = xilinx_pm_request(PM_IOCTL, node_id, IOCTL_UFS_TXRX_CFGRDY_GET,
> +				0, 0, ret_payload);
> +	*value = ret_payload[1];
> +
> +	return ret;
> +}
> +
> +int versal2_pm_ufs_sram_csr_sel(u32 node_id, u32 type, u32 *value)
> +{
> +	u32 ret_payload[PAYLOAD_ARG_CNT];
> +	int ret;
> +
> +	if (!value)
> +		return -EINVAL;
> +
> +	if (type == PM_UFS_SRAM_CSR_READ) {
> +		ret =  xilinx_pm_request(PM_IOCTL, node_id, IOCTL_UFS_SRAM_CSR_SEL,
> +					 type, 0, ret_payload);
> +		*value = ret_payload[1];
> +	} else {
> +		ret = xilinx_pm_request(PM_IOCTL, node_id, IOCTL_UFS_SRAM_CSR_SEL,
> +					type, *value, 0);
> +	}
> +
> +	return ret;
> +}
> +
> +static int ufs_versal2_enable_phy(struct ufs_hba *hba)
> +{
> +	u32 offset, reg;
> +	int ret;
> +
> +	ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYDISABLE), 0);
> +	if (ret)
> +		return ret;
> +
> +	ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 1);
> +	if (ret)
> +		return ret;
> +
> +	/* Check Tx/Rx FSM states */
> +	for (offset = 0; offset < 2; offset++) {
> +		u32 time_left, mibsel;
> +
> +		time_left = TIMEOUT_MICROSEC;
> +		mibsel = UIC_ARG_MIB_SEL(MTX_FSM_STATE, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(offset));
> +		do {
> +			ret = ufshcd_dme_get(hba, mibsel, &reg);
> +			if (ret)
> +				return ret;
> +
> +			if (reg == TX_STATE_HIBERN8 || reg == TX_STATE_SLEEP ||
> +			    reg == TX_STATE_LSBURST)
> +				break;
> +
> +			time_left--;
> +			mdelay(5);
> +		} while (time_left);
> +
> +		if (!time_left) {
> +			dev_err(hba->dev, "Invalid Tx FSM state.\n");
> +			return -ETIMEDOUT;
> +		}
> +
> +		time_left = TIMEOUT_MICROSEC;
> +		mibsel = UIC_ARG_MIB_SEL(MRX_FSM_STATE, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(offset));
> +		do {
> +			ret = ufshcd_dme_get(hba, mibsel, &reg);
> +			if (ret)
> +				return ret;
> +
> +			if (reg == RX_STATE_HIBERN8 || reg == RX_STATE_SLEEP ||
> +			    reg == RX_STATE_LSBURST)
> +				break;
> +
> +			time_left--;
> +			mdelay(5);
> +		} while (time_left);
> +
> +		if (!time_left) {
> +			dev_err(hba->dev, "Invalid Rx FSM state.\n");
> +			return -ETIMEDOUT;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int ufs_versal2_setup_phy(struct ufs_hba *hba)
> +{
> +	struct ufs_versal2_priv *priv = dev_get_priv(hba->dev);
> +	int ret;
> +	u32 reg;
> +
> +	/* Bypass RX-AFE offset calibrations (ATT/CTLE) */
> +	ret = ufs_versal2_phy_reg_read(hba, FAST_FLAGS(0), &reg);
> +	if (ret)
> +		return ret;
> +
> +	reg |= MPHY_FAST_RX_AFE_CAL;
> +	ret = ufs_versal2_phy_reg_write(hba, FAST_FLAGS(0), reg);
> +	if (ret)
> +		return ret;
> +
> +	ret = ufs_versal2_phy_reg_read(hba, FAST_FLAGS(1), &reg);
> +	if (ret)
> +		return ret;
> +
> +	reg |= MPHY_FAST_RX_AFE_CAL;
> +	ret = ufs_versal2_phy_reg_write(hba, FAST_FLAGS(1), reg);
> +	if (ret)
> +		return ret;
> +
> +	/* Program ATT and CTLE compensation values */
> +	if (priv->attcompval0) {
> +		ret = ufs_versal2_phy_reg_write(hba, RX_AFE_ATT_IDAC(0), priv->attcompval0);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (priv->attcompval1) {
> +		ret = ufs_versal2_phy_reg_write(hba, RX_AFE_ATT_IDAC(1), priv->attcompval1);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (priv->ctlecompval0) {
> +		ret = ufs_versal2_phy_reg_write(hba, RX_AFE_CTLE_IDAC(0), priv->ctlecompval0);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (priv->ctlecompval1) {
> +		ret = ufs_versal2_phy_reg_write(hba, RX_AFE_CTLE_IDAC(1), priv->ctlecompval1);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = ufs_versal2_phy_reg_read(hba, FW_CALIB_CCFG(0), &reg);
> +	if (ret)
> +		return ret;
> +
> +	reg |= MPHY_FW_CALIB_CFG_VAL;
> +	ret = ufs_versal2_phy_reg_write(hba, FW_CALIB_CCFG(0), reg);
> +	if (ret)
> +		return ret;
> +
> +	ret = ufs_versal2_phy_reg_read(hba, FW_CALIB_CCFG(1), &reg);
> +	if (ret)
> +		return ret;
> +
> +	reg |= MPHY_FW_CALIB_CFG_VAL;
> +	return ufs_versal2_phy_reg_write(hba, FW_CALIB_CCFG(1), reg);
> +}
> +
> +static int ufs_versal2_phy_init(struct ufs_hba *hba)
> +{
> +	struct ufs_versal2_priv *priv = dev_get_priv(hba->dev);
> +	u32 reg, time_left;
> +	int ret;
> +	static const struct ufshcd_dme_attr_val rmmi_attrs[] = {
> +		{ UIC_ARG_MIB(CBREFCLKCTRL2), CBREFREFCLK_GATE_OVR_EN, DME_LOCAL },
> +		{ UIC_ARG_MIB(CBCRCTRL), 1, DME_LOCAL },
> +		{ UIC_ARG_MIB(CBC10DIRECTCONF2), 1, DME_LOCAL },
> +		{ UIC_ARG_MIB(VS_MPHYCFGUPDT), 1, DME_LOCAL }
> +	};
> +
> +	/* Wait for Tx/Rx config_rdy */
> +	time_left = TIMEOUT_MICROSEC;
> +	do {
> +		time_left--;
> +		ret = versal2_pm_ufs_get_txrx_cfgrdy(priv->pd_dev_id, &reg);
> +		if (ret)
> +			return ret;
> +
> +		reg &= TX_RX_CFG_RDY_MASK;
> +		if (!reg)
> +			break;
> +
> +		mdelay(5);
> +	} while (time_left);
> +
> +	if (!time_left) {
> +		dev_err(hba->dev, "Tx/Rx configuration signal busy.\n");
> +		return -ETIMEDOUT;
> +	}
> +
> +	ret = ufshcd_dwc_dme_set_attrs(hba, rmmi_attrs, ARRAY_SIZE(rmmi_attrs));
> +	if (ret)
> +		return ret;
> +
> +	/* DeAssert PHY reset */
> +	ret = reset_deassert(priv->rstphy);
> +	if (ret) {
> +		dev_err(hba->dev, "ufsphy reset deassert failed\n");
> +		return ret;
> +	}
> +
> +	/* Wait for SRAM init done */
> +	time_left = TIMEOUT_MICROSEC;
> +	do {
> +		time_left--;
> +		ret = versal2_pm_ufs_sram_csr_sel(priv->pd_dev_id,
> +						  PM_UFS_SRAM_CSR_READ, &reg);
> +		if (ret)
> +			return ret;
> +
> +		reg &= SRAM_CSR_INIT_DONE_MASK;
> +		if (reg)
> +			break;
> +
> +		mdelay(5);
> +	} while (time_left);
> +
> +	if (!time_left) {
> +		dev_err(hba->dev, "SRAM initialization failed.\n");
> +		return -ETIMEDOUT;
> +	}
> +
> +	ret = ufs_versal2_setup_phy(hba);
> +	if (ret)
> +		return ret;
> +
> +	return ufs_versal2_enable_phy(hba);
> +}
> +
> +static int ufs_versal2_init(struct ufs_hba *hba)
> +{
> +	struct ufs_versal2_priv *priv = dev_get_priv(hba->dev);
> +	struct clk clk;
> +	unsigned long core_clk_rate = 0;
> +	int ret = 0;
> +
> +	priv->phy_mode = UFSHCD_DWC_PHY_MODE_ROM;
> +	priv->pd_dev_id = VERSAL2_UFS_DEVICE_ID;
> +
> +	ret = clk_get_by_name(hba->dev, "core_clk", &clk);
> +	if (ret) {
> +		dev_err(hba->dev, "failed to get core_clk clock\n");
> +		return ret;
> +	}
> +
> +	core_clk_rate = clk_get_rate(&clk);
> +	if (IS_ERR_VALUE(core_clk_rate)) {
> +		dev_err(hba->dev, "%s: unable to find core_clk rate\n",
> +			__func__);
> +		return core_clk_rate;
> +	}
> +	priv->host_clk = core_clk_rate;
> +
> +	priv->rstc = devm_reset_control_get(hba->dev, "ufshc-rst");
> +	if (IS_ERR(priv->rstc)) {
> +		dev_err(hba->dev, "failed to get reset ctl: ufshc-rst\n");
> +		return PTR_ERR(priv->rstc);
> +	}
> +	priv->rstphy = devm_reset_control_get(hba->dev, "ufsphy-rst");
> +	if (IS_ERR(priv->rstphy)) {
> +		dev_err(hba->dev, "failed to get reset ctl: ufsphy-rst\n");
> +		return PTR_ERR(priv->rstphy);
> +	}
> +
> +	return ret;
> +}
> +
> +static int ufs_versal2_hce_enable_notify(struct ufs_hba *hba,
> +					 enum ufs_notify_change_status status)
> +{
> +	struct ufs_versal2_priv *priv = dev_get_priv(hba->dev);
> +	u32 sram_csr;
> +	int ret;
> +
> +	switch (status) {
> +	case PRE_CHANGE:
> +		/* Assert RST_UFS Reset for UFS block in PMX_IOU */
> +		ret = reset_assert(priv->rstc);
> +		if (ret) {
> +			dev_err(hba->dev, "ufshc reset assert failed, err = %d\n", ret);
> +			return ret;
> +		}
> +
> +		/* Assert PHY reset */
> +		ret = reset_assert(priv->rstphy);
> +		if (ret) {
> +			dev_err(hba->dev, "ufsphy reset assert failed, err = %d\n", ret);
> +			return ret;
> +		}
> +
> +		ret = versal2_pm_ufs_sram_csr_sel(priv->pd_dev_id,
> +						  PM_UFS_SRAM_CSR_READ, &sram_csr);
> +		if (ret)
> +			return ret;
> +
> +		if (!priv->phy_mode) {
> +			sram_csr &= ~SRAM_CSR_EXT_LD_DONE_MASK;
> +			sram_csr |= SRAM_CSR_BYPASS_MASK;
> +		} else {
> +			dev_err(hba->dev, "Invalid phy-mode %d.\n", priv->phy_mode);
> +			return -EINVAL;
> +		}
> +
> +		ret = versal2_pm_ufs_sram_csr_sel(priv->pd_dev_id,
> +						  PM_UFS_SRAM_CSR_WRITE, &sram_csr);
> +		if (ret)
> +			return ret;
> +
> +		/* De Assert RST_UFS Reset for UFS block in PMX_IOU */
> +		ret = reset_deassert(priv->rstc);
> +		if (ret)
> +			dev_err(hba->dev, "ufshc reset deassert failed, err = %d\n", ret);
> +
> +		break;
> +	case POST_CHANGE:
> +		ret = ufs_versal2_phy_init(hba);
> +		if (ret)
> +			dev_err(hba->dev, "Phy init failed (%d)\n", ret);
> +
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +static int ufs_versal2_link_startup_notify(struct ufs_hba *hba,
> +					   enum ufs_notify_change_status status)
> +{
> +	struct ufs_versal2_priv *priv = dev_get_priv(hba->dev);
> +	int ret = 0;
> +
> +	switch (status) {
> +	case PRE_CHANGE:
> +		if (priv->host_clk) {
> +			u32 core_clk_div = priv->host_clk / TIMEOUT_MICROSEC;
> +
> +			ufshcd_writel(hba, core_clk_div, DWC_UFS_REG_HCLKDIV);
> +		}
> +		break;
> +	case POST_CHANGE:
> +		ret = ufshcd_dwc_link_startup_notify(hba, status);
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +static struct ufs_hba_ops ufs_versal2_hba_ops = {
> +	.init = ufs_versal2_init,
> +	.link_startup_notify = ufs_versal2_link_startup_notify,
> +	.hce_enable_notify = ufs_versal2_hce_enable_notify,
> +};
> +
> +static int ufs_versal2_probe(struct udevice *dev)
> +{
> +	int ret;
> +
> +	/* Perform generic probe */
> +	ret = ufshcd_probe(dev, &ufs_versal2_hba_ops);
> +	if (ret)
> +		dev_err(dev, "ufshcd_probe() failed %d\n", ret);
> +
> +	return ret;
> +}
> +
> +static int ufs_versal2_bind(struct udevice *dev)
> +{
> +	struct udevice *scsi_dev;
> +
> +	return ufs_scsi_bind(dev, &scsi_dev);
> +}
> +
> +static const struct udevice_id ufs_versal2_ids[] = {
> +	{
> +		.compatible = "amd,versal2-ufs",
> +	},
> +	{},
> +};
> +
> +U_BOOT_DRIVER(ufs_versal2_pltfm) = {
> +	.name           = "ufs-versal2-pltfm",
> +	.id             = UCLASS_UFS,
> +	.of_match       = ufs_versal2_ids,
> +	.probe          = ufs_versal2_probe,
> +	.bind           = ufs_versal2_bind,
> +};
> diff --git a/drivers/ufs/ufshcd-dwc.h b/drivers/ufs/ufshcd-dwc.h
> index d997045d3c..fc1bcca8cc 100644
> --- a/drivers/ufs/ufshcd-dwc.h
> +++ b/drivers/ufs/ufshcd-dwc.h
> @@ -10,6 +10,52 @@
>   #ifndef _UFSHCD_DWC_H
>   #define _UFSHCD_DWC_H
>   
> +/* PHY modes */
> +#define UFSHCD_DWC_PHY_MODE_ROM		0
> +
> +/* RMMI Attributes */
> +#define CBREFCLKCTRL2		0x8132
> +#define CBCRCTRL		0x811F
> +#define CBC10DIRECTCONF2	0x810E
> +#define CBCREGADDRLSB		0x8116
> +#define CBCREGADDRMSB		0x8117
> +#define CBCREGWRLSB		0x8118
> +#define CBCREGWRMSB		0x8119
> +#define CBCREGRDLSB		0x811A
> +#define CBCREGRDMSB		0x811B
> +#define CBCREGRDWRSEL		0x811C
> +
> +#define CBREFREFCLK_GATE_OVR_EN		BIT(7)
> +
> +/* M-PHY Attributes */
> +#define MTX_FSM_STATE		0x41
> +#define MRX_FSM_STATE		0xC1
> +
> +/* M-PHY registers */
> +#define FAST_FLAGS(n)		(0x401C + ((n) * 0x100))
> +#define RX_AFE_ATT_IDAC(n)	(0x4000 + ((n) * 0x100))
> +#define RX_AFE_CTLE_IDAC(n)	(0x4001 + ((n) * 0x100))
> +#define FW_CALIB_CCFG(n)	(0x404D + ((n) * 0x100))
> +
> +/* Tx/Rx FSM state */
> +enum rx_fsm_state {
> +	RX_STATE_DISABLED = 0,
> +	RX_STATE_HIBERN8 = 1,
> +	RX_STATE_SLEEP = 2,
> +	RX_STATE_STALL = 3,
> +	RX_STATE_LSBURST = 4,
> +	RX_STATE_HSBURST = 5,
> +};
> +
> +enum tx_fsm_state {
> +	TX_STATE_DISABLED = 0,
> +	TX_STATE_HIBERN8 = 1,
> +	TX_STATE_SLEEP = 2,
> +	TX_STATE_STALL = 3,
> +	TX_STATE_LSBURST = 4,
> +	TX_STATE_HSBURST = 5,
> +};
> +
>   struct ufshcd_dme_attr_val {
>   	u32 attr_sel;
>   	u32 mib_val;
> diff --git a/drivers/ufs/unipro.h b/drivers/ufs/unipro.h
> index b30b17fa5a..2f5726d4d3 100644
> --- a/drivers/ufs/unipro.h
> +++ b/drivers/ufs/unipro.h
> @@ -148,6 +148,7 @@
>   #define VS_MPHYCFGUPDT		0xD085
>   #define VS_DEBUGOMC		0xD09E
>   #define VS_POWERSTATE		0xD083
> +#define VS_MPHYDISABLE		0xD0C1
>   
>   #define PA_GRANULARITY_MIN_VAL	1
>   #define PA_GRANULARITY_MAX_VAL	6

Looks fine!

Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>

Neil
diff mbox series

Patch

diff --git a/drivers/ufs/Kconfig b/drivers/ufs/Kconfig
index 7da46faed6..ad5d0316f6 100644
--- a/drivers/ufs/Kconfig
+++ b/drivers/ufs/Kconfig
@@ -41,4 +41,12 @@  config UFS_RENESAS
 	  UFS host on Renesas needs some vendor specific configuration before
 	  accessing the hardware.
 
+config UFS_AMD_VERSAL2
+	bool "AMD Versal Gen 2 UFS controller platform driver"
+	depends on UFS && ZYNQMP_FIRMWARE
+	help
+	  This selects the AMD specific additions to UFSHCD platform driver.
+	  UFS host on AMD needs some vendor specific configuration before accessing
+	  the hardware.
+
 endmenu
diff --git a/drivers/ufs/Makefile b/drivers/ufs/Makefile
index 67c42621ab..745de5c3db 100644
--- a/drivers/ufs/Makefile
+++ b/drivers/ufs/Makefile
@@ -8,3 +8,4 @@  obj-$(CONFIG_CADENCE_UFS) += cdns-platform.o
 obj-$(CONFIG_TI_J721E_UFS) += ti-j721e-ufs.o
 obj-$(CONFIG_UFS_PCI) += ufs-pci.o
 obj-$(CONFIG_UFS_RENESAS) += ufs-renesas.o
+obj-$(CONFIG_UFS_AMD_VERSAL2) += ufs-amd-versal2.o ufshcd-dwc.o
diff --git a/drivers/ufs/ufs-amd-versal2.c b/drivers/ufs/ufs-amd-versal2.c
new file mode 100644
index 0000000000..bfd844e419
--- /dev/null
+++ b/drivers/ufs/ufs-amd-versal2.c
@@ -0,0 +1,501 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 Advanced Micro Devices, Inc.
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <ufs.h>
+#include <asm/io.h>
+#include <dm/device_compat.h>
+#include <zynqmp_firmware.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/time.h>
+#include <reset.h>
+
+#include "ufs.h"
+#include "ufshcd-dwc.h"
+#include "ufshci-dwc.h"
+
+#define VERSAL2_UFS_DEVICE_ID		4
+
+#define SRAM_CSR_INIT_DONE_MASK		BIT(0)
+#define SRAM_CSR_EXT_LD_DONE_MASK	BIT(1)
+#define SRAM_CSR_BYPASS_MASK		BIT(2)
+
+#define MPHY_FAST_RX_AFE_CAL		BIT(2)
+#define MPHY_FW_CALIB_CFG_VAL		BIT(8)
+
+#define TX_RX_CFG_RDY_MASK		GENMASK(3, 0)
+
+#define TIMEOUT_MICROSEC		1000000L
+
+#define IOCTL_UFS_TXRX_CFGRDY_GET	40
+#define IOCTL_UFS_SRAM_CSR_SEL		41
+
+#define PM_UFS_SRAM_CSR_WRITE		0
+#define PM_UFS_SRAM_CSR_READ		1
+
+struct ufs_versal2_priv {
+	struct ufs_hba *hba;
+	struct reset_ctl *rstc;
+	struct reset_ctl *rstphy;
+	u32 phy_mode;
+	u32 host_clk;
+	u32 pd_dev_id;
+	u8 attcompval0;
+	u8 attcompval1;
+	u8 ctlecompval0;
+	u8 ctlecompval1;
+};
+
+static int ufs_versal2_phy_reg_write(struct ufs_hba *hba, u32 addr, u32 val)
+{
+	static struct ufshcd_dme_attr_val phy_write_attrs[] = {
+		{ UIC_ARG_MIB(CBCREGADDRLSB), 0, DME_LOCAL },
+		{ UIC_ARG_MIB(CBCREGADDRMSB), 0, DME_LOCAL },
+		{ UIC_ARG_MIB(CBCREGWRLSB), 0, DME_LOCAL },
+		{ UIC_ARG_MIB(CBCREGWRMSB), 0, DME_LOCAL },
+		{ UIC_ARG_MIB(CBCREGRDWRSEL), 1, DME_LOCAL },
+		{ UIC_ARG_MIB(VS_MPHYCFGUPDT), 1, DME_LOCAL }
+	};
+
+	phy_write_attrs[0].mib_val = (u8)addr;
+	phy_write_attrs[1].mib_val = (u8)(addr >> 8);
+	phy_write_attrs[2].mib_val = (u8)val;
+	phy_write_attrs[3].mib_val = (u8)(val >> 8);
+
+	return ufshcd_dwc_dme_set_attrs(hba, phy_write_attrs, ARRAY_SIZE(phy_write_attrs));
+}
+
+static int ufs_versal2_phy_reg_read(struct ufs_hba *hba, u32 addr, u32 *val)
+{
+	u32 mib_val;
+	int ret;
+	static struct ufshcd_dme_attr_val phy_read_attrs[] = {
+		{ UIC_ARG_MIB(CBCREGADDRLSB), 0, DME_LOCAL },
+		{ UIC_ARG_MIB(CBCREGADDRMSB), 0, DME_LOCAL },
+		{ UIC_ARG_MIB(CBCREGRDWRSEL), 0, DME_LOCAL },
+		{ UIC_ARG_MIB(VS_MPHYCFGUPDT), 1, DME_LOCAL }
+	};
+
+	phy_read_attrs[0].mib_val = (u8)addr;
+	phy_read_attrs[1].mib_val = (u8)(addr >> 8);
+
+	ret = ufshcd_dwc_dme_set_attrs(hba, phy_read_attrs, ARRAY_SIZE(phy_read_attrs));
+	if (ret)
+		return ret;
+
+	ret = ufshcd_dme_get(hba, UIC_ARG_MIB(CBCREGRDLSB), &mib_val);
+	if (ret)
+		return ret;
+
+	*val = mib_val;
+	ret = ufshcd_dme_get(hba, UIC_ARG_MIB(CBCREGRDMSB), &mib_val);
+	if (ret)
+		return ret;
+
+	*val |= (mib_val << 8);
+
+	return 0;
+}
+
+int versal2_pm_ufs_get_txrx_cfgrdy(u32 node_id, u32 *value)
+{
+	u32 ret_payload[PAYLOAD_ARG_CNT];
+	int ret;
+
+	if (!value)
+		return -EINVAL;
+
+	ret = xilinx_pm_request(PM_IOCTL, node_id, IOCTL_UFS_TXRX_CFGRDY_GET,
+				0, 0, ret_payload);
+	*value = ret_payload[1];
+
+	return ret;
+}
+
+int versal2_pm_ufs_sram_csr_sel(u32 node_id, u32 type, u32 *value)
+{
+	u32 ret_payload[PAYLOAD_ARG_CNT];
+	int ret;
+
+	if (!value)
+		return -EINVAL;
+
+	if (type == PM_UFS_SRAM_CSR_READ) {
+		ret =  xilinx_pm_request(PM_IOCTL, node_id, IOCTL_UFS_SRAM_CSR_SEL,
+					 type, 0, ret_payload);
+		*value = ret_payload[1];
+	} else {
+		ret = xilinx_pm_request(PM_IOCTL, node_id, IOCTL_UFS_SRAM_CSR_SEL,
+					type, *value, 0);
+	}
+
+	return ret;
+}
+
+static int ufs_versal2_enable_phy(struct ufs_hba *hba)
+{
+	u32 offset, reg;
+	int ret;
+
+	ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYDISABLE), 0);
+	if (ret)
+		return ret;
+
+	ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 1);
+	if (ret)
+		return ret;
+
+	/* Check Tx/Rx FSM states */
+	for (offset = 0; offset < 2; offset++) {
+		u32 time_left, mibsel;
+
+		time_left = TIMEOUT_MICROSEC;
+		mibsel = UIC_ARG_MIB_SEL(MTX_FSM_STATE, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(offset));
+		do {
+			ret = ufshcd_dme_get(hba, mibsel, &reg);
+			if (ret)
+				return ret;
+
+			if (reg == TX_STATE_HIBERN8 || reg == TX_STATE_SLEEP ||
+			    reg == TX_STATE_LSBURST)
+				break;
+
+			time_left--;
+			mdelay(5);
+		} while (time_left);
+
+		if (!time_left) {
+			dev_err(hba->dev, "Invalid Tx FSM state.\n");
+			return -ETIMEDOUT;
+		}
+
+		time_left = TIMEOUT_MICROSEC;
+		mibsel = UIC_ARG_MIB_SEL(MRX_FSM_STATE, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(offset));
+		do {
+			ret = ufshcd_dme_get(hba, mibsel, &reg);
+			if (ret)
+				return ret;
+
+			if (reg == RX_STATE_HIBERN8 || reg == RX_STATE_SLEEP ||
+			    reg == RX_STATE_LSBURST)
+				break;
+
+			time_left--;
+			mdelay(5);
+		} while (time_left);
+
+		if (!time_left) {
+			dev_err(hba->dev, "Invalid Rx FSM state.\n");
+			return -ETIMEDOUT;
+		}
+	}
+
+	return 0;
+}
+
+static int ufs_versal2_setup_phy(struct ufs_hba *hba)
+{
+	struct ufs_versal2_priv *priv = dev_get_priv(hba->dev);
+	int ret;
+	u32 reg;
+
+	/* Bypass RX-AFE offset calibrations (ATT/CTLE) */
+	ret = ufs_versal2_phy_reg_read(hba, FAST_FLAGS(0), &reg);
+	if (ret)
+		return ret;
+
+	reg |= MPHY_FAST_RX_AFE_CAL;
+	ret = ufs_versal2_phy_reg_write(hba, FAST_FLAGS(0), reg);
+	if (ret)
+		return ret;
+
+	ret = ufs_versal2_phy_reg_read(hba, FAST_FLAGS(1), &reg);
+	if (ret)
+		return ret;
+
+	reg |= MPHY_FAST_RX_AFE_CAL;
+	ret = ufs_versal2_phy_reg_write(hba, FAST_FLAGS(1), reg);
+	if (ret)
+		return ret;
+
+	/* Program ATT and CTLE compensation values */
+	if (priv->attcompval0) {
+		ret = ufs_versal2_phy_reg_write(hba, RX_AFE_ATT_IDAC(0), priv->attcompval0);
+		if (ret)
+			return ret;
+	}
+
+	if (priv->attcompval1) {
+		ret = ufs_versal2_phy_reg_write(hba, RX_AFE_ATT_IDAC(1), priv->attcompval1);
+		if (ret)
+			return ret;
+	}
+
+	if (priv->ctlecompval0) {
+		ret = ufs_versal2_phy_reg_write(hba, RX_AFE_CTLE_IDAC(0), priv->ctlecompval0);
+		if (ret)
+			return ret;
+	}
+
+	if (priv->ctlecompval1) {
+		ret = ufs_versal2_phy_reg_write(hba, RX_AFE_CTLE_IDAC(1), priv->ctlecompval1);
+		if (ret)
+			return ret;
+	}
+
+	ret = ufs_versal2_phy_reg_read(hba, FW_CALIB_CCFG(0), &reg);
+	if (ret)
+		return ret;
+
+	reg |= MPHY_FW_CALIB_CFG_VAL;
+	ret = ufs_versal2_phy_reg_write(hba, FW_CALIB_CCFG(0), reg);
+	if (ret)
+		return ret;
+
+	ret = ufs_versal2_phy_reg_read(hba, FW_CALIB_CCFG(1), &reg);
+	if (ret)
+		return ret;
+
+	reg |= MPHY_FW_CALIB_CFG_VAL;
+	return ufs_versal2_phy_reg_write(hba, FW_CALIB_CCFG(1), reg);
+}
+
+static int ufs_versal2_phy_init(struct ufs_hba *hba)
+{
+	struct ufs_versal2_priv *priv = dev_get_priv(hba->dev);
+	u32 reg, time_left;
+	int ret;
+	static const struct ufshcd_dme_attr_val rmmi_attrs[] = {
+		{ UIC_ARG_MIB(CBREFCLKCTRL2), CBREFREFCLK_GATE_OVR_EN, DME_LOCAL },
+		{ UIC_ARG_MIB(CBCRCTRL), 1, DME_LOCAL },
+		{ UIC_ARG_MIB(CBC10DIRECTCONF2), 1, DME_LOCAL },
+		{ UIC_ARG_MIB(VS_MPHYCFGUPDT), 1, DME_LOCAL }
+	};
+
+	/* Wait for Tx/Rx config_rdy */
+	time_left = TIMEOUT_MICROSEC;
+	do {
+		time_left--;
+		ret = versal2_pm_ufs_get_txrx_cfgrdy(priv->pd_dev_id, &reg);
+		if (ret)
+			return ret;
+
+		reg &= TX_RX_CFG_RDY_MASK;
+		if (!reg)
+			break;
+
+		mdelay(5);
+	} while (time_left);
+
+	if (!time_left) {
+		dev_err(hba->dev, "Tx/Rx configuration signal busy.\n");
+		return -ETIMEDOUT;
+	}
+
+	ret = ufshcd_dwc_dme_set_attrs(hba, rmmi_attrs, ARRAY_SIZE(rmmi_attrs));
+	if (ret)
+		return ret;
+
+	/* DeAssert PHY reset */
+	ret = reset_deassert(priv->rstphy);
+	if (ret) {
+		dev_err(hba->dev, "ufsphy reset deassert failed\n");
+		return ret;
+	}
+
+	/* Wait for SRAM init done */
+	time_left = TIMEOUT_MICROSEC;
+	do {
+		time_left--;
+		ret = versal2_pm_ufs_sram_csr_sel(priv->pd_dev_id,
+						  PM_UFS_SRAM_CSR_READ, &reg);
+		if (ret)
+			return ret;
+
+		reg &= SRAM_CSR_INIT_DONE_MASK;
+		if (reg)
+			break;
+
+		mdelay(5);
+	} while (time_left);
+
+	if (!time_left) {
+		dev_err(hba->dev, "SRAM initialization failed.\n");
+		return -ETIMEDOUT;
+	}
+
+	ret = ufs_versal2_setup_phy(hba);
+	if (ret)
+		return ret;
+
+	return ufs_versal2_enable_phy(hba);
+}
+
+static int ufs_versal2_init(struct ufs_hba *hba)
+{
+	struct ufs_versal2_priv *priv = dev_get_priv(hba->dev);
+	struct clk clk;
+	unsigned long core_clk_rate = 0;
+	int ret = 0;
+
+	priv->phy_mode = UFSHCD_DWC_PHY_MODE_ROM;
+	priv->pd_dev_id = VERSAL2_UFS_DEVICE_ID;
+
+	ret = clk_get_by_name(hba->dev, "core_clk", &clk);
+	if (ret) {
+		dev_err(hba->dev, "failed to get core_clk clock\n");
+		return ret;
+	}
+
+	core_clk_rate = clk_get_rate(&clk);
+	if (IS_ERR_VALUE(core_clk_rate)) {
+		dev_err(hba->dev, "%s: unable to find core_clk rate\n",
+			__func__);
+		return core_clk_rate;
+	}
+	priv->host_clk = core_clk_rate;
+
+	priv->rstc = devm_reset_control_get(hba->dev, "ufshc-rst");
+	if (IS_ERR(priv->rstc)) {
+		dev_err(hba->dev, "failed to get reset ctl: ufshc-rst\n");
+		return PTR_ERR(priv->rstc);
+	}
+	priv->rstphy = devm_reset_control_get(hba->dev, "ufsphy-rst");
+	if (IS_ERR(priv->rstphy)) {
+		dev_err(hba->dev, "failed to get reset ctl: ufsphy-rst\n");
+		return PTR_ERR(priv->rstphy);
+	}
+
+	return ret;
+}
+
+static int ufs_versal2_hce_enable_notify(struct ufs_hba *hba,
+					 enum ufs_notify_change_status status)
+{
+	struct ufs_versal2_priv *priv = dev_get_priv(hba->dev);
+	u32 sram_csr;
+	int ret;
+
+	switch (status) {
+	case PRE_CHANGE:
+		/* Assert RST_UFS Reset for UFS block in PMX_IOU */
+		ret = reset_assert(priv->rstc);
+		if (ret) {
+			dev_err(hba->dev, "ufshc reset assert failed, err = %d\n", ret);
+			return ret;
+		}
+
+		/* Assert PHY reset */
+		ret = reset_assert(priv->rstphy);
+		if (ret) {
+			dev_err(hba->dev, "ufsphy reset assert failed, err = %d\n", ret);
+			return ret;
+		}
+
+		ret = versal2_pm_ufs_sram_csr_sel(priv->pd_dev_id,
+						  PM_UFS_SRAM_CSR_READ, &sram_csr);
+		if (ret)
+			return ret;
+
+		if (!priv->phy_mode) {
+			sram_csr &= ~SRAM_CSR_EXT_LD_DONE_MASK;
+			sram_csr |= SRAM_CSR_BYPASS_MASK;
+		} else {
+			dev_err(hba->dev, "Invalid phy-mode %d.\n", priv->phy_mode);
+			return -EINVAL;
+		}
+
+		ret = versal2_pm_ufs_sram_csr_sel(priv->pd_dev_id,
+						  PM_UFS_SRAM_CSR_WRITE, &sram_csr);
+		if (ret)
+			return ret;
+
+		/* De Assert RST_UFS Reset for UFS block in PMX_IOU */
+		ret = reset_deassert(priv->rstc);
+		if (ret)
+			dev_err(hba->dev, "ufshc reset deassert failed, err = %d\n", ret);
+
+		break;
+	case POST_CHANGE:
+		ret = ufs_versal2_phy_init(hba);
+		if (ret)
+			dev_err(hba->dev, "Phy init failed (%d)\n", ret);
+
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int ufs_versal2_link_startup_notify(struct ufs_hba *hba,
+					   enum ufs_notify_change_status status)
+{
+	struct ufs_versal2_priv *priv = dev_get_priv(hba->dev);
+	int ret = 0;
+
+	switch (status) {
+	case PRE_CHANGE:
+		if (priv->host_clk) {
+			u32 core_clk_div = priv->host_clk / TIMEOUT_MICROSEC;
+
+			ufshcd_writel(hba, core_clk_div, DWC_UFS_REG_HCLKDIV);
+		}
+		break;
+	case POST_CHANGE:
+		ret = ufshcd_dwc_link_startup_notify(hba, status);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static struct ufs_hba_ops ufs_versal2_hba_ops = {
+	.init = ufs_versal2_init,
+	.link_startup_notify = ufs_versal2_link_startup_notify,
+	.hce_enable_notify = ufs_versal2_hce_enable_notify,
+};
+
+static int ufs_versal2_probe(struct udevice *dev)
+{
+	int ret;
+
+	/* Perform generic probe */
+	ret = ufshcd_probe(dev, &ufs_versal2_hba_ops);
+	if (ret)
+		dev_err(dev, "ufshcd_probe() failed %d\n", ret);
+
+	return ret;
+}
+
+static int ufs_versal2_bind(struct udevice *dev)
+{
+	struct udevice *scsi_dev;
+
+	return ufs_scsi_bind(dev, &scsi_dev);
+}
+
+static const struct udevice_id ufs_versal2_ids[] = {
+	{
+		.compatible = "amd,versal2-ufs",
+	},
+	{},
+};
+
+U_BOOT_DRIVER(ufs_versal2_pltfm) = {
+	.name           = "ufs-versal2-pltfm",
+	.id             = UCLASS_UFS,
+	.of_match       = ufs_versal2_ids,
+	.probe          = ufs_versal2_probe,
+	.bind           = ufs_versal2_bind,
+};
diff --git a/drivers/ufs/ufshcd-dwc.h b/drivers/ufs/ufshcd-dwc.h
index d997045d3c..fc1bcca8cc 100644
--- a/drivers/ufs/ufshcd-dwc.h
+++ b/drivers/ufs/ufshcd-dwc.h
@@ -10,6 +10,52 @@ 
 #ifndef _UFSHCD_DWC_H
 #define _UFSHCD_DWC_H
 
+/* PHY modes */
+#define UFSHCD_DWC_PHY_MODE_ROM		0
+
+/* RMMI Attributes */
+#define CBREFCLKCTRL2		0x8132
+#define CBCRCTRL		0x811F
+#define CBC10DIRECTCONF2	0x810E
+#define CBCREGADDRLSB		0x8116
+#define CBCREGADDRMSB		0x8117
+#define CBCREGWRLSB		0x8118
+#define CBCREGWRMSB		0x8119
+#define CBCREGRDLSB		0x811A
+#define CBCREGRDMSB		0x811B
+#define CBCREGRDWRSEL		0x811C
+
+#define CBREFREFCLK_GATE_OVR_EN		BIT(7)
+
+/* M-PHY Attributes */
+#define MTX_FSM_STATE		0x41
+#define MRX_FSM_STATE		0xC1
+
+/* M-PHY registers */
+#define FAST_FLAGS(n)		(0x401C + ((n) * 0x100))
+#define RX_AFE_ATT_IDAC(n)	(0x4000 + ((n) * 0x100))
+#define RX_AFE_CTLE_IDAC(n)	(0x4001 + ((n) * 0x100))
+#define FW_CALIB_CCFG(n)	(0x404D + ((n) * 0x100))
+
+/* Tx/Rx FSM state */
+enum rx_fsm_state {
+	RX_STATE_DISABLED = 0,
+	RX_STATE_HIBERN8 = 1,
+	RX_STATE_SLEEP = 2,
+	RX_STATE_STALL = 3,
+	RX_STATE_LSBURST = 4,
+	RX_STATE_HSBURST = 5,
+};
+
+enum tx_fsm_state {
+	TX_STATE_DISABLED = 0,
+	TX_STATE_HIBERN8 = 1,
+	TX_STATE_SLEEP = 2,
+	TX_STATE_STALL = 3,
+	TX_STATE_LSBURST = 4,
+	TX_STATE_HSBURST = 5,
+};
+
 struct ufshcd_dme_attr_val {
 	u32 attr_sel;
 	u32 mib_val;
diff --git a/drivers/ufs/unipro.h b/drivers/ufs/unipro.h
index b30b17fa5a..2f5726d4d3 100644
--- a/drivers/ufs/unipro.h
+++ b/drivers/ufs/unipro.h
@@ -148,6 +148,7 @@ 
 #define VS_MPHYCFGUPDT		0xD085
 #define VS_DEBUGOMC		0xD09E
 #define VS_POWERSTATE		0xD083
+#define VS_MPHYDISABLE		0xD0C1
 
 #define PA_GRANULARITY_MIN_VAL	1
 #define PA_GRANULARITY_MAX_VAL	6