summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/scsi/ufs/ufs-qcom.c214
-rw-r--r--drivers/scsi/ufs/ufs-qcom.h36
2 files changed, 182 insertions, 68 deletions
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_notify(struct ufs_hba *hba,
- enum ufs_notify_change_status status)
+static int ufs_qcom_link_startup_pre_change(struct ufs_hba *hba)
{
- int err = 0;
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
struct phy *phy = host->generic_phy;
+ u32 unipro_ver;
+ int err = 0;
- 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;
- }
+ 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;
- /* make sure RX LineCfg is enabled before link startup */
- err = ufs_qcom_phy_ctrl_rx_linecfg(phy, true);
+ 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;
+ }
- 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);
+ 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.
- */
- 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);
+ /*
+ * 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;
+
+ switch (status) {
+ case PRE_CHANGE:
+ 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_ */