// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2025 Advanced Micro Devices, Inc. * * Authors: Sai Krishna Potthuri */ #include #include #include #include #include #include #include #include #include #include "ufshcd-dwc.h" #include "ufshcd-pltfrm.h" #include "ufshci-dwc.h" /* PHY modes */ #define UFSHCD_DWC_PHY_MODE_ROM 0 #define MPHY_FAST_RX_AFE_CAL BIT(2) #define MPHY_FW_CALIB_CFG_VAL BIT(8) #define MPHY_RX_OVRD_EN BIT(3) #define MPHY_RX_OVRD_VAL BIT(2) #define MPHY_RX_ACK_MASK BIT(0) #define TIMEOUT_MICROSEC 1000000 struct ufs_versal2_host { struct ufs_hba *hba; struct reset_control *rstc; struct reset_control *rstphy; u32 phy_mode; unsigned long host_clk; 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; } 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--; usleep_range(1, 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--; usleep_range(1, 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_host *host = ufshcd_get_variant(hba); 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 (host->attcompval0) { ret = ufs_versal2_phy_reg_write(hba, RX_AFE_ATT_IDAC(0), host->attcompval0); if (ret) return ret; } if (host->attcompval1) { ret = ufs_versal2_phy_reg_write(hba, RX_AFE_ATT_IDAC(1), host->attcompval1); if (ret) return ret; } if (host->ctlecompval0) { ret = ufs_versal2_phy_reg_write(hba, RX_AFE_CTLE_IDAC(0), host->ctlecompval0); if (ret) return ret; } if (host->ctlecompval1) { ret = ufs_versal2_phy_reg_write(hba, RX_AFE_CTLE_IDAC(1), host->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_host *host = ufshcd_get_variant(hba); u32 time_left; bool is_ready; 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 = zynqmp_pm_is_mphy_tx_rx_config_ready(&is_ready); if (ret) return ret; if (!is_ready) break; usleep_range(1, 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; ret = reset_control_deassert(host->rstphy); if (ret) { dev_err(hba->dev, "ufsphy reset deassert failed, err = %d\n", ret); return ret; } /* Wait for SRAM init done */ time_left = TIMEOUT_MICROSEC; do { time_left--; ret = zynqmp_pm_is_sram_init_done(&is_ready); if (ret) return ret; if (is_ready) break; usleep_range(1, 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_host *host; struct device *dev = hba->dev; struct ufs_clk_info *clki; int ret; u32 cal; host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL); if (!host) return -ENOMEM; host->hba = hba; ufshcd_set_variant(hba, host); host->phy_mode = UFSHCD_DWC_PHY_MODE_ROM; list_for_each_entry(clki, &hba->clk_list_head, list) { if (!strcmp(clki->name, "core")) host->host_clk = clk_get_rate(clki->clk); } host->rstc = devm_reset_control_get_exclusive(dev, "host"); if (IS_ERR(host->rstc)) { dev_err(dev, "failed to get reset ctrl: host\n"); return PTR_ERR(host->rstc); } host->rstphy = devm_reset_control_get_exclusive(dev, "phy"); if (IS_ERR(host->rstphy)) { dev_err(dev, "failed to get reset ctrl: phy\n"); return PTR_ERR(host->rstphy); } ret = reset_control_assert(host->rstc); if (ret) { dev_err(hba->dev, "host reset assert failed, err = %d\n", ret); return ret; } ret = reset_control_assert(host->rstphy); if (ret) { dev_err(hba->dev, "phy reset assert failed, err = %d\n", ret); return ret; } ret = zynqmp_pm_set_sram_bypass(); if (ret) { dev_err(dev, "Bypass SRAM interface failed, err = %d\n", ret); return ret; } ret = reset_control_deassert(host->rstc); if (ret) dev_err(hba->dev, "host reset deassert failed, err = %d\n", ret); ret = zynqmp_pm_get_ufs_calibration_values(&cal); if (ret) { dev_err(dev, "failed to read calibration values\n"); return ret; } host->attcompval0 = (u8)cal; host->attcompval1 = (u8)(cal >> 8); host->ctlecompval0 = (u8)(cal >> 16); host->ctlecompval1 = (u8)(cal >> 24); hba->quirks |= UFSHCD_QUIRK_SKIP_DEF_UNIPRO_TIMEOUT_SETTING; return 0; } static int ufs_versal2_hce_enable_notify(struct ufs_hba *hba, enum ufs_notify_change_status status) { int ret = 0; if (status == PRE_CHANGE) { ret = ufs_versal2_phy_init(hba); if (ret) dev_err(hba->dev, "Phy init failed (%d)\n", ret); } return ret; } static int ufs_versal2_link_startup_notify(struct ufs_hba *hba, enum ufs_notify_change_status status) { struct ufs_versal2_host *host = ufshcd_get_variant(hba); int ret = 0; switch (status) { case PRE_CHANGE: if (host->host_clk) ufshcd_writel(hba, host->host_clk / 1000000, DWC_UFS_REG_HCLKDIV); break; case POST_CHANGE: ret = ufshcd_dwc_link_startup_notify(hba, status); break; default: ret = -EINVAL; break; } return ret; } static int ufs_versal2_phy_ratesel(struct ufs_hba *hba, u32 activelanes, u32 rx_req) { u32 time_left, reg, lane; int ret; for (lane = 0; lane < activelanes; lane++) { time_left = TIMEOUT_MICROSEC; ret = ufs_versal2_phy_reg_read(hba, RX_OVRD_IN_1(lane), ®); if (ret) return ret; reg |= MPHY_RX_OVRD_EN; if (rx_req) reg |= MPHY_RX_OVRD_VAL; else reg &= ~MPHY_RX_OVRD_VAL; ret = ufs_versal2_phy_reg_write(hba, RX_OVRD_IN_1(lane), reg); if (ret) return ret; do { ret = ufs_versal2_phy_reg_read(hba, RX_PCS_OUT(lane), ®); if (ret) return ret; reg &= MPHY_RX_ACK_MASK; if (reg == rx_req) break; time_left--; usleep_range(1, 5); } while (time_left); if (!time_left) { dev_err(hba->dev, "Invalid Rx Ack value.\n"); return -ETIMEDOUT; } } return 0; } static int ufs_versal2_pwr_change_notify(struct ufs_hba *hba, enum ufs_notify_change_status status, const struct ufs_pa_layer_attr *dev_max_params, struct ufs_pa_layer_attr *dev_req_params) { struct ufs_versal2_host *host = ufshcd_get_variant(hba); u32 lane, reg, rate = 0; int ret = 0; if (status == PRE_CHANGE) { memcpy(dev_req_params, dev_max_params, sizeof(struct ufs_pa_layer_attr)); /* If it is not a calibrated part, switch PWRMODE to SLOW_MODE */ if (!host->attcompval0 && !host->attcompval1 && !host->ctlecompval0 && !host->ctlecompval1) { dev_req_params->pwr_rx = SLOW_MODE; dev_req_params->pwr_tx = SLOW_MODE; return 0; } if (dev_req_params->pwr_rx == SLOW_MODE || dev_req_params->pwr_rx == SLOWAUTO_MODE) return 0; if (dev_req_params->hs_rate == PA_HS_MODE_B) rate = 1; /* Select the rate */ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(CBRATESEL), rate); if (ret) return ret; ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 1); if (ret) return ret; ret = ufs_versal2_phy_ratesel(hba, dev_req_params->lane_tx, 1); if (ret) return ret; ret = ufs_versal2_phy_ratesel(hba, dev_req_params->lane_tx, 0); if (ret) return ret; /* Remove rx_req override */ for (lane = 0; lane < dev_req_params->lane_tx; lane++) { ret = ufs_versal2_phy_reg_read(hba, RX_OVRD_IN_1(lane), ®); if (ret) return ret; reg &= ~MPHY_RX_OVRD_EN; ret = ufs_versal2_phy_reg_write(hba, RX_OVRD_IN_1(lane), reg); if (ret) return ret; } if (dev_req_params->lane_tx == UFS_LANE_2 && dev_req_params->lane_rx == UFS_LANE_2) ret = ufshcd_dme_configure_adapt(hba, dev_req_params->gear_tx, PA_INITIAL_ADAPT); } return ret; } static struct ufs_hba_variant_ops ufs_versal2_hba_vops = { .name = "ufs-versal2-pltfm", .init = ufs_versal2_init, .link_startup_notify = ufs_versal2_link_startup_notify, .hce_enable_notify = ufs_versal2_hce_enable_notify, .pwr_change_notify = ufs_versal2_pwr_change_notify, }; static const struct of_device_id ufs_versal2_pltfm_match[] = { { .compatible = "amd,versal2-ufs", .data = &ufs_versal2_hba_vops, }, { }, }; MODULE_DEVICE_TABLE(of, ufs_versal2_pltfm_match); static int ufs_versal2_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; int ret; /* Perform generic probe */ ret = ufshcd_pltfrm_init(pdev, &ufs_versal2_hba_vops); if (ret) dev_err(dev, "ufshcd_pltfrm_init() failed %d\n", ret); return ret; } static void ufs_versal2_remove(struct platform_device *pdev) { struct ufs_hba *hba = platform_get_drvdata(pdev); pm_runtime_get_sync(&(pdev)->dev); ufshcd_remove(hba); } static const struct dev_pm_ops ufs_versal2_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 struct platform_driver ufs_versal2_pltfm = { .probe = ufs_versal2_probe, .remove = ufs_versal2_remove, .driver = { .name = "ufshcd-versal2", .pm = &ufs_versal2_pm_ops, .of_match_table = of_match_ptr(ufs_versal2_pltfm_match), }, }; module_platform_driver(ufs_versal2_pltfm); MODULE_AUTHOR("Sai Krishna Potthuri "); MODULE_DESCRIPTION("AMD Versal Gen 2 UFS Host Controller driver"); MODULE_LICENSE("GPL");