scsi: ufs-qcom: add auto hibern8 support
QCOM UFS controller version 3.0.0 supports the auto hibern8 feature as defined by the UFS HCI v2.0 specification. Make necessary changes to enable this during host controller initialization. Change-Id: I5a048629dade92fe79631ba30d3b3e280a77d4e6 Signed-off-by: Venkat Gopalakrishnan <venkatg@codeaurora.org>
This commit is contained in:
parent
dbd6e84bff
commit
cad3a64d0c
2 changed files with 185 additions and 71 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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_ */
|
||||
|
|
Loading…
Add table
Reference in a new issue