diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c index 992122c94515..a53dc6590d92 100644 --- a/drivers/scsi/ufs/ufs-qcom.c +++ b/drivers/scsi/ufs/ufs-qcom.c @@ -209,34 +209,6 @@ out: return err; } -static int ufs_qcom_link_startup_post_change(struct ufs_hba *hba) -{ - struct ufs_qcom_host *host = ufshcd_get_variant(hba); - struct phy *phy = host->generic_phy; - u32 tx_lanes; - int err = 0; - - err = ufs_qcom_get_connected_tx_lanes(hba, &tx_lanes); - if (err) - goto out; - - err = ufs_qcom_phy_set_tx_lane_enable(phy, tx_lanes); - if (err) - dev_err(hba->dev, "%s: ufs_qcom_phy_set_tx_lane_enable failed\n", - __func__); - - /* - * Some UFS devices send incorrect LineCfg data as part of power mode - * change sequence which may cause host PHY to go into bad state. - * Disabling Rx LineCfg of host PHY should help avoid this. - */ - if (ufshcd_get_local_unipro_ver(hba) == UFS_UNIPRO_VER_1_41) - err = ufs_qcom_phy_ctrl_rx_linecfg(phy, false); - -out: - return err; -} - static int ufs_qcom_check_hibern8(struct ufs_hba *hba) { int err; @@ -340,15 +312,43 @@ out: * in a specific operation, UTP controller CGCs are by default disabled and * this function enables them (after every UFS link startup) to save some power * leakage. + * + * UFS host controller v3.0.0 onwards has internal clock gating mechanism + * in Qunipro, enable them to save additional power. */ -static void ufs_qcom_enable_hw_clk_gating(struct ufs_hba *hba) +static int ufs_qcom_enable_hw_clk_gating(struct ufs_hba *hba) { + struct ufs_qcom_host *host = ufshcd_get_variant(hba); + int err = 0; + + /* Enable UTP internal clock gating */ ufshcd_writel(hba, ufshcd_readl(hba, REG_UFS_CFG2) | REG_UFS_CFG2_CGC_EN_ALL, REG_UFS_CFG2); /* Ensure that HW clock gating is enabled before next operations */ mb(); + + /* Enable Qunipro internal clock gating if supported */ + if (!ufs_qcom_cap_qunipro_clk_gating(host)) + goto out; + + /* Enable all the mask bits */ + err = ufshcd_dme_rmw(hba, DL_VS_CLK_CFG_MASK, + DL_VS_CLK_CFG_MASK, DL_VS_CLK_CFG); + if (err) + goto out; + + err = ufshcd_dme_rmw(hba, PA_VS_CLK_CFG_REG_MASK, + PA_VS_CLK_CFG_REG_MASK, PA_VS_CLK_CFG_REG); + if (err) + goto out; + + err = ufshcd_dme_rmw(hba, DME_VS_CORE_CLK_CTRL_DME_HW_CGC_EN, + DME_VS_CORE_CLK_CTRL_DME_HW_CGC_EN, + DME_VS_CORE_CLK_CTRL); +out: + return err; } static int ufs_qcom_hce_enable_notify(struct ufs_hba *hba, @@ -379,7 +379,6 @@ static int ufs_qcom_hce_enable_notify(struct ufs_hba *hba, case POST_CHANGE: /* check if UFS PHY moved from DISABLED to HIBERN8 */ err = ufs_qcom_check_hibern8(hba); - ufs_qcom_enable_hw_clk_gating(hba); break; default: dev_err(hba->dev, "%s: invalid status %d\n", __func__, status); @@ -427,9 +426,11 @@ static int ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear, * SYS1CLK_1US_REG, TX_SYMBOL_CLK_1US_REG, CLK_NS_REG & * UFS_REG_PA_LINK_STARTUP_TIMER * But UTP controller uses SYS1CLK_1US_REG register for Interrupt - * Aggregation logic. + * Aggregation / Auto hibern8 logic. */ - if (ufs_qcom_cap_qunipro(host) && !ufshcd_is_intr_aggr_allowed(hba)) + if (ufs_qcom_cap_qunipro(host) && + (!(ufshcd_is_intr_aggr_allowed(hba) || + ufshcd_is_auto_hibern8_supported(hba)))) goto out; if (gear == 0) { @@ -536,57 +537,136 @@ out: return ret; } +static int ufs_qcom_link_startup_pre_change(struct ufs_hba *hba) +{ + struct ufs_qcom_host *host = ufshcd_get_variant(hba); + struct phy *phy = host->generic_phy; + u32 unipro_ver; + int err = 0; + + if (ufs_qcom_cfg_timers(hba, UFS_PWM_G1, SLOWAUTO_MODE, 0, true)) { + dev_err(hba->dev, "%s: ufs_qcom_cfg_timers() failed\n", + __func__); + err = -EINVAL; + goto out; + } + + /* make sure RX LineCfg is enabled before link startup */ + err = ufs_qcom_phy_ctrl_rx_linecfg(phy, true); + if (err) + goto out; + + if (ufs_qcom_cap_qunipro(host)) { + /* + * set unipro core clock cycles to 150 & clear clock divider + */ + err = ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(hba, 150); + if (err) + goto out; + } + + err = ufs_qcom_enable_hw_clk_gating(hba); + if (err) + goto out; + + /* + * Some UFS devices (and may be host) have issues if LCC is + * enabled. So we are setting PA_Local_TX_LCC_Enable to 0 + * before link startup which will make sure that both host + * and device TX LCC are disabled once link startup is + * completed. + */ + unipro_ver = ufshcd_get_local_unipro_ver(hba); + if (unipro_ver != UFS_UNIPRO_VER_1_41) + err = ufshcd_dme_set(hba, + UIC_ARG_MIB(PA_LOCAL_TX_LCC_ENABLE), + 0); + if (err) + goto out; + + if (!ufs_qcom_cap_qunipro_clk_gating(host)) + goto out; + + /* Enable all the mask bits */ + err = ufshcd_dme_rmw(hba, SAVECONFIGTIME_MODE_MASK, + SAVECONFIGTIME_MODE_MASK, + PA_VS_CONFIG_REG1); +out: + return err; +} + +static int ufs_qcom_link_startup_post_change(struct ufs_hba *hba) +{ + struct ufs_qcom_host *host = ufshcd_get_variant(hba); + struct phy *phy = host->generic_phy; + u32 tx_lanes; + int err = 0; + + err = ufs_qcom_get_connected_tx_lanes(hba, &tx_lanes); + if (err) + goto out; + + err = ufs_qcom_phy_set_tx_lane_enable(phy, tx_lanes); + if (err) { + dev_err(hba->dev, "%s: ufs_qcom_phy_set_tx_lane_enable failed\n", + __func__); + goto out; + } + + /* + * Some UFS devices send incorrect LineCfg data as part of power mode + * change sequence which may cause host PHY to go into bad state. + * Disabling Rx LineCfg of host PHY should help avoid this. + */ + if (ufshcd_get_local_unipro_ver(hba) == UFS_UNIPRO_VER_1_41) + err = ufs_qcom_phy_ctrl_rx_linecfg(phy, false); + if (err) { + dev_err(hba->dev, "%s: ufs_qcom_phy_ctrl_rx_linecfg failed\n", + __func__); + goto out; + } + + /* + * UFS controller has *clk_req output to GCC, for each one if the clocks + * entering it. When *clk_req for a specific clock is de-asserted, + * a corresponding clock from GCC is stopped. UFS controller de-asserts + * *clk_req outputs when it is in Auto Hibernate state only if the + * Clock request feature is enabled. + * Enable the Clock request feature: + * - Enable HW clock control for UFS clocks in GCC (handled by the + * clock driver as part of clk_prepare_enable). + * - Set the AH8_CFG.*CLK_REQ register bits to 1. + */ + if (ufshcd_is_auto_hibern8_supported(hba)) + ufshcd_writel(hba, ufshcd_readl(hba, UFS_AH8_CFG) | + UFS_HW_CLK_CTRL_EN, + UFS_AH8_CFG); + /* + * Make sure clock request feature gets enabled for HW clk gating + * before further operations. + */ + mb(); + +out: + return err; +} + static int ufs_qcom_link_startup_notify(struct ufs_hba *hba, enum ufs_notify_change_status status) { int err = 0; - struct ufs_qcom_host *host = ufshcd_get_variant(hba); - struct phy *phy = host->generic_phy; switch (status) { case PRE_CHANGE: - if (ufs_qcom_cfg_timers(hba, UFS_PWM_G1, SLOWAUTO_MODE, - 0, true)) { - dev_err(hba->dev, "%s: ufs_qcom_cfg_timers() failed\n", - __func__); - err = -EINVAL; - goto out; - } - - /* make sure RX LineCfg is enabled before link startup */ - err = ufs_qcom_phy_ctrl_rx_linecfg(phy, true); - if (err) - goto out; - - if (ufs_qcom_cap_qunipro(host)) - /* - * set unipro core clock cycles to 150 & clear clock - * divider - */ - err = ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(hba, - 150); - - /* - * Some UFS devices (and may be host) have issues if LCC is - * enabled. So we are setting PA_Local_TX_LCC_Enable to 0 - * before link startup which will make sure that both host - * and device TX LCC are disabled once link startup is - * completed. - */ - if (ufshcd_get_local_unipro_ver(hba) != UFS_UNIPRO_VER_1_41) - err = ufshcd_dme_set(hba, - UIC_ARG_MIB(PA_LOCAL_TX_LCC_ENABLE), - 0); - + err = ufs_qcom_link_startup_pre_change(hba); break; case POST_CHANGE: - ufs_qcom_link_startup_post_change(hba); + err = ufs_qcom_link_startup_post_change(hba); break; default: break; } -out: return err; } @@ -1294,6 +1374,8 @@ static void ufs_qcom_set_caps(struct ufs_hba *hba) host->caps = UFS_QCOM_CAP_QUNIPRO | UFS_QCOM_CAP_RETAIN_SEC_CFG_AFTER_PWR_COLLAPSE; } + if (host->hw_ver.major >= 0x3) + host->caps |= UFS_QCOM_CAP_QUNIPRO_CLK_GATING; } /** diff --git a/drivers/scsi/ufs/ufs-qcom.h b/drivers/scsi/ufs/ufs-qcom.h index ec22c290c45d..ead384133612 100644 --- a/drivers/scsi/ufs/ufs-qcom.h +++ b/drivers/scsi/ufs/ufs-qcom.h @@ -119,6 +119,17 @@ enum { DFC_HW_CGC_EN | TRLUT_HW_CGC_EN |\ TMRLUT_HW_CGC_EN | OCSC_HW_CGC_EN) +/* bit definitions for UFS_AH8_CFG register */ +#define CC_UFS_HCLK_REQ_EN BIT(1) +#define CC_UFS_SYS_CLK_REQ_EN BIT(2) +#define CC_UFS_ICE_CORE_CLK_REQ_EN BIT(3) +#define CC_UFS_UNIPRO_CORE_CLK_REQ_EN BIT(4) +#define CC_UFS_AUXCLK_REQ_EN BIT(5) + +#define UFS_HW_CLK_CTRL_EN (CC_UFS_SYS_CLK_REQ_EN |\ + CC_UFS_ICE_CORE_CLK_REQ_EN |\ + CC_UFS_UNIPRO_CORE_CLK_REQ_EN |\ + CC_UFS_AUXCLK_REQ_EN) /* bit offset */ enum { OFFSET_UFS_PHY_SOFT_RESET = 1, @@ -147,11 +158,20 @@ enum ufs_qcom_phy_init_type { UFS_QCOM_DBG_PRINT_TEST_BUS_EN) /* QUniPro Vendor specific attributes */ -#define PA_VS_CONFIG_REG1 0x9000 +#define PA_VS_CONFIG_REG1 0x9000 +#define SAVECONFIGTIME_MODE_MASK 0x6000 + +#define PA_VS_CLK_CFG_REG 0x9004 +#define PA_VS_CLK_CFG_REG_MASK 0x1FF + +#define DL_VS_CLK_CFG 0xA00B +#define DL_VS_CLK_CFG_MASK 0x3FF + #define DME_VS_CORE_CLK_CTRL 0xD002 /* bit and mask definitions for DME_VS_CORE_CLK_CTRL attribute */ -#define DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT BIT(8) #define DME_VS_CORE_CLK_CTRL_MAX_CORE_CLK_1US_CYCLES_MASK 0xFF +#define DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT BIT(8) +#define DME_VS_CORE_CLK_CTRL_DME_HW_CGC_EN BIT(9) static inline void ufs_qcom_get_controller_revision(struct ufs_hba *hba, @@ -309,6 +329,13 @@ struct ufs_qcom_host { * configuration even after UFS controller core power collapse. */ #define UFS_QCOM_CAP_RETAIN_SEC_CFG_AFTER_PWR_COLLAPSE UFS_BIT(1) + + /* + * Set this capability if host controller supports Qunipro internal + * clock gating. + */ + #define UFS_QCOM_CAP_QUNIPRO_CLK_GATING UFS_BIT(2) + u32 caps; struct phy *generic_phy; @@ -368,4 +395,9 @@ static inline bool ufs_qcom_cap_qunipro(struct ufs_qcom_host *host) return false; } +static inline bool ufs_qcom_cap_qunipro_clk_gating(struct ufs_qcom_host *host) +{ + return !!(host->caps & UFS_QCOM_CAP_QUNIPRO_CLK_GATING); +} + #endif /* UFS_QCOM_H_ */