summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVenkat Gopalakrishnan <venkatg@codeaurora.org>2016-04-15 11:24:31 -0700
committerKyle Yan <kyan@codeaurora.org>2016-06-08 15:13:17 -0700
commit6b266ad4c87c7bfe02eb0482191af1bc4c46c7cd (patch)
treea77e0e1e1b2d33bcff1cdd31e9b4d19b524e57ab
parentcad3a64d0cdcfed9e6bb653b329f4f12309861fb (diff)
phy: qcom-ufs: add svs2 support
phy-qcom-ufs-qmp-v3 supports SVS2 voltage scaling mode that allows lowest power consumption in HS G1. The PHY must be put in hibern8 state before configuring the PHY to enter SVS2 mode. The voltage can be reduced after this to SVS2 level. This change exposes an API that allows the UFS driver to configure the PHY to enter SVS2 mode. Change-Id: I2ef01d98603840289c436e14bf3df54a2ab9198b Signed-off-by: Venkat Gopalakrishnan <venkatg@codeaurora.org>
-rw-r--r--drivers/phy/phy-qcom-ufs-i.h9
-rw-r--r--drivers/phy/phy-qcom-ufs-qmp-v3.c36
-rw-r--r--drivers/phy/phy-qcom-ufs-qmp-v3.h34
-rw-r--r--drivers/phy/phy-qcom-ufs.c39
-rw-r--r--include/linux/phy/phy-qcom-ufs.h1
5 files changed, 110 insertions, 9 deletions
diff --git a/drivers/phy/phy-qcom-ufs-i.h b/drivers/phy/phy-qcom-ufs-i.h
index adeabe817174..35179c8be471 100644
--- a/drivers/phy/phy-qcom-ufs-i.h
+++ b/drivers/phy/phy-qcom-ufs-i.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
+ * Copyright (c) 2013-2016, Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -150,6 +150,8 @@ struct ufs_qcom_phy {
* state.
* @power_control: pointer to a function that controls analog rail of phy
* and writes to QSERDES_RX_SIGDET_CNTRL attribute
+ * @configure_lpm: pointer to a function that configures the phy
+ * for low power mode.
*/
struct ufs_qcom_phy_specific_ops {
int (*calibrate_phy)(struct ufs_qcom_phy *phy, bool is_rate_B);
@@ -158,6 +160,7 @@ struct ufs_qcom_phy_specific_ops {
void (*set_tx_lane_enable)(struct ufs_qcom_phy *phy, u32 val);
void (*ctrl_rx_linecfg)(struct ufs_qcom_phy *phy, bool ctrl);
void (*power_control)(struct ufs_qcom_phy *phy, bool val);
+ int (*configure_lpm)(struct ufs_qcom_phy *phy, bool enable);
};
struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy);
@@ -178,4 +181,8 @@ int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
struct ufs_qcom_phy_calibration *tbl_A, int tbl_size_A,
struct ufs_qcom_phy_calibration *tbl_B, int tbl_size_B,
bool is_rate_B);
+void ufs_qcom_phy_write_tbl(struct ufs_qcom_phy *ufs_qcom_phy,
+ struct ufs_qcom_phy_calibration *tbl,
+ int tbl_size);
+
#endif
diff --git a/drivers/phy/phy-qcom-ufs-qmp-v3.c b/drivers/phy/phy-qcom-ufs-qmp-v3.c
index 0aee89bc15e0..6b8dbc29f6e8 100644
--- a/drivers/phy/phy-qcom-ufs-qmp-v3.c
+++ b/drivers/phy/phy-qcom-ufs-qmp-v3.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -145,6 +145,39 @@ out:
return err;
}
+static
+int ufs_qcom_phy_qmp_v3_configure_lpm(struct ufs_qcom_phy *ufs_qcom_phy,
+ bool enable)
+{
+ int err = 0;
+ int tbl_size;
+ struct ufs_qcom_phy_calibration *tbl = NULL;
+
+ /* The default low power mode configuration is SVS2 */
+ if (enable) {
+ tbl_size = ARRAY_SIZE(phy_cal_table_svs2_enable);
+ tbl = phy_cal_table_svs2_enable;
+ } else {
+ tbl_size = ARRAY_SIZE(phy_cal_table_svs2_disable);
+ tbl = phy_cal_table_svs2_disable;
+ }
+
+ if (!tbl) {
+ dev_err(ufs_qcom_phy->dev, "%s: tbl for SVS2 %s is NULL",
+ __func__, enable ? "enable" : "disable");
+ err = -EINVAL;
+ goto out;
+ }
+
+ ufs_qcom_phy_write_tbl(ufs_qcom_phy, tbl, tbl_size);
+
+ /* flush buffered writes */
+ mb();
+
+out:
+ return err;
+}
+
struct phy_ops ufs_qcom_phy_qmp_v3_phy_ops = {
.init = ufs_qcom_phy_qmp_v3_init,
.exit = ufs_qcom_phy_exit,
@@ -160,6 +193,7 @@ struct ufs_qcom_phy_specific_ops phy_v3_ops = {
.set_tx_lane_enable = ufs_qcom_phy_qmp_v3_set_tx_lane_enable,
.ctrl_rx_linecfg = ufs_qcom_phy_qmp_v3_ctrl_rx_linecfg,
.power_control = ufs_qcom_phy_qmp_v3_power_control,
+ .configure_lpm = ufs_qcom_phy_qmp_v3_configure_lpm,
};
static int ufs_qcom_phy_qmp_v3_probe(struct platform_device *pdev)
diff --git a/drivers/phy/phy-qcom-ufs-qmp-v3.h b/drivers/phy/phy-qcom-ufs-qmp-v3.h
index ab4179481402..e9ac76b43812 100644
--- a/drivers/phy/phy-qcom-ufs-qmp-v3.h
+++ b/drivers/phy/phy-qcom-ufs-qmp-v3.h
@@ -127,6 +127,8 @@
/* UFS PHY registers */
#define UFS_PHY_PHY_START PHY_OFF(0x00)
#define UFS_PHY_POWER_DOWN_CONTROL PHY_OFF(0x04)
+#define UFS_PHY_TIMER_20US_CORECLK_STEPS_MSB PHY_OFF(0x08)
+#define UFS_PHY_TIMER_20US_CORECLK_STEPS_LSB PHY_OFF(0x0C)
#define UFS_PHY_TX_LARGE_AMP_DRV_LVL PHY_OFF(0x2C)
#define UFS_PHY_TX_SMALL_AMP_DRV_LVL PHY_OFF(0x34)
#define UFS_PHY_LINECFG_DISABLE PHY_OFF(0x130)
@@ -238,4 +240,36 @@ static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x44),
};
+static struct ufs_qcom_phy_calibration phy_cal_table_svs2_enable[] = {
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV_MODE0, 0x14),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV_MODE1, 0x14),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SVS_MODE_CLK_SEL, 0x0a),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x7e),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE0, 0x7f),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE0, 0x06),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE1, 0x7e),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE1, 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE1, 0x99),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE1, 0x07),
+ UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TIMER_20US_CORECLK_STEPS_MSB, 0x0b),
+ UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TIMER_20US_CORECLK_STEPS_LSB, 0x66),
+};
+
+static struct ufs_qcom_phy_calibration phy_cal_table_svs2_disable[] = {
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV_MODE0, 0x0a),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV_MODE1, 0x0a),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SVS_MODE_CLK_SEL, 0x05),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x3f),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE0, 0xff),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE0, 0x0c),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE1, 0x3f),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE1, 0x00),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE1, 0x32),
+ UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE1, 0x0f),
+ UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TIMER_20US_CORECLK_STEPS_MSB, 0x16),
+ UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TIMER_20US_CORECLK_STEPS_LSB, 0xcc),
+};
+
#endif
diff --git a/drivers/phy/phy-qcom-ufs.c b/drivers/phy/phy-qcom-ufs.c
index b78f03ad6c46..de32b75f4f57 100644
--- a/drivers/phy/phy-qcom-ufs.c
+++ b/drivers/phy/phy-qcom-ufs.c
@@ -29,13 +29,24 @@ static int ufs_qcom_phy_init_vreg(struct phy *, struct ufs_qcom_phy_vreg *,
static int ufs_qcom_phy_base_init(struct platform_device *pdev,
struct ufs_qcom_phy *phy_common);
+void ufs_qcom_phy_write_tbl(struct ufs_qcom_phy *ufs_qcom_phy,
+ struct ufs_qcom_phy_calibration *tbl,
+ int tbl_size)
+{
+ int i;
+
+ for (i = 0; i < tbl_size; i++)
+ writel_relaxed(tbl[i].cfg_value,
+ ufs_qcom_phy->mmio + tbl[i].reg_offset);
+}
+EXPORT_SYMBOL(ufs_qcom_phy_write_tbl);
+
int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
struct ufs_qcom_phy_calibration *tbl_A,
int tbl_size_A,
struct ufs_qcom_phy_calibration *tbl_B,
int tbl_size_B, bool is_rate_B)
{
- int i;
int ret = 0;
if (!tbl_A) {
@@ -44,9 +55,7 @@ int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
goto out;
}
- for (i = 0; i < tbl_size_A; i++)
- writel_relaxed(tbl_A[i].cfg_value,
- ufs_qcom_phy->mmio + tbl_A[i].reg_offset);
+ ufs_qcom_phy_write_tbl(ufs_qcom_phy, tbl_A, tbl_size_A);
/*
* In case we would like to work in rate B, we need
@@ -62,9 +71,7 @@ int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
goto out;
}
- for (i = 0; i < tbl_size_B; i++)
- writel_relaxed(tbl_B[i].cfg_value,
- ufs_qcom_phy->mmio + tbl_B[i].reg_offset);
+ ufs_qcom_phy_write_tbl(ufs_qcom_phy, tbl_B, tbl_size_B);
}
/* flush buffered writes */
@@ -763,3 +770,21 @@ int ufs_qcom_phy_power_off(struct phy *generic_phy)
return 0;
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_off);
+
+int ufs_qcom_phy_configure_lpm(struct phy *generic_phy, bool enable)
+{
+ struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
+ int ret = 0;
+
+ if (ufs_qcom_phy->phy_spec_ops->configure_lpm) {
+ ret = ufs_qcom_phy->phy_spec_ops->
+ configure_lpm(ufs_qcom_phy, enable);
+ if (ret)
+ dev_err(ufs_qcom_phy->dev,
+ "%s: configure_lpm(%s) failed %d\n",
+ __func__, enable ? "enable" : "disable", ret);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(ufs_qcom_phy_configure_lpm);
diff --git a/include/linux/phy/phy-qcom-ufs.h b/include/linux/phy/phy-qcom-ufs.h
index 540938ea3bc3..7945fea14d77 100644
--- a/include/linux/phy/phy-qcom-ufs.h
+++ b/include/linux/phy/phy-qcom-ufs.h
@@ -57,5 +57,6 @@ int ufs_qcom_phy_is_pcs_ready(struct phy *phy);
void ufs_qcom_phy_save_controller_version(struct phy *phy,
u8 major, u16 minor, u16 step);
const char *ufs_qcom_phy_name(struct phy *phy);
+int ufs_qcom_phy_configure_lpm(struct phy *generic_phy, bool enable);
#endif /* PHY_QCOM_UFS_H_ */