diff options
| -rw-r--r-- | drivers/phy/phy-qcom-ufs-qmp-14nm.c | 112 | ||||
| -rw-r--r-- | drivers/phy/phy-qcom-ufs-qmp-14nm.h | 6 |
2 files changed, 111 insertions, 7 deletions
diff --git a/drivers/phy/phy-qcom-ufs-qmp-14nm.c b/drivers/phy/phy-qcom-ufs-qmp-14nm.c index 9299cf190b80..c6cf41964d8d 100644 --- a/drivers/phy/phy-qcom-ufs-qmp-14nm.c +++ b/drivers/phy/phy-qcom-ufs-qmp-14nm.c @@ -99,14 +99,50 @@ out: } static -void ufs_qcom_phy_qmp_14nm_power_control(struct ufs_qcom_phy *phy, bool val) +void ufs_qcom_phy_qmp_14nm_power_control(struct ufs_qcom_phy *phy, + bool is_pwr_collapse) { - writel_relaxed(val ? 0x1 : 0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL); - /* - * Before any transactions involving PHY, ensure PHY knows - * that it's analog rail is powered ON (or OFF). - */ - mb(); + bool is_workaround_req = false; + + if (phy->quirks & + UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE) + is_workaround_req = true; + + if (is_pwr_collapse) { + if (is_workaround_req) { + /* assert common reset before analog power collapse */ + writel_relaxed(0x1, phy->mmio + QSERDES_COM_SW_RESET); + /* + * make sure that reset is propogated before analog + * power collapse + */ + mb(); + } + /* apply analog power collapse */ + writel_relaxed(0x1, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL); + /* + * Make sure that PHY knows its analog rail is going to be + * powered OFF. + */ + mb(); + } else { + /* bring PHY out of analog power collapse */ + writel_relaxed(0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL); + /* + * Before any transactions involving PHY, ensure PHY knows + * that it's analog rail is powered ON. + */ + mb(); + if (is_workaround_req) { + /* + * de-assert common reset after coming out of analog + * power collapse + */ + writel_relaxed(0x0, phy->mmio + QSERDES_COM_SW_RESET); + /* make common reset is de-asserted before proceeding */ + mb(); + } + } } static inline @@ -146,6 +182,64 @@ static inline void ufs_qcom_phy_qmp_14nm_start_serdes(struct ufs_qcom_phy *phy) /* Ensure register value is committed */ mb(); } +/* + * This additional sequence is required as a workaround for following bug: + * Due to missing reset in the UFS PHY logic, the pll may not lock following + * analog power collapse. As a result the common block of the PHY must be put + * into reset during hibernate entry and taken out of reset during hibernate + * exit. The following sequence is required to save the calibrated VCO codes. + * Saving the codes will save substantial time on hibernate exit + * (<50us vs. 1.7ms). + */ +static void +ufs_qcom_phy_qmp_14nm_save_calibrated_vco_code(struct ufs_qcom_phy *phy) +{ + u32 vco_tune_mode0, vco_tune_mode1; + u32 temp; + + /* set common debug bus select */ + writel_relaxed(0x02, phy->mmio + QSERDES_COM_DEBUG_BUS_SEL); + /* apply debug bus select before reading the debug bus registers */ + mb(); + + /* vco_tune_mode0[7:0]: Read VCO tuning code for Series A */ + vco_tune_mode0 = readl_relaxed(phy->mmio + QSERDES_COM_DEBUG_BUS0) + & 0xFF; + + temp = readl_relaxed(phy->mmio + QSERDES_COM_DEBUG_BUS1); + /* vco_tune_mode0[9:8]: Read VCO tuning code for Series A */ + vco_tune_mode0 |= (temp & 0x300); + /* vco_tune_mode1[5:0]: Read VCO tuning code for Series B */ + vco_tune_mode1 = temp & 0x3F; + + temp = readl_relaxed(phy->mmio + QSERDES_COM_DEBUG_BUS2); + /* vco_tune_mode1[9:6]: Read VCO tuning code for Series B */ + vco_tune_mode1 |= (temp & 0x3C0); + + /* vco_tune_mode0[7:0] */ + writel_relaxed((vco_tune_mode0 & 0xff), + phy->mmio + QSERDES_COM_VCO_TUNE1_MODE0); + /* vco_tune_mode0[9:8] */ + writel_relaxed((vco_tune_mode0 & 0x300) >> 8, + phy->mmio + QSERDES_COM_VCO_TUNE2_MODE0); + /* vco_tune_mode1[7:0] */ + writel_relaxed((vco_tune_mode1 & 0xff), + phy->mmio + QSERDES_COM_VCO_TUNE1_MODE1); + /* vco_tune_mode1[9:8] */ + writel_relaxed((vco_tune_mode1 & 0x300) >> 8, + phy->mmio + QSERDES_COM_VCO_TUNE2_MODE1); + /* apply vco tuning codes before enabling the vco bypass mode */ + mb(); + + temp = readl_relaxed(phy->mmio + QSERDES_COM_VCO_TUNE_CTRL); + /* + * Bypass the calibrated code and use the stored code for + * both A and B series + */ + writel_relaxed((temp | 0xC), phy->mmio + QSERDES_COM_VCO_TUNE_CTRL); + /* apply this configuration before return */ + mb(); +} static int ufs_qcom_phy_qmp_14nm_is_pcs_ready(struct ufs_qcom_phy *phy_common) { @@ -171,6 +265,10 @@ static int ufs_qcom_phy_qmp_14nm_is_pcs_ready(struct ufs_qcom_phy *phy_common) mb(); } + if (phy_common->quirks & + UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE) + ufs_qcom_phy_qmp_14nm_save_calibrated_vco_code(phy_common); + out: return err; } diff --git a/drivers/phy/phy-qcom-ufs-qmp-14nm.h b/drivers/phy/phy-qcom-ufs-qmp-14nm.h index e2f55aae8ad3..d4704cc28b6f 100644 --- a/drivers/phy/phy-qcom-ufs-qmp-14nm.h +++ b/drivers/phy/phy-qcom-ufs-qmp-14nm.h @@ -66,9 +66,15 @@ #define QSERDES_COM_CLK_SELECT COM_OFF(0x174) #define QSERDES_COM_HSCLK_SEL COM_OFF(0x178) #define QSERDES_COM_CORECLK_DIV COM_OFF(0x184) +#define QSERDES_COM_SW_RESET COM_OFF(0x188) #define QSERDES_COM_CORE_CLK_EN COM_OFF(0x18C) #define QSERDES_COM_CMN_CONFIG COM_OFF(0x194) #define QSERDES_COM_SVS_MODE_CLK_SEL COM_OFF(0x19C) +#define QSERDES_COM_DEBUG_BUS0 COM_OFF(0x1A0) +#define QSERDES_COM_DEBUG_BUS1 COM_OFF(0x1A4) +#define QSERDES_COM_DEBUG_BUS2 COM_OFF(0x1A8) +#define QSERDES_COM_DEBUG_BUS3 COM_OFF(0x1AC) +#define QSERDES_COM_DEBUG_BUS_SEL COM_OFF(0x1B0) #define QSERDES_COM_CORECLK_DIV_MODE1 COM_OFF(0x1BC) /* UFS PHY registers */ |
