Reviewed-by: Gilad Broner <gbroner@xxxxxxxxxxxxxx> > New revisions of UFS host controller supports the new UniPro > hardware controller (referred as QUniPro). This patch adds > the support to enable this new UniPro controller hardware. > > This change also adds power optimization for bus scaling feature, > as well as support for HS-G3 power mode. > > Signed-off-by: Yaniv Gardi <ygardi@xxxxxxxxxxxxxx> > > --- > drivers/scsi/ufs/ufs-qcom.c | 640 > ++++++++++++++++++++++++++++++++------------ > drivers/scsi/ufs/ufs-qcom.h | 31 ++- > drivers/scsi/ufs/ufshcd.c | 8 +- > drivers/scsi/ufs/ufshcd.h | 27 +- > 4 files changed, 525 insertions(+), 181 deletions(-) > > diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c > index 1633808..4f38d00 100644 > --- a/drivers/scsi/ufs/ufs-qcom.c > +++ b/drivers/scsi/ufs/ufs-qcom.c > @@ -44,11 +44,11 @@ enum { > > static struct ufs_qcom_host *ufs_qcom_hosts[MAX_UFS_QCOM_HOSTS]; > > -static void ufs_qcom_get_speed_mode(struct ufs_pa_layer_attr *p, char > *result); > -static int ufs_qcom_get_bus_vote(struct ufs_qcom_host *host, > - const char *speed_mode); > static int ufs_qcom_set_bus_vote(struct ufs_qcom_host *host, int vote); > 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) > { > @@ -177,6 +177,7 @@ static int ufs_qcom_init_lane_clks(struct > ufs_qcom_host *host) > > err = ufs_qcom_host_clk_get(dev, "tx_lane1_sync_clk", > &host->tx_l1_sync_clk); > + > out: > return err; > } > @@ -209,7 +210,9 @@ static int ufs_qcom_check_hibern8(struct ufs_hba *hba) > > do { > err = ufshcd_dme_get(hba, > - UIC_ARG_MIB(MPHY_TX_FSM_STATE), &tx_fsm_val); > + UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE, > + UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), > + &tx_fsm_val); > if (err || tx_fsm_val == TX_FSM_HIBERN8) > break; > > @@ -223,7 +226,9 @@ static int ufs_qcom_check_hibern8(struct ufs_hba *hba) > */ > if (time_after(jiffies, timeout)) > err = ufshcd_dme_get(hba, > - UIC_ARG_MIB(MPHY_TX_FSM_STATE), &tx_fsm_val); > + UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE, > + UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), > + &tx_fsm_val); > > if (err) { > dev_err(hba->dev, "%s: unable to get TX_FSM_STATE, err %d\n", > @@ -237,6 +242,15 @@ static int ufs_qcom_check_hibern8(struct ufs_hba > *hba) > return err; > } > > +static void ufs_qcom_select_unipro_mode(struct ufs_qcom_host *host) > +{ > + ufshcd_rmwl(host->hba, QUNIPRO_SEL, > + ufs_qcom_cap_qunipro(host) ? QUNIPRO_SEL : 0, > + REG_UFS_CFG1); > + /* make sure above configuration is applied before we return */ > + mb(); > +} > + > static int ufs_qcom_power_up_sequence(struct ufs_hba *hba) > { > struct ufs_qcom_host *host = ufshcd_get_variant(hba); > @@ -251,9 +265,11 @@ static int ufs_qcom_power_up_sequence(struct ufs_hba > *hba) > usleep_range(1000, 1100); > > ret = ufs_qcom_phy_calibrate_phy(phy, is_rate_B); > + > if (ret) { > - dev_err(hba->dev, "%s: ufs_qcom_phy_calibrate_phy() failed, ret = > %d\n", > - __func__, ret); > + dev_err(hba->dev, > + "%s: ufs_qcom_phy_calibrate_phy()failed, ret = %d\n", > + __func__, ret); > goto out; > } > > @@ -274,9 +290,12 @@ static int ufs_qcom_power_up_sequence(struct ufs_hba > *hba) > > ret = ufs_qcom_phy_is_pcs_ready(phy); > if (ret) > - dev_err(hba->dev, "%s: is_physical_coding_sublayer_ready() failed, ret > = %d\n", > + dev_err(hba->dev, > + "%s: is_physical_coding_sublayer_ready() failed, ret = %d\n", > __func__, ret); > > + ufs_qcom_select_unipro_mode(host); > + > out: > return ret; > } > @@ -299,7 +318,8 @@ static void ufs_qcom_enable_hw_clk_gating(struct > ufs_hba *hba) > mb(); > } > > -static int ufs_qcom_hce_enable_notify(struct ufs_hba *hba, bool status) > +static int ufs_qcom_hce_enable_notify(struct ufs_hba *hba, > + enum ufs_notify_change_status status) > { > struct ufs_qcom_host *host = ufshcd_get_variant(hba); > int err = 0; > @@ -329,12 +349,12 @@ static int ufs_qcom_hce_enable_notify(struct ufs_hba > *hba, bool status) > } > > /** > - * Returns non-zero for success (which rate of core_clk) and 0 > - * in case of a failure > + * Returns zero for success and non-zero in case of a failure > */ > -static unsigned long > -ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear, u32 hs, u32 rate) > +static int ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear, > + u32 hs, u32 rate, bool update_link_startup_timer) > { > + int ret = 0; > struct ufs_qcom_host *host = ufshcd_get_variant(hba); > struct ufs_clk_info *clki; > u32 core_clk_period_in_ns; > @@ -352,11 +372,13 @@ ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear, > u32 hs, u32 rate) > static u32 hs_fr_table_rA[][2] = { > {UFS_HS_G1, 0x1F}, > {UFS_HS_G2, 0x3e}, > + {UFS_HS_G3, 0x7D}, > }; > > static u32 hs_fr_table_rB[][2] = { > {UFS_HS_G1, 0x24}, > {UFS_HS_G2, 0x49}, > + {UFS_HS_G3, 0x92}, > }; > > /* > @@ -384,7 +406,17 @@ ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear, > u32 hs, u32 rate) > core_clk_rate = DEFAULT_CLK_RATE_HZ; > > core_clk_cycles_per_us = core_clk_rate / USEC_PER_SEC; > - ufshcd_writel(hba, core_clk_cycles_per_us, REG_UFS_SYS1CLK_1US); > + if (ufshcd_readl(hba, REG_UFS_SYS1CLK_1US) != core_clk_cycles_per_us) { > + ufshcd_writel(hba, core_clk_cycles_per_us, REG_UFS_SYS1CLK_1US); > + /* > + * make sure above write gets applied before we return from > + * this function. > + */ > + mb(); > + } > + > + if (ufs_qcom_cap_qunipro(host)) > + goto out; > > core_clk_period_in_ns = NSEC_PER_SEC / core_clk_rate; > core_clk_period_in_ns <<= OFFSET_CLK_NS_REG; > @@ -434,35 +466,59 @@ ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear, > u32 hs, u32 rate) > goto out_error; > } > > - /* this register 2 fields shall be written at once */ > - ufshcd_writel(hba, core_clk_period_in_ns | tx_clk_cycles_per_us, > - REG_UFS_TX_SYMBOL_CLK_NS_US); > + if (ufshcd_readl(hba, REG_UFS_TX_SYMBOL_CLK_NS_US) != > + (core_clk_period_in_ns | tx_clk_cycles_per_us)) { > + /* this register 2 fields shall be written at once */ > + ufshcd_writel(hba, core_clk_period_in_ns | tx_clk_cycles_per_us, > + REG_UFS_TX_SYMBOL_CLK_NS_US); > + /* > + * make sure above write gets applied before we return from > + * this function. > + */ > + mb(); > + } > + > + if (update_link_startup_timer) { > + ufshcd_writel(hba, ((core_clk_rate / MSEC_PER_SEC) * 100), > + REG_UFS_PA_LINK_STARTUP_TIMER); > + /* > + * make sure that this configuration is applied before > + * we return > + */ > + mb(); > + } > goto out; > > out_error: > - core_clk_rate = 0; > + ret = -EINVAL; > out: > - return core_clk_rate; > + return ret; > } > > -static int ufs_qcom_link_startup_notify(struct ufs_hba *hba, bool status) > +static int ufs_qcom_link_startup_notify(struct ufs_hba *hba, > + enum ufs_notify_change_status status) > { > - unsigned long core_clk_rate = 0; > - u32 core_clk_cycles_per_100ms; > + int err = 0; > + struct ufs_qcom_host *host = ufshcd_get_variant(hba); > > switch (status) { > case PRE_CHANGE: > - core_clk_rate = ufs_qcom_cfg_timers(hba, UFS_PWM_G1, > - SLOWAUTO_MODE, 0); > - if (!core_clk_rate) { > + if (ufs_qcom_cfg_timers(hba, UFS_PWM_G1, SLOWAUTO_MODE, > + 0, true)) { > dev_err(hba->dev, "%s: ufs_qcom_cfg_timers() failed\n", > __func__); > - return -EINVAL; > + err = -EINVAL; > + goto out; > } > - core_clk_cycles_per_100ms = > - (core_clk_rate / MSEC_PER_SEC) * 100; > - ufshcd_writel(hba, core_clk_cycles_per_100ms, > - REG_UFS_PA_LINK_STARTUP_TIMER); > + > + 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); > @@ -471,7 +527,8 @@ static int ufs_qcom_link_startup_notify(struct ufs_hba > *hba, bool status) > break; > } > > - return 0; > +out: > + return err; > } > > static int ufs_qcom_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op) > @@ -498,8 +555,10 @@ static int ufs_qcom_suspend(struct ufs_hba *hba, enum > ufs_pm_op pm_op) > * If UniPro link is not active, PHY ref_clk, main PHY analog power > * rail and low noise analog power rail for PLL can be switched off. > */ > - if (!ufs_qcom_is_link_active(hba)) > + if (!ufs_qcom_is_link_active(hba)) { > + ufs_qcom_disable_lane_clks(host); > phy_power_off(phy); > + } > > out: > return ret; > @@ -518,6 +577,10 @@ static int ufs_qcom_resume(struct ufs_hba *hba, enum > ufs_pm_op pm_op) > goto out; > } > > + err = ufs_qcom_enable_lane_clks(host); > + if (err) > + goto out; > + > hba->is_sys_suspended = false; > > out: > @@ -622,6 +685,81 @@ static int ufs_qcom_get_pwr_dev_param(struct > ufs_qcom_dev_params *qcom_param, > return 0; > } > > +#ifdef CONFIG_MSM_BUS_SCALING > +static int ufs_qcom_get_bus_vote(struct ufs_qcom_host *host, > + const char *speed_mode) > +{ > + struct device *dev = host->hba->dev; > + struct device_node *np = dev->of_node; > + int err; > + const char *key = "qcom,bus-vector-names"; > + > + if (!speed_mode) { > + err = -EINVAL; > + goto out; > + } > + > + if (host->bus_vote.is_max_bw_needed && !!strcmp(speed_mode, "MIN")) > + err = of_property_match_string(np, key, "MAX"); > + else > + err = of_property_match_string(np, key, speed_mode); > + > +out: > + if (err < 0) > + dev_err(dev, "%s: Invalid %s mode %d\n", > + __func__, speed_mode, err); > + return err; > +} > + > +static void ufs_qcom_get_speed_mode(struct ufs_pa_layer_attr *p, char > *result) > +{ > + int gear = max_t(u32, p->gear_rx, p->gear_tx); > + int lanes = max_t(u32, p->lane_rx, p->lane_tx); > + int pwr; > + > + /* default to PWM Gear 1, Lane 1 if power mode is not initialized */ > + if (!gear) > + gear = 1; > + > + if (!lanes) > + lanes = 1; > + > + if (!p->pwr_rx && !p->pwr_tx) { > + pwr = SLOWAUTO_MODE; > + snprintf(result, BUS_VECTOR_NAME_LEN, "MIN"); > + } else if (p->pwr_rx == FAST_MODE || p->pwr_rx == FASTAUTO_MODE || > + p->pwr_tx == FAST_MODE || p->pwr_tx == FASTAUTO_MODE) { > + pwr = FAST_MODE; > + snprintf(result, BUS_VECTOR_NAME_LEN, "%s_R%s_G%d_L%d", "HS", > + p->hs_rate == PA_HS_MODE_B ? "B" : "A", gear, lanes); > + } else { > + pwr = SLOW_MODE; > + snprintf(result, BUS_VECTOR_NAME_LEN, "%s_G%d_L%d", > + "PWM", gear, lanes); > + } > +} > + > +static int ufs_qcom_set_bus_vote(struct ufs_qcom_host *host, int vote) > +{ > + int err = 0; > + > + if (vote != host->bus_vote.curr_vote) { > + err = msm_bus_scale_client_update_request( > + host->bus_vote.client_handle, vote); > + if (err) { > + dev_err(host->hba->dev, > + "%s: msm_bus_scale_client_update_request() failed: > bus_client_handle=0x%x, vote=%d, err=%d\n", > + __func__, host->bus_vote.client_handle, > + vote, err); > + goto out; > + } > + > + host->bus_vote.curr_vote = vote; > + } > +out: > + return err; > +} > + > static int ufs_qcom_update_bus_bw_vote(struct ufs_qcom_host *host) > { > int vote; > @@ -643,8 +781,132 @@ static int ufs_qcom_update_bus_bw_vote(struct > ufs_qcom_host *host) > return err; > } > > +static ssize_t > +show_ufs_to_mem_max_bus_bw(struct device *dev, struct device_attribute > *attr, > + char *buf) > +{ > + struct ufs_hba *hba = dev_get_drvdata(dev); > + struct ufs_qcom_host *host = ufshcd_get_variant(hba); > + > + return snprintf(buf, PAGE_SIZE, "%u\n", > + host->bus_vote.is_max_bw_needed); > +} > + > +static ssize_t > +store_ufs_to_mem_max_bus_bw(struct device *dev, struct device_attribute > *attr, > + const char *buf, size_t count) > +{ > + struct ufs_hba *hba = dev_get_drvdata(dev); > + struct ufs_qcom_host *host = ufshcd_get_variant(hba); > + uint32_t value; > + > + if (!kstrtou32(buf, 0, &value)) { > + host->bus_vote.is_max_bw_needed = !!value; > + ufs_qcom_update_bus_bw_vote(host); > + } > + > + return count; > +} > + > +static int ufs_qcom_bus_register(struct ufs_qcom_host *host) > +{ > + int err; > + struct msm_bus_scale_pdata *bus_pdata; > + struct device *dev = host->hba->dev; > + struct platform_device *pdev = to_platform_device(dev); > + struct device_node *np = dev->of_node; > + > + bus_pdata = msm_bus_cl_get_pdata(pdev); > + if (!bus_pdata) { > + dev_err(dev, "%s: failed to get bus vectors\n", __func__); > + err = -ENODATA; > + goto out; > + } > + > + err = of_property_count_strings(np, "qcom,bus-vector-names"); > + if (err < 0 || err != bus_pdata->num_usecases) { > + dev_err(dev, "%s: qcom,bus-vector-names not specified correctly %d\n", > + __func__, err); > + goto out; > + } > + > + host->bus_vote.client_handle = msm_bus_scale_register_client(bus_pdata); > + if (!host->bus_vote.client_handle) { > + dev_err(dev, "%s: msm_bus_scale_register_client failed\n", > + __func__); > + err = -EFAULT; > + goto out; > + } > + > + /* cache the vote index for minimum and maximum bandwidth */ > + host->bus_vote.min_bw_vote = ufs_qcom_get_bus_vote(host, "MIN"); > + host->bus_vote.max_bw_vote = ufs_qcom_get_bus_vote(host, "MAX"); > + > + host->bus_vote.max_bus_bw.show = show_ufs_to_mem_max_bus_bw; > + host->bus_vote.max_bus_bw.store = store_ufs_to_mem_max_bus_bw; > + sysfs_attr_init(&host->bus_vote.max_bus_bw.attr); > + host->bus_vote.max_bus_bw.attr.name = "max_bus_bw"; > + host->bus_vote.max_bus_bw.attr.mode = S_IRUGO | S_IWUSR; > + err = device_create_file(dev, &host->bus_vote.max_bus_bw); > +out: > + return err; > +} > +#else /* CONFIG_MSM_BUS_SCALING */ > +static int ufs_qcom_update_bus_bw_vote(struct ufs_qcom_host *host) > +{ > + return 0; > +} > + > +static int ufs_qcom_set_bus_vote(struct ufs_qcom_host *host, int vote) > +{ > + return 0; > +} > + > +static int ufs_qcom_bus_register(struct ufs_qcom_host *host) > +{ > + return 0; > +} > +#endif /* CONFIG_MSM_BUS_SCALING */ > + > +static void ufs_qcom_dev_ref_clk_ctrl(struct ufs_qcom_host *host, bool > enable) > +{ > + if (host->dev_ref_clk_ctrl_mmio && > + (enable ^ host->is_dev_ref_clk_enabled)) { > + u32 temp = readl_relaxed(host->dev_ref_clk_ctrl_mmio); > + > + if (enable) > + temp |= host->dev_ref_clk_en_mask; > + else > + temp &= ~host->dev_ref_clk_en_mask; > + > + /* > + * If we are here to disable this clock it might be immediately > + * after entering into hibern8 in which case we need to make > + * sure that device ref_clk is active at least 1us after the > + * hibern8 enter. > + */ > + if (!enable) > + udelay(1); > + > + writel_relaxed(temp, host->dev_ref_clk_ctrl_mmio); > + > + /* ensure that ref_clk is enabled/disabled before we return */ > + wmb(); > + > + /* > + * If we call hibern8 exit after this, we need to make sure that > + * device ref_clk is stable for at least 1us before the hibern8 > + * exit command. > + */ > + if (enable) > + udelay(1); > + > + host->is_dev_ref_clk_enabled = enable; > + } > +} > + > static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba, > - bool status, > + enum ufs_notify_change_status status, > struct ufs_pa_layer_attr *dev_max_params, > struct ufs_pa_layer_attr *dev_req_params) > { > @@ -677,6 +939,20 @@ static int ufs_qcom_pwr_change_notify(struct ufs_hba > *hba, > ufs_qcom_cap.desired_working_mode = > UFS_QCOM_LIMIT_DESIRED_MODE; > > + if (host->hw_ver.major == 0x1) { > + /* > + * HS-G3 operations may not reliably work on legacy QCOM > + * UFS host controller hardware even though capability > + * exchange during link startup phase may end up > + * negotiating maximum supported gear as G3. > + * Hence downgrade the maximum supported gear to HS-G2. > + */ > + if (ufs_qcom_cap.hs_tx_gear > UFS_HS_G2) > + ufs_qcom_cap.hs_tx_gear = UFS_HS_G2; > + if (ufs_qcom_cap.hs_rx_gear > UFS_HS_G2) > + ufs_qcom_cap.hs_rx_gear = UFS_HS_G2; > + } > + > ret = ufs_qcom_get_pwr_dev_param(&ufs_qcom_cap, > dev_max_params, > dev_req_params); > @@ -688,9 +964,9 @@ static int ufs_qcom_pwr_change_notify(struct ufs_hba > *hba, > > break; > case POST_CHANGE: > - if (!ufs_qcom_cfg_timers(hba, dev_req_params->gear_rx, > + if (ufs_qcom_cfg_timers(hba, dev_req_params->gear_rx, > dev_req_params->pwr_rx, > - dev_req_params->hs_rate)) { > + dev_req_params->hs_rate, false)) { > dev_err(hba->dev, "%s: ufs_qcom_cfg_timers() failed\n", > __func__); > /* > @@ -752,10 +1028,11 @@ static void ufs_qcom_advertise_quirks(struct > ufs_hba *hba) > > if (host->hw_ver.minor == 0x0001 && host->hw_ver.step == 0x0001) > hba->quirks |= UFSHCD_QUIRK_BROKEN_INTR_AGGR; > + > + hba->quirks |= UFSHCD_QUIRK_BROKEN_LCC; > } > > if (host->hw_ver.major >= 0x2) { > - hba->quirks |= UFSHCD_QUIRK_BROKEN_LCC; > hba->quirks |= UFSHCD_QUIRK_BROKEN_UFS_HCI_VERSION; > > if (!ufs_qcom_cap_qunipro(host)) > @@ -770,77 +1047,27 @@ static void ufs_qcom_set_caps(struct ufs_hba *hba) > { > struct ufs_qcom_host *host = ufshcd_get_variant(hba); > > - if (host->hw_ver.major >= 0x2) > - host->caps = UFS_QCOM_CAP_QUNIPRO; > -} > - > -static int ufs_qcom_get_bus_vote(struct ufs_qcom_host *host, > - const char *speed_mode) > -{ > - struct device *dev = host->hba->dev; > - struct device_node *np = dev->of_node; > - int err; > - const char *key = "qcom,bus-vector-names"; > - > - if (!speed_mode) { > - err = -EINVAL; > - goto out; > - } > - > - if (host->bus_vote.is_max_bw_needed && !!strcmp(speed_mode, "MIN")) > - err = of_property_match_string(np, key, "MAX"); > - else > - err = of_property_match_string(np, key, speed_mode); > - > -out: > - if (err < 0) > - dev_err(dev, "%s: Invalid %s mode %d\n", > - __func__, speed_mode, err); > - return err; > -} > - > -static int ufs_qcom_set_bus_vote(struct ufs_qcom_host *host, int vote) > -{ > - int err = 0; > - > - if (vote != host->bus_vote.curr_vote) > - host->bus_vote.curr_vote = vote; > - > - return err; > -} > - > -static void ufs_qcom_get_speed_mode(struct ufs_pa_layer_attr *p, char > *result) > -{ > - int gear = max_t(u32, p->gear_rx, p->gear_tx); > - int lanes = max_t(u32, p->lane_rx, p->lane_tx); > - int pwr; > - > - /* default to PWM Gear 1, Lane 1 if power mode is not initialized */ > - if (!gear) > - gear = 1; > - > - if (!lanes) > - lanes = 1; > + hba->caps |= UFSHCD_CAP_CLK_GATING | UFSHCD_CAP_HIBERN8_WITH_CLK_GATING; > + hba->caps |= UFSHCD_CAP_CLK_SCALING; > + hba->caps |= UFSHCD_CAP_AUTO_BKOPS_SUSPEND; > > - if (!p->pwr_rx && !p->pwr_tx) { > - pwr = SLOWAUTO_MODE; > - snprintf(result, BUS_VECTOR_NAME_LEN, "MIN"); > - } else if (p->pwr_rx == FAST_MODE || p->pwr_rx == FASTAUTO_MODE || > - p->pwr_tx == FAST_MODE || p->pwr_tx == FASTAUTO_MODE) { > - pwr = FAST_MODE; > - snprintf(result, BUS_VECTOR_NAME_LEN, "%s_R%s_G%d_L%d", "HS", > - p->hs_rate == PA_HS_MODE_B ? "B" : "A", gear, lanes); > - } else { > - pwr = SLOW_MODE; > - snprintf(result, BUS_VECTOR_NAME_LEN, "%s_G%d_L%d", > - "PWM", gear, lanes); > + if (host->hw_ver.major >= 0x2) { > + host->caps = UFS_QCOM_CAP_QUNIPRO | > + UFS_QCOM_CAP_RETAIN_SEC_CFG_AFTER_PWR_COLLAPSE; > } > } > > +/** > + * ufs_qcom_setup_clocks - enables/disable clocks > + * @hba: host controller instance > + * @on: If true, enable clocks else disable them. > + * > + * Returns 0 on success, non-zero on failure. > + */ > static int ufs_qcom_setup_clocks(struct ufs_hba *hba, bool on) > { > struct ufs_qcom_host *host = ufshcd_get_variant(hba); > - int err = 0; > + int err; > int vote = 0; > > /* > @@ -863,20 +1090,18 @@ static int ufs_qcom_setup_clocks(struct ufs_hba > *hba, bool on) > ufs_qcom_phy_disable_iface_clk(host->generic_phy); > goto out; > } > - /* enable the device ref clock */ > - ufs_qcom_phy_enable_dev_ref_clk(host->generic_phy); > vote = host->bus_vote.saved_vote; > if (vote == host->bus_vote.min_bw_vote) > ufs_qcom_update_bus_bw_vote(host); > + > } else { > + > /* M-PHY RMMI interface clocks can be turned off */ > ufs_qcom_phy_disable_iface_clk(host->generic_phy); > - if (!ufs_qcom_is_link_active(hba)) { > - /* turn off UFS local PHY ref_clk */ > - ufs_qcom_phy_disable_ref_clk(host->generic_phy); > + if (!ufs_qcom_is_link_active(hba)) > /* disable device ref_clk */ > - ufs_qcom_phy_disable_dev_ref_clk(host->generic_phy); > - } > + ufs_qcom_dev_ref_clk_ctrl(host, false); > + > vote = host->bus_vote.min_bw_vote; > } > > @@ -889,60 +1114,6 @@ out: > return err; > } > > -static ssize_t > -show_ufs_to_mem_max_bus_bw(struct device *dev, struct device_attribute > *attr, > - char *buf) > -{ > - struct ufs_hba *hba = dev_get_drvdata(dev); > - struct ufs_qcom_host *host = ufshcd_get_variant(hba); > - > - return snprintf(buf, PAGE_SIZE, "%u\n", > - host->bus_vote.is_max_bw_needed); > -} > - > -static ssize_t > -store_ufs_to_mem_max_bus_bw(struct device *dev, struct device_attribute > *attr, > - const char *buf, size_t count) > -{ > - struct ufs_hba *hba = dev_get_drvdata(dev); > - struct ufs_qcom_host *host = ufshcd_get_variant(hba); > - uint32_t value; > - > - if (!kstrtou32(buf, 0, &value)) { > - host->bus_vote.is_max_bw_needed = !!value; > - ufs_qcom_update_bus_bw_vote(host); > - } > - > - return count; > -} > - > -static int ufs_qcom_bus_register(struct ufs_qcom_host *host) > -{ > - int err; > - struct device *dev = host->hba->dev; > - struct device_node *np = dev->of_node; > - > - err = of_property_count_strings(np, "qcom,bus-vector-names"); > - if (err < 0 ) { > - dev_err(dev, "%s: qcom,bus-vector-names not specified correctly %d\n", > - __func__, err); > - goto out; > - } > - > - /* cache the vote index for minimum and maximum bandwidth */ > - host->bus_vote.min_bw_vote = ufs_qcom_get_bus_vote(host, "MIN"); > - host->bus_vote.max_bw_vote = ufs_qcom_get_bus_vote(host, "MAX"); > - > - host->bus_vote.max_bus_bw.show = show_ufs_to_mem_max_bus_bw; > - host->bus_vote.max_bus_bw.store = store_ufs_to_mem_max_bus_bw; > - sysfs_attr_init(&host->bus_vote.max_bus_bw.attr); > - host->bus_vote.max_bus_bw.attr.name = "max_bus_bw"; > - host->bus_vote.max_bus_bw.attr.mode = S_IRUGO | S_IWUSR; > - err = device_create_file(dev, &host->bus_vote.max_bus_bw); > -out: > - return err; > -} > - > #define ANDROID_BOOT_DEV_MAX 30 > static char android_boot_dev[ANDROID_BOOT_DEV_MAX]; > > @@ -969,7 +1140,9 @@ static int ufs_qcom_init(struct ufs_hba *hba) > { > int err; > struct device *dev = hba->dev; > + struct platform_device *pdev = to_platform_device(dev); > struct ufs_qcom_host *host; > + struct resource *res; > > if (strlen(android_boot_dev) && strcmp(android_boot_dev, dev_name(dev))) > return -ENODEV; > @@ -981,9 +1154,15 @@ static int ufs_qcom_init(struct ufs_hba *hba) > goto out; > } > > + /* Make a two way bind between the qcom host and the hba */ > host->hba = hba; > ufshcd_set_variant(hba, host); > > + /* > + * voting/devoting device ref_clk source is time consuming hence > + * skip devoting it during aggressive clock gating. This clock > + * will still be gated off during runtime suspend. > + */ > host->generic_phy = devm_phy_get(dev, "ufsphy"); > > if (IS_ERR(host->generic_phy)) { > @@ -999,6 +1178,30 @@ static int ufs_qcom_init(struct ufs_hba *hba) > ufs_qcom_get_controller_revision(hba, &host->hw_ver.major, > &host->hw_ver.minor, &host->hw_ver.step); > > + /* > + * for newer controllers, device reference clock control bit has > + * moved inside UFS controller register address space itself. > + */ > + if (host->hw_ver.major >= 0x02) { > + host->dev_ref_clk_ctrl_mmio = hba->mmio_base + REG_UFS_CFG1; > + host->dev_ref_clk_en_mask = BIT(26); > + } else { > + /* "dev_ref_clk_ctrl_mem" is optional resource */ > + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); > + if (res) { > + host->dev_ref_clk_ctrl_mmio = > + devm_ioremap_resource(dev, res); > + if (IS_ERR(host->dev_ref_clk_ctrl_mmio)) { > + dev_warn(dev, > + "%s: could not map dev_ref_clk_ctrl_mmio, err %ld\n", > + __func__, > + PTR_ERR(host->dev_ref_clk_ctrl_mmio)); > + host->dev_ref_clk_ctrl_mmio = NULL; > + } > + host->dev_ref_clk_en_mask = BIT(5); > + } > + } > + > /* update phy revision information before calling phy_init() */ > ufs_qcom_phy_save_controller_version(host->generic_phy, > host->hw_ver.major, host->hw_ver.minor, host->hw_ver.step); > @@ -1015,9 +1218,6 @@ static int ufs_qcom_init(struct ufs_hba *hba) > ufs_qcom_set_caps(hba); > ufs_qcom_advertise_quirks(hba); > > - hba->caps |= UFSHCD_CAP_CLK_GATING | UFSHCD_CAP_CLK_SCALING; > - hba->caps |= UFSHCD_CAP_AUTO_BKOPS_SUSPEND; > - > ufs_qcom_setup_clocks(hba, true); > > if (hba->dev->id < MAX_UFS_QCOM_HOSTS) > @@ -1053,14 +1253,118 @@ static void ufs_qcom_exit(struct ufs_hba *hba) > phy_power_off(host->generic_phy); > } > > -static > -void ufs_qcom_clk_scale_notify(struct ufs_hba *hba) > +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; > + > + 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 = ufshcd_get_variant(hba); > + > + 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 = ufshcd_get_variant(hba); > + 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 = ufshcd_get_variant(hba); > + > + 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, enum ufs_notify_change_status status) > { > struct ufs_qcom_host *host = ufshcd_get_variant(hba); > struct ufs_pa_layer_attr *dev_req_params = &host->dev_req_params; > + int err = 0; > > - if (!dev_req_params) > - return; > + 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); > + > + if (err || !dev_req_params) > + goto out; > + > + 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; > } > > static void ufs_qcom_get_default_testbus_cfg(struct ufs_qcom_host *host) > diff --git a/drivers/scsi/ufs/ufs-qcom.h b/drivers/scsi/ufs/ufs-qcom.h > index 1b71a1b..36249b3 100644 > --- a/drivers/scsi/ufs/ufs-qcom.h > +++ b/drivers/scsi/ufs/ufs-qcom.h > @@ -35,8 +35,8 @@ > > #define UFS_QCOM_LIMIT_NUM_LANES_RX 2 > #define UFS_QCOM_LIMIT_NUM_LANES_TX 2 > -#define UFS_QCOM_LIMIT_HSGEAR_RX UFS_HS_G2 > -#define UFS_QCOM_LIMIT_HSGEAR_TX UFS_HS_G2 > +#define UFS_QCOM_LIMIT_HSGEAR_RX UFS_HS_G3 > +#define UFS_QCOM_LIMIT_HSGEAR_TX UFS_HS_G3 > #define UFS_QCOM_LIMIT_PWMGEAR_RX UFS_PWM_G4 > #define UFS_QCOM_LIMIT_PWMGEAR_TX UFS_PWM_G4 > #define UFS_QCOM_LIMIT_RX_PWR_PWM SLOW_MODE > @@ -64,6 +64,11 @@ enum { > UFS_TEST_BUS_CTRL_2 = 0xF4, > UFS_UNIPRO_CFG = 0xF8, > > + /* > + * QCOM UFS host controller vendor specific registers > + * added in HW Version 3.0.0 > + */ > + UFS_AH8_CFG = 0xFC, > }; > > /* QCOM UFS host controller vendor specific debug registers */ > @@ -83,6 +88,11 @@ enum { > UFS_UFS_DBG_RD_EDTL_RAM = 0x1900, > }; > > +#define UFS_CNTLR_2_x_x_VEN_REGS_OFFSET(x) (0x000 + x) > +#define UFS_CNTLR_3_x_x_VEN_REGS_OFFSET(x) (0x400 + x) > + > +/* bit definitions for REG_UFS_CFG1 register */ > +#define QUNIPRO_SEL UFS_BIT(0) > #define TEST_BUS_EN BIT(18) > #define TEST_BUS_SEL GENMASK(22, 19) > > @@ -131,6 +141,12 @@ enum ufs_qcom_phy_init_type { > (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) > @@ -196,6 +212,12 @@ struct ufs_qcom_host { > * controller supports the QUniPro mode. > */ > #define UFS_QCOM_CAP_QUNIPRO UFS_BIT(0) > + > + /* > + * Set this capability if host controller can retain the secure > + * configuration even after UFS controller core power collapse. > + */ > + #define UFS_QCOM_CAP_RETAIN_SEC_CFG_AFTER_PWR_COLLAPSE UFS_BIT(1) > u32 caps; > > struct phy *generic_phy; > @@ -208,7 +230,12 @@ struct ufs_qcom_host { > struct clk *tx_l1_sync_clk; > bool is_lane_clks_enabled; > > + void __iomem *dev_ref_clk_ctrl_mmio; > + bool is_dev_ref_clk_enabled; > struct ufs_hw_version hw_ver; > + > + u32 dev_ref_clk_en_mask; > + > /* Bitmask for enabling debug prints */ > u32 dbg_print_en; > struct ufs_qcom_testbus testbus; > diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c > index 52f9dad..131c720 100644 > --- a/drivers/scsi/ufs/ufshcd.c > +++ b/drivers/scsi/ufs/ufshcd.c > @@ -5420,6 +5420,10 @@ static int ufshcd_scale_clks(struct ufs_hba *hba, > bool scale_up) > if (!head || list_empty(head)) > goto out; > > + ret = ufshcd_vops_clk_scale_notify(hba, scale_up, PRE_CHANGE); > + if (ret) > + return ret; > + > list_for_each_entry(clki, head, list) { > if (!IS_ERR_OR_NULL(clki->clk)) { > if (scale_up && clki->max_freq) { > @@ -5450,7 +5454,9 @@ static int ufshcd_scale_clks(struct ufs_hba *hba, > bool scale_up) > dev_dbg(hba->dev, "%s: clk: %s, rate: %lu\n", __func__, > clki->name, clk_get_rate(clki->clk)); > } > - ufshcd_vops_clk_scale_notify(hba); > + > + ret = ufshcd_vops_clk_scale_notify(hba, scale_up, POST_CHANGE); > + > out: > return ret; > } > diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h > index 471c667..2570d94 100644 > --- a/drivers/scsi/ufs/ufshcd.h > +++ b/drivers/scsi/ufs/ufshcd.h > @@ -223,8 +223,10 @@ struct ufs_clk_info { > bool enabled; > }; > > -#define PRE_CHANGE 0 > -#define POST_CHANGE 1 > +enum ufs_notify_change_status { > + PRE_CHANGE, > + POST_CHANGE, > +}; > > struct ufs_pa_layer_attr { > u32 gear_rx; > @@ -266,13 +268,17 @@ struct ufs_hba_variant_ops { > int (*init)(struct ufs_hba *); > void (*exit)(struct ufs_hba *); > u32 (*get_ufs_hci_version)(struct ufs_hba *); > - void (*clk_scale_notify)(struct ufs_hba *); > - int (*setup_clocks)(struct ufs_hba *, bool); > + int (*clk_scale_notify)(struct ufs_hba *, bool, > + enum ufs_notify_change_status); > + int (*setup_clocks)(struct ufs_hba *, bool); > int (*setup_regulators)(struct ufs_hba *, bool); > - int (*hce_enable_notify)(struct ufs_hba *, bool); > - int (*link_startup_notify)(struct ufs_hba *, bool); > + int (*hce_enable_notify)(struct ufs_hba *, > + enum ufs_notify_change_status); > + int (*link_startup_notify)(struct ufs_hba *, > + enum ufs_notify_change_status); > int (*pwr_change_notify)(struct ufs_hba *, > - bool, struct ufs_pa_layer_attr *, > + enum ufs_notify_change_status status, > + struct ufs_pa_layer_attr *, > struct ufs_pa_layer_attr *); > int (*suspend)(struct ufs_hba *, enum ufs_pm_op); > int (*resume)(struct ufs_hba *, enum ufs_pm_op); > @@ -708,17 +714,18 @@ static inline u32 > ufshcd_vops_get_ufs_hci_version(struct ufs_hba *hba) > return ufshcd_readl(hba, REG_UFS_VERSION); > } > > -static inline void ufshcd_vops_clk_scale_notify(struct ufs_hba *hba) > +static inline int ufshcd_vops_clk_scale_notify(struct ufs_hba *hba, > + bool up, enum ufs_notify_change_status status) > { > if (hba->vops && hba->vops->clk_scale_notify) > - return hba->vops->clk_scale_notify(hba); > + return hba->vops->clk_scale_notify(hba, up, status); > + return 0; > } > > static inline int ufshcd_vops_setup_clocks(struct ufs_hba *hba, bool on) > { > if (hba->vops && hba->vops->setup_clocks) > return hba->vops->setup_clocks(hba, on); > - > return 0; > } > > -- > 1.8.5.2 > > -- > QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member > of Code Aurora Forum, hosted by The Linux Foundation > -- > To unsubscribe from this list: send the line "unsubscribe linux-scsi" in > the body of a message to majordomo@xxxxxxxxxxxxxxx > More majordomo info at http://vger.kernel.org/majordomo-info.html > -- Qualcomm Israel, on behalf of Qualcomm Innovation Center, Inc. The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html