]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
ufs: ufs-amd-versal2: Add support for AMD UFS controller
authorVenkatesh Yadav Abbarapu <venkatesh.abbarapu@amd.com>
Fri, 20 Sep 2024 04:16:50 +0000 (09:46 +0530)
committerNeil Armstrong <neil.armstrong@linaro.org>
Mon, 14 Oct 2024 06:55:35 +0000 (08:55 +0200)
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>
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Link: https://lore.kernel.org/r/20240920041651.18173-3-venkatesh.abbarapu@amd.com
Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
drivers/ufs/Kconfig
drivers/ufs/Makefile
drivers/ufs/ufs-amd-versal2.c [new file with mode: 0644]
drivers/ufs/ufshcd-dwc.h
drivers/ufs/unipro.h

index 7da46faed6be75d50a07a0c0c8225b3eb6718782..ad5d0316f68717786f816d5c5d1c9fea44175777 100644 (file)
@@ -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
index 67c42621abae918fa997c1ad6b800b5d8e97e385..745de5c3db3cdf34e200b3bbd4607e21b7b874a1 100644 (file)
@@ -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 (file)
index 0000000..bfd844e
--- /dev/null
@@ -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,
+};
index d997045d3ca3219ab9f0c187a0e997e4fd935834..fc1bcca8ccba23751878fa63e343da01e70a8b10 100644 (file)
 #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;
index b30b17fa5ad61121ac4f93edf0d1abd9543de54d..2f5726d4d3e029120761e7796d822badc32fb501 100644 (file)
 #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