summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/phy/phy-qcom-ufs-qmp-14nm.c112
-rw-r--r--drivers/phy/phy-qcom-ufs-qmp-14nm.h6
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 */