diff options
Diffstat (limited to 'drivers/scsi/ufs')
-rw-r--r-- | drivers/scsi/ufs/Kconfig | 2 | ||||
-rw-r--r-- | drivers/scsi/ufs/ufshcd.c | 178 | ||||
-rw-r--r-- | drivers/scsi/ufs/ufshcd.h | 20 |
3 files changed, 199 insertions, 1 deletions
diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig index f07f90179bbc..6e07b2afddeb 100644 --- a/drivers/scsi/ufs/Kconfig +++ b/drivers/scsi/ufs/Kconfig @@ -35,6 +35,8 @@ config SCSI_UFSHCD tristate "Universal Flash Storage Controller Driver Core" depends on SCSI && SCSI_DMA + select PM_DEVFREQ + select DEVFREQ_GOV_SIMPLE_ONDEMAND ---help--- This selects the support for UFS devices in Linux, say Y and make sure that you know the name of your UFS host adapter (the card diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index 6f1ea5192db6..df525e3b2c19 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -38,6 +38,7 @@ */ #include <linux/async.h> +#include <linux/devfreq.h> #include "ufshcd.h" #include "unipro.h" @@ -545,6 +546,8 @@ static void ufshcd_ungate_work(struct work_struct *work) hba->clk_gating.is_suspended = false; } unblock_reqs: + if (ufshcd_is_clkscaling_enabled(hba)) + devfreq_resume_device(hba->devfreq); scsi_unblock_requests(hba->host); } @@ -561,10 +564,10 @@ int ufshcd_hold(struct ufs_hba *hba, bool async) if (!ufshcd_is_clkgating_allowed(hba)) goto out; -start: spin_lock_irqsave(hba->host->host_lock, flags); hba->clk_gating.active_reqs++; +start: switch (hba->clk_gating.state) { case CLKS_ON: break; @@ -596,6 +599,7 @@ start: spin_unlock_irqrestore(hba->host->host_lock, flags); flush_work(&hba->clk_gating.ungate_work); /* Make sure state is CLKS_ON before returning */ + spin_lock_irqsave(hba->host->host_lock, flags); goto start; default: dev_err(hba->dev, "%s: clk gating is in invalid state %d\n", @@ -636,6 +640,11 @@ static void ufshcd_gate_work(struct work_struct *work) ufshcd_set_link_hibern8(hba); } + if (ufshcd_is_clkscaling_enabled(hba)) { + devfreq_suspend_device(hba->devfreq); + hba->clk_scaling.window_start_t = 0; + } + if (!ufshcd_is_link_active(hba)) ufshcd_setup_clocks(hba, false); else @@ -737,6 +746,32 @@ static void ufshcd_exit_clk_gating(struct ufs_hba *hba) device_remove_file(hba->dev, &hba->clk_gating.delay_attr); } +/* Must be called with host lock acquired */ +static void ufshcd_clk_scaling_start_busy(struct ufs_hba *hba) +{ + if (!ufshcd_is_clkscaling_enabled(hba)) + return; + + if (!hba->clk_scaling.is_busy_started) { + hba->clk_scaling.busy_start_t = ktime_get(); + hba->clk_scaling.is_busy_started = true; + } +} + +static void ufshcd_clk_scaling_update_busy(struct ufs_hba *hba) +{ + struct ufs_clk_scaling *scaling = &hba->clk_scaling; + + if (!ufshcd_is_clkscaling_enabled(hba)) + return; + + if (!hba->outstanding_reqs && scaling->is_busy_started) { + scaling->tot_busy_t += ktime_to_us(ktime_sub(ktime_get(), + scaling->busy_start_t)); + scaling->busy_start_t = ktime_set(0, 0); + scaling->is_busy_started = false; + } +} /** * ufshcd_send_command - Send SCSI or device management commands * @hba: per adapter instance @@ -745,6 +780,7 @@ static void ufshcd_exit_clk_gating(struct ufs_hba *hba) static inline void ufshcd_send_command(struct ufs_hba *hba, unsigned int task_tag) { + ufshcd_clk_scaling_start_busy(hba); __set_bit(task_tag, &hba->outstanding_reqs); ufshcd_writel(hba, 1 << task_tag, REG_UTP_TRANSFER_REQ_DOOR_BELL); } @@ -3027,6 +3063,8 @@ static void ufshcd_transfer_req_compl(struct ufs_hba *hba) /* clear corresponding bits of completed commands */ hba->outstanding_reqs ^= completed_reqs; + ufshcd_clk_scaling_update_busy(hba); + /* we might have free'd some tags above */ wake_up(&hba->dev_cmd.tag_wq); } @@ -4151,6 +4189,10 @@ static int ufshcd_probe_hba(struct ufs_hba *hba) if (!hba->is_init_prefetch) hba->is_init_prefetch = true; + /* Resume devfreq after UFS device is detected */ + if (ufshcd_is_clkscaling_enabled(hba)) + devfreq_resume_device(hba->devfreq); + out: /* * If we failed to initialize the device or the device is not @@ -4472,6 +4514,7 @@ static int ufshcd_init_clocks(struct ufs_hba *hba) clki->max_freq, ret); goto out; } + clki->curr_freq = clki->max_freq; } dev_dbg(dev, "%s: clk: %s, rate: %lu\n", __func__, clki->name, clk_get_rate(clki->clk)); @@ -4868,6 +4911,15 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op) disable_clks: /* + * The clock scaling needs access to controller registers. Hence, Wait + * for pending clock scaling work to be done before clocks are + * turned off. + */ + if (ufshcd_is_clkscaling_enabled(hba)) { + devfreq_suspend_device(hba->devfreq); + hba->clk_scaling.window_start_t = 0; + } + /* * Call vendor specific suspend callback. As these callbacks may access * vendor specific host controller register space call them before the * host clocks are ON. @@ -4989,6 +5041,9 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op) ufshcd_disable_auto_bkops(hba); hba->clk_gating.is_suspended = false; + if (ufshcd_is_clkscaling_enabled(hba)) + devfreq_resume_device(hba->devfreq); + /* Schedule clock gating in case of no access to UFS device yet */ ufshcd_release(hba); goto out; @@ -5172,6 +5227,8 @@ void ufshcd_remove(struct ufs_hba *hba) scsi_host_put(hba->host); ufshcd_exit_clk_gating(hba); + if (ufshcd_is_clkscaling_enabled(hba)) + devfreq_remove_device(hba->devfreq); ufshcd_hba_exit(hba); } EXPORT_SYMBOL_GPL(ufshcd_remove); @@ -5228,6 +5285,112 @@ out_error: } EXPORT_SYMBOL(ufshcd_alloc_host); +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; + } + 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; + } + clki->curr_freq = clki->min_freq; + } + } + dev_dbg(hba->dev, "%s: clk: %s, rate: %lu\n", __func__, + clki->name, clk_get_rate(clki->clk)); + } + if (hba->vops->clk_scale_notify) + hba->vops->clk_scale_notify(hba); +out: + return ret; +} + +static int ufshcd_devfreq_target(struct device *dev, + unsigned long *freq, u32 flags) +{ + int err = 0; + struct ufs_hba *hba = dev_get_drvdata(dev); + + if (!ufshcd_is_clkscaling_enabled(hba)) + return -EINVAL; + + if (*freq == UINT_MAX) + err = ufshcd_scale_clks(hba, true); + else if (*freq == 0) + err = ufshcd_scale_clks(hba, false); + + return err; +} + +static int ufshcd_devfreq_get_dev_status(struct device *dev, + struct devfreq_dev_status *stat) +{ + struct ufs_hba *hba = dev_get_drvdata(dev); + struct ufs_clk_scaling *scaling = &hba->clk_scaling; + unsigned long flags; + + if (!ufshcd_is_clkscaling_enabled(hba)) + return -EINVAL; + + memset(stat, 0, sizeof(*stat)); + + spin_lock_irqsave(hba->host->host_lock, flags); + if (!scaling->window_start_t) + goto start_window; + + if (scaling->is_busy_started) + scaling->tot_busy_t += ktime_to_us(ktime_sub(ktime_get(), + scaling->busy_start_t)); + + stat->total_time = jiffies_to_usecs((long)jiffies - + (long)scaling->window_start_t); + stat->busy_time = scaling->tot_busy_t; +start_window: + scaling->window_start_t = jiffies; + scaling->tot_busy_t = 0; + + if (hba->outstanding_reqs) { + scaling->busy_start_t = ktime_get(); + scaling->is_busy_started = true; + } else { + scaling->busy_start_t = ktime_set(0, 0); + scaling->is_busy_started = false; + } + spin_unlock_irqrestore(hba->host->host_lock, flags); + return 0; +} + +static struct devfreq_dev_profile ufs_devfreq_profile = { + .polling_ms = 100, + .target = ufshcd_devfreq_target, + .get_dev_status = ufshcd_devfreq_get_dev_status, +}; + /** * ufshcd_init - Driver initialization routine * @hba: per-adapter instance @@ -5337,6 +5500,19 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) goto out_remove_scsi_host; } + if (ufshcd_is_clkscaling_enabled(hba)) { + hba->devfreq = devfreq_add_device(dev, &ufs_devfreq_profile, + "simple_ondemand", NULL); + if (IS_ERR(hba->devfreq)) { + dev_err(hba->dev, "Unable to register with devfreq %ld\n", + PTR_ERR(hba->devfreq)); + goto out_remove_scsi_host; + } + /* Suspend devfreq until the UFS device is detected */ + devfreq_suspend_device(hba->devfreq); + hba->clk_scaling.window_start_t = 0; + } + /* Hold auto suspend until async scan completes */ pm_runtime_get_sync(dev); diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h index ac72a6daf50e..908db3eb0609 100644 --- a/drivers/scsi/ufs/ufshcd.h +++ b/drivers/scsi/ufs/ufshcd.h @@ -210,6 +210,7 @@ struct ufs_dev_cmd { * @name: clock name * @max_freq: maximum frequency supported by the clock * @min_freq: min frequency that can be used for clock scaling + * @curr_freq: indicates the current frequency that it is set to * @enabled: variable to check against multiple enable/disable */ struct ufs_clk_info { @@ -218,6 +219,7 @@ struct ufs_clk_info { const char *name; u32 max_freq; u32 min_freq; + u32 curr_freq; bool enabled; }; @@ -244,6 +246,7 @@ struct ufs_pwr_mode_info { * @name: variant name * @init: called when the driver is initialized * @exit: called to cleanup everything done in init + * @clk_scale_notify: notifies that clks are scaled up/down * @setup_clocks: called before touching any of the controller registers * @setup_regulators: called before accessing the host controller * @hce_enable_notify: called before and after HCE enable bit is set to allow @@ -260,6 +263,7 @@ struct ufs_hba_variant_ops { const char *name; int (*init)(struct ufs_hba *); void (*exit)(struct ufs_hba *); + void (*clk_scale_notify)(struct ufs_hba *); int (*setup_clocks)(struct ufs_hba *, bool); int (*setup_regulators)(struct ufs_hba *, bool); int (*hce_enable_notify)(struct ufs_hba *, bool); @@ -303,6 +307,13 @@ struct ufs_clk_gating { int active_reqs; }; +struct ufs_clk_scaling { + ktime_t busy_start_t; + bool is_busy_started; + unsigned long tot_busy_t; + unsigned long window_start_t; +}; + /** * struct ufs_init_prefetch - contains data that is pre-fetched once during * initialization @@ -456,6 +467,11 @@ struct ufs_hba { #define UFSHCD_CAP_CLK_GATING (1 << 0) /* Allow hiberb8 with clk gating */ #define UFSHCD_CAP_HIBERN8_WITH_CLK_GATING (1 << 1) + /* Allow dynamic clk scaling */ +#define UFSHCD_CAP_CLK_SCALING (1 << 2) + + struct devfreq *devfreq; + struct ufs_clk_scaling clk_scaling; }; /* Returns true if clocks can be gated. Otherwise false */ @@ -467,6 +483,10 @@ static inline bool ufshcd_can_hibern8_during_gating(struct ufs_hba *hba) { return hba->caps & UFSHCD_CAP_HIBERN8_WITH_CLK_GATING; } +static inline int ufshcd_is_clkscaling_enabled(struct ufs_hba *hba) +{ + return hba->caps & UFSHCD_CAP_CLK_SCALING; +} #define ufshcd_writel(hba, val, reg) \ writel((val), (hba)->mmio_base + (reg)) #define ufshcd_readl(hba, reg) \ |