From 413d97dd2a8c5f39c43346bfa8644bf6ad1a4ce6 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Wed, 28 Jan 2015 15:54:17 -0800 Subject: [PATCH] scsi: ufs-qcom: implement pre and post notification for clock scaling QUniPro controller requires additional configuration before and after clock scaling, this change adds the support for it. Change-Id: I0add27ff3ab54f72b8b79e1e554541c2e492a4c8 Signed-off-by: Subhash Jadavani --- drivers/scsi/ufs/ufs-qcom.c | 126 +++++++++++++++++++++++++++--- include/linux/scsi/ufs/ufs-qcom.h | 6 ++ 2 files changed, 123 insertions(+), 9 deletions(-) diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c index f2d0fc1fdaf0..1f812c000302 100644 --- a/drivers/scsi/ufs/ufs-qcom.c +++ b/drivers/scsi/ufs/ufs-qcom.c @@ -63,6 +63,8 @@ static int ufs_qcom_get_bus_vote(struct ufs_qcom_host *host, static int ufs_qcom_set_bus_vote(struct ufs_qcom_host *host, int vote); static int ufs_qcom_update_sec_cfg(struct ufs_hba *hba, bool restore_sec_cfg); static void ufs_qcom_get_default_testbus_cfg(struct ufs_qcom_host *host); +static int ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(struct ufs_hba *hba, + u32 clk_cycles); static void ufs_qcom_dump_regs(struct ufs_hba *hba, int offset, int len, char *prefix) @@ -546,6 +548,16 @@ static int ufs_qcom_link_startup_notify(struct ufs_hba *hba, bool status) /* make sure RX LineCfg is enabled before link startup */ err = ufs_qcom_phy_ctrl_rx_linecfg(phy, true); + if (err) + goto out; + + if (ufs_qcom_cap_qunipro(host)) + /* + * set unipro core clock cycles to 150 & clear clock + * divider + */ + err = ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(hba, + 150); break; case POST_CHANGE: ufs_qcom_link_startup_post_change(hba); @@ -1386,22 +1398,118 @@ static void ufs_qcom_exit(struct ufs_hba *hba) phy_power_off(host->generic_phy); } +static int ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(struct ufs_hba *hba, + u32 clk_cycles) +{ + int err; + u32 core_clk_ctrl_reg; -int ufs_qcom_clk_scale_notify(struct ufs_hba *hba, - bool scale_up, bool status) + if (clk_cycles > DME_VS_CORE_CLK_CTRL_MAX_CORE_CLK_1US_CYCLES_MASK) + return -EINVAL; + + err = ufshcd_dme_get(hba, + UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL), + &core_clk_ctrl_reg); + if (err) + goto out; + + core_clk_ctrl_reg &= ~DME_VS_CORE_CLK_CTRL_MAX_CORE_CLK_1US_CYCLES_MASK; + core_clk_ctrl_reg |= clk_cycles; + + /* Clear CORE_CLK_DIV_EN */ + core_clk_ctrl_reg &= ~DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT; + + err = ufshcd_dme_set(hba, + UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL), + core_clk_ctrl_reg); +out: + return err; +} + +static int ufs_qcom_clk_scale_up_pre_change(struct ufs_hba *hba) +{ + /* nothing to do as of now */ + return 0; +} + +static int ufs_qcom_clk_scale_up_post_change(struct ufs_hba *hba) +{ + struct ufs_qcom_host *host = hba->priv; + + if (!ufs_qcom_cap_qunipro(host)) + return 0; + + /* set unipro core clock cycles to 150 and clear clock divider */ + return ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(hba, 150); +} + +static int ufs_qcom_clk_scale_down_pre_change(struct ufs_hba *hba) +{ + struct ufs_qcom_host *host = hba->priv; + int err; + u32 core_clk_ctrl_reg; + + if (!ufs_qcom_cap_qunipro(host)) + return 0; + + err = ufshcd_dme_get(hba, + UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL), + &core_clk_ctrl_reg); + + /* make sure CORE_CLK_DIV_EN is cleared */ + if (!err && + (core_clk_ctrl_reg & DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT)) { + core_clk_ctrl_reg &= ~DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT; + err = ufshcd_dme_set(hba, + UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL), + core_clk_ctrl_reg); + } + + return err; +} + +static int ufs_qcom_clk_scale_down_post_change(struct ufs_hba *hba) +{ + struct ufs_qcom_host *host = hba->priv; + + if (!ufs_qcom_cap_qunipro(host)) + return 0; + + /* set unipro core clock cycles to 75 and clear clock divider */ + return ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(hba, 75); +} + +static int ufs_qcom_clk_scale_notify(struct ufs_hba *hba, + bool scale_up, bool status) { struct ufs_qcom_host *host = hba->priv; struct ufs_pa_layer_attr *dev_req_params = &host->dev_req_params; + int err = 0; - if (!dev_req_params) - return 0; + if (status == PRE_CHANGE) { + if (scale_up) + err = ufs_qcom_clk_scale_up_pre_change(hba); + else + err = ufs_qcom_clk_scale_down_pre_change(hba); + } else { + if (scale_up) + err = ufs_qcom_clk_scale_up_post_change(hba); + else + err = ufs_qcom_clk_scale_down_post_change(hba); - ufs_qcom_cfg_timers(hba, dev_req_params->gear_rx, - dev_req_params->pwr_rx, - dev_req_params->hs_rate, false); - ufs_qcom_update_bus_bw_vote(host); + if (err || !dev_req_params) + goto out; - return 0; + ufs_qcom_cfg_timers(hba, + dev_req_params->gear_rx, + dev_req_params->pwr_rx, + dev_req_params->hs_rate, + false); + ufs_qcom_update_bus_bw_vote(host); + } + +out: + return err; } /* diff --git a/include/linux/scsi/ufs/ufs-qcom.h b/include/linux/scsi/ufs/ufs-qcom.h index 87c3853773ed..c38ae5ed4965 100644 --- a/include/linux/scsi/ufs/ufs-qcom.h +++ b/include/linux/scsi/ufs/ufs-qcom.h @@ -141,6 +141,12 @@ struct ufs_qcom_phy_vreg { (UFS_QCOM_DBG_PRINT_REGS_EN | UFS_QCOM_DBG_PRINT_ICE_REGS_EN | \ UFS_QCOM_DBG_PRINT_TEST_BUS_EN) +/* QUniPro Vendor specific attributes */ +#define DME_VS_CORE_CLK_CTRL 0xD002 +/* bit and mask definitions for DME_VS_CORE_CLK_CTRL attribute */ +#define DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT BIT(8) +#define DME_VS_CORE_CLK_CTRL_MAX_CORE_CLK_1US_CYCLES_MASK 0xFF + static inline void ufs_qcom_get_controller_revision(struct ufs_hba *hba, u8 *major, u16 *minor, u16 *step)