diff options
| -rw-r--r-- | drivers/phy/phy-qcom-ufs-i.h | 9 | ||||
| -rw-r--r-- | drivers/phy/phy-qcom-ufs-qmp-v3.c | 36 | ||||
| -rw-r--r-- | drivers/phy/phy-qcom-ufs-qmp-v3.h | 34 | ||||
| -rw-r--r-- | drivers/phy/phy-qcom-ufs.c | 39 | ||||
| -rw-r--r-- | include/linux/phy/phy-qcom-ufs.h | 1 |
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_ */ |
