summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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_ */