| // 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, ®); |
| 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, ®); |
| 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), ®); |
| 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), ®); |
| 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), ®); |
| 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), ®); |
| 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, ®); |
| 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, ®); |
| 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, |
| }; |