Re: [PATCH v7 8/8] scsi: ufs-qcom: add QUniPro hardware support and power optimizations

From: subhashj
Date: Thu Oct 22 2015 - 03:05:15 EST


Looks good to me.
Reviewed-by: Subhash Jadavani <subhashj@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
>


--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/