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>
This commit is contained in:
parent
cad3a64d0c
commit
6b266ad4c8
5 changed files with 110 additions and 9 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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_ */
|
||||
|
|
Loading…
Add table
Reference in a new issue