diff options
-rw-r--r-- | drivers/scsi/ufs/ufshcd.c | 263 | ||||
-rw-r--r-- | include/linux/scsi/ufs/ufshcd.h | 6 |
2 files changed, 216 insertions, 53 deletions
diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index 9ce383109ad9..f9ac27847f8c 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -996,6 +996,68 @@ static int ufshcd_send_request_sense_all_lus(struct ufs_hba *hba) return 0; } +/** + * ufshcd_scale_clks - scale up or scale down UFS controller clocks + * @hba: per adapter instance + * @scale_up: True if scaling up and false if scaling down + * + * Returns 0 if successful + * Returns < 0 for any other errors + */ +static int ufshcd_scale_clks(struct ufs_hba *hba, bool scale_up) +{ + int ret = 0; + struct ufs_clk_info *clki; + struct list_head *head = &hba->clk_list_head; + + if (!head || list_empty(head)) + goto out; + + list_for_each_entry(clki, head, list) { + if (!IS_ERR_OR_NULL(clki->clk)) { + if (scale_up && clki->max_freq) { + if (clki->curr_freq == clki->max_freq) + continue; + + ret = clk_set_rate(clki->clk, clki->max_freq); + if (ret) { + dev_err(hba->dev, "%s: %s clk set rate(%dHz) failed, %d\n", + __func__, clki->name, + clki->max_freq, ret); + break; + } + trace_ufshcd_clk_scaling(dev_name(hba->dev), + "scaled up", clki->name, + clki->curr_freq, + clki->max_freq); + clki->curr_freq = clki->max_freq; + + } else if (!scale_up && clki->min_freq) { + if (clki->curr_freq == clki->min_freq) + continue; + + ret = clk_set_rate(clki->clk, clki->min_freq); + if (ret) { + dev_err(hba->dev, "%s: %s clk set rate(%dHz) failed, %d\n", + __func__, clki->name, + clki->min_freq, ret); + break; + } + trace_ufshcd_clk_scaling(dev_name(hba->dev), + "scaled down", clki->name, + clki->curr_freq, + clki->min_freq); + clki->curr_freq = clki->min_freq; + } + } + dev_dbg(hba->dev, "%s: clk: %s, rate: %lu\n", __func__, + clki->name, clk_get_rate(clki->clk)); + } + +out: + return ret; +} + static void ufshcd_ungate_work(struct work_struct *work) { int ret; @@ -5868,6 +5930,9 @@ static int ufshcd_host_reset_and_restore(struct ufs_hba *hba) ufshcd_hba_stop(hba, false); spin_unlock_irqrestore(hba->host->host_lock, flags); + /* scale up clocks to max frequency before full reinitialization */ + ufshcd_scale_clks(hba, true); + err = ufshcd_hba_enable(hba); if (err) goto out; @@ -6415,6 +6480,9 @@ static int ufshcd_probe_hba(struct ufs_hba *hba) /* Resume devfreq after UFS device is detected */ if (ufshcd_is_clkscaling_supported(hba)) { + memcpy(&hba->clk_scaling.saved_pwr_info.info, &hba->pwr_info, + sizeof(struct ufs_pa_layer_attr)); + hba->clk_scaling.saved_pwr_info.is_valid = true; ufshcd_resume_clkscaling(hba); hba->clk_scaling.is_allowed = true; } @@ -8004,17 +8072,21 @@ out_error: } EXPORT_SYMBOL(ufshcd_alloc_host); -static int ufshcd_scale_clks(struct ufs_hba *hba, bool scale_up) +/** + * ufshcd_is_devfreq_scaling_required - check if scaling is required or not + * @hba: per adapter instance + * @scale_up: True if scaling up and false if scaling down + * + * Returns true if scaling is required, false otherwise. + */ +static bool ufshcd_is_devfreq_scaling_required(struct ufs_hba *hba, + bool scale_up) { - int ret = 0; struct ufs_clk_info *clki; struct list_head *head = &hba->clk_list_head; - ktime_t start = ktime_get(); - bool clk_state_changed = false; - unsigned long flags; if (!head || list_empty(head)) - goto out; + return false; ret = ufshcd_vops_clk_scale_notify(hba, scale_up, PRE_CHANGE); if (ret) @@ -8025,57 +8097,122 @@ static int ufshcd_scale_clks(struct ufs_hba *hba, bool scale_up) if (scale_up && clki->max_freq) { if (clki->curr_freq == clki->max_freq) continue; - - clk_state_changed = true; - ret = clk_set_rate(clki->clk, clki->max_freq); - if (ret) { - dev_err(hba->dev, "%s: %s clk set rate(%dHz) failed, %d\n", - __func__, clki->name, - clki->max_freq, ret); - break; - } - trace_ufshcd_clk_scaling(dev_name(hba->dev), - "scaled up", clki->name, - clki->curr_freq, - clki->max_freq); - - clki->curr_freq = clki->max_freq; - + return true; } else if (!scale_up && clki->min_freq) { if (clki->curr_freq == clki->min_freq) continue; - - clk_state_changed = true; - ret = clk_set_rate(clki->clk, clki->min_freq); - if (ret) { - dev_err(hba->dev, "%s: %s clk set rate(%dHz) failed, %d\n", - __func__, clki->name, - clki->min_freq, ret); - break; - } - trace_ufshcd_clk_scaling(dev_name(hba->dev), - "scaled down", clki->name, - clki->curr_freq, - clki->min_freq); - clki->curr_freq = clki->min_freq; + return true; } } - dev_dbg(hba->dev, "%s: clk: %s, rate: %lu\n", __func__, - clki->name, clk_get_rate(clki->clk)); } - /* Suspend PM QoS voting when clocks are scaled down and vise versa */ - spin_lock_irqsave(hba->host->host_lock, flags); - hba->pm_qos.is_suspended = !scale_up; - spin_unlock_irqrestore(hba->host->host_lock, flags); + return false; +} + +/** + * ufshcd_scale_gear - scale up/down UFS gear + * @hba: per adapter instance + * @scale_up: True for scaling up gear and false for scaling down + * + * Returns 0 for success, + * Returns -EBUSY if scaling can't happen at this time + * Returns non-zero for any other errors + */ +static int ufshcd_scale_gear(struct ufs_hba *hba, bool scale_up) +{ + #define UFS_MIN_GEAR_TO_SCALE_DOWN UFS_HS_G2 + #define DOORBELL_CLR_TOUT_US (1000 * 1000) /* 1 sec */ + int ret = 0; + struct ufs_pa_layer_attr new_pwr_info; + + BUG_ON(!hba->clk_scaling.saved_pwr_info.is_valid); + + if (scale_up) { + memcpy(&new_pwr_info, &hba->clk_scaling.saved_pwr_info.info, + sizeof(struct ufs_pa_layer_attr)); + } else { + memcpy(&new_pwr_info, &hba->pwr_info, + sizeof(struct ufs_pa_layer_attr)); + + if (hba->pwr_info.gear_tx > UFS_MIN_GEAR_TO_SCALE_DOWN + || hba->pwr_info.gear_rx > UFS_MIN_GEAR_TO_SCALE_DOWN) { + /* save the current power mode */ + memcpy(&hba->clk_scaling.saved_pwr_info.info, + &hba->pwr_info, + sizeof(struct ufs_pa_layer_attr)); + + /* scale down gear */ + new_pwr_info.gear_tx = UFS_MIN_GEAR_TO_SCALE_DOWN; + new_pwr_info.gear_rx = UFS_MIN_GEAR_TO_SCALE_DOWN; + } + } + + /* check if the power mode needs to be changed or not? */ + if (memcmp(&new_pwr_info, &hba->pwr_info, + sizeof(struct ufs_pa_layer_attr))) { + /* + * make sure that there are no outstanding requests when + * power mode change is requested + */ + scsi_block_requests(hba->host); + if (ufshcd_wait_for_doorbell_clr(hba, DOORBELL_CLR_TOUT_US)) { + ret = -EBUSY; + goto out_unblock; + } + + ret = ufshcd_change_power_mode(hba, &new_pwr_info); +out_unblock: + scsi_unblock_requests(hba->host); + } + + if (ret) + dev_err(hba->dev, "%s: failed err %d, old gear: (tx %d rx %d), new gear: (tx %d rx %d)", + __func__, ret, + hba->pwr_info.gear_tx, hba->pwr_info.gear_tx, + new_pwr_info.gear_tx, new_pwr_info.gear_rx); + + return ret; +} + +/** + * ufshcd_devfreq_scale - scale up/down UFS clocks and gear + * @hba: per adapter instance + * @scale_up: True for scaling up and false for scalin down + * + * Returns 0 for success, + * Returns -EBUSY if scaling can't happen at this time + * Returns non-zero for any other errors + */ +static int ufshcd_devfreq_scale(struct ufs_hba *hba, bool scale_up) +{ + int ret = 0; + + /* scale down the gear before scaling down clocks */ + if (!scale_up) { + ret = ufshcd_scale_gear(hba, false); + if (ret) + goto out; + } + + ret = ufshcd_scale_clks(hba, scale_up); + if (ret) { + if (!scale_up) + ufshcd_scale_gear(hba, true); + goto out; + } + + /* scale up the gear after scaling up clocks */ + if (scale_up) { + ret = ufshcd_scale_gear(hba, true); + if (ret) { + ufshcd_scale_clks(hba, false); + goto out; + } + } ret = ufshcd_vops_clk_scale_notify(hba, scale_up, POST_CHANGE); out: - if (clk_state_changed) - trace_ufshcd_profile_clk_scaling(dev_name(hba->dev), - (scale_up ? "up" : "down"), - ktime_to_us(ktime_sub(ktime_get(), start)), ret); return ret; } @@ -8122,7 +8259,7 @@ static ssize_t ufshcd_clkscale_enable_store(struct device *dev, ufshcd_resume_clkscaling(hba); } else { ufshcd_suspend_clkscaling(hba); - err = ufshcd_scale_clks(hba, true); + err = ufshcd_devfreq_scale(hba, true); if (err) dev_err(hba->dev, "%s: failed to scale clocks up %d\n", __func__, err); @@ -8138,18 +8275,38 @@ out: static int ufshcd_devfreq_target(struct device *dev, unsigned long *freq, u32 flags) { - int err = 0; + int ret = 0; struct ufs_hba *hba = dev_get_drvdata(dev); + unsigned long irq_flags; + ktime_t start; + bool scale_up; if (!ufshcd_is_clkscaling_supported(hba)) return -EINVAL; - if (*freq == UINT_MAX) - err = ufshcd_scale_clks(hba, true); - else if (*freq == 0) - err = ufshcd_scale_clks(hba, false); + if ((*freq > 0) && (*freq < UINT_MAX)) { + dev_err(hba->dev, "%s: invalid freq = %lu\n", __func__, *freq); + return -EINVAL; + } - return err; + scale_up = (*freq == UINT_MAX) ? true : false; + if (!ufshcd_is_devfreq_scaling_required(hba, scale_up)) + return 0; /* no state change required */ + + start = ktime_get(); + + ret = ufshcd_devfreq_scale(hba, scale_up); + + /* suspend PM QoS voting when scaled down and vise versa */ + spin_lock_irqsave(hba->host->host_lock, irq_flags); + hba->pm_qos.is_suspended = !scale_up; + spin_unlock_irqrestore(hba->host->host_lock, irq_flags); + + trace_ufshcd_profile_clk_scaling(dev_name(hba->dev), + (scale_up ? "up" : "down"), + ktime_to_us(ktime_sub(ktime_get(), start)), ret); + + return ret; } static int ufshcd_devfreq_get_dev_status(struct device *dev, diff --git a/include/linux/scsi/ufs/ufshcd.h b/include/linux/scsi/ufs/ufshcd.h index 5e17d1b26a98..a94e3f3f7e64 100644 --- a/include/linux/scsi/ufs/ufshcd.h +++ b/include/linux/scsi/ufs/ufshcd.h @@ -419,6 +419,11 @@ struct ufs_hibern8_on_idle { bool is_enabled; }; +struct ufs_saved_pwr_info { + struct ufs_pa_layer_attr info; + bool is_valid; +}; + struct ufs_clk_scaling { ktime_t busy_start_t; bool is_busy_started; @@ -426,6 +431,7 @@ struct ufs_clk_scaling { unsigned long window_start_t; struct device_attribute enable_attr; bool is_allowed; + struct ufs_saved_pwr_info saved_pwr_info; }; /** |