summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/scsi/ufs/ufshcd.c161
-rw-r--r--drivers/scsi/ufs/ufshcd.h14
2 files changed, 147 insertions, 28 deletions
diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index 9db4fcc3ed5b..570d93545d8c 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -217,6 +217,9 @@ void ufshcd_update_query_stats(struct ufs_hba *hba,
/* default value of auto suspend is 3 seconds */
#define UFSHCD_AUTO_SUSPEND_DELAY_MS 3000 /* millisecs */
+#define UFSHCD_CLK_GATING_DELAY_MS_PWR_SAVE 10
+#define UFSHCD_CLK_GATING_DELAY_MS_PERF 50
+
/* IOCTL opcode for command - ufs set device read only */
#define UFS_IOCTL_BLKROSET BLKROSET
@@ -1332,8 +1335,6 @@ out:
/* host lock must be held before calling this variant */
static void __ufshcd_release(struct ufs_hba *hba, bool no_sched)
{
- unsigned long delay_in_jiffies;
-
if (!ufshcd_is_clkgating_allowed(hba))
return;
@@ -1349,19 +1350,8 @@ static void __ufshcd_release(struct ufs_hba *hba, bool no_sched)
hba->clk_gating.state = REQ_CLKS_OFF;
trace_ufshcd_clk_gating(dev_name(hba->dev), hba->clk_gating.state);
- /*
- * Scheduling the delayed work after 1 jiffies will make the work to
- * get schedule any time from 0ms to 1000/HZ ms which is not desirable
- * for hibern8 enter work as it may impact the performance if it gets
- * scheduled almost immediately. Hence make sure that hibern8 enter
- * work gets scheduled atleast after 2 jiffies (any time between
- * 1000/HZ ms to 2000/HZ ms).
- */
- delay_in_jiffies = msecs_to_jiffies(hba->clk_gating.delay_ms);
- if (delay_in_jiffies == 1)
- delay_in_jiffies++;
-
- schedule_delayed_work(&hba->clk_gating.gate_work, delay_in_jiffies);
+ schedule_delayed_work(&hba->clk_gating.gate_work,
+ msecs_to_jiffies(hba->clk_gating.delay_ms));
}
void ufshcd_release(struct ufs_hba *hba, bool no_sched)
@@ -1397,6 +1387,63 @@ static ssize_t ufshcd_clkgate_delay_store(struct device *dev,
return count;
}
+static ssize_t ufshcd_clkgate_delay_pwr_save_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%lu\n",
+ hba->clk_gating.delay_ms_pwr_save);
+}
+
+static ssize_t ufshcd_clkgate_delay_pwr_save_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ unsigned long flags, value;
+
+ if (kstrtoul(buf, 0, &value))
+ return -EINVAL;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+
+ hba->clk_gating.delay_ms_pwr_save = value;
+ if (ufshcd_is_clkscaling_supported(hba) &&
+ !hba->clk_scaling.is_scaled_up)
+ hba->clk_gating.delay_ms = hba->clk_gating.delay_ms_pwr_save;
+
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ return count;
+}
+
+static ssize_t ufshcd_clkgate_delay_perf_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%lu\n", hba->clk_gating.delay_ms_perf);
+}
+
+static ssize_t ufshcd_clkgate_delay_perf_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev);
+ unsigned long flags, value;
+
+ if (kstrtoul(buf, 0, &value))
+ return -EINVAL;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+
+ hba->clk_gating.delay_ms_perf = value;
+ if (ufshcd_is_clkscaling_supported(hba) &&
+ hba->clk_scaling.is_scaled_up)
+ hba->clk_gating.delay_ms = hba->clk_gating.delay_ms_perf;
+
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ return count;
+}
+
static ssize_t ufshcd_clkgate_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -1434,15 +1481,58 @@ out:
static void ufshcd_init_clk_gating(struct ufs_hba *hba)
{
+ struct ufs_clk_gating *gating = &hba->clk_gating;
+
if (!ufshcd_is_clkgating_allowed(hba))
return;
- hba->clk_gating.delay_ms = 10;
- INIT_DELAYED_WORK(&hba->clk_gating.gate_work, ufshcd_gate_work);
- INIT_WORK(&hba->clk_gating.ungate_work, ufshcd_ungate_work);
+ INIT_DELAYED_WORK(&gating->gate_work, ufshcd_gate_work);
+ INIT_WORK(&gating->ungate_work, ufshcd_ungate_work);
- hba->clk_gating.is_enabled = true;
+ gating->is_enabled = true;
+ /*
+ * Scheduling the delayed work after 1 jiffies will make the work to
+ * get schedule any time from 0ms to 1000/HZ ms which is not desirable
+ * for hibern8 enter work as it may impact the performance if it gets
+ * scheduled almost immediately. Hence make sure that hibern8 enter
+ * work gets scheduled atleast after 2 jiffies (any time between
+ * 1000/HZ ms to 2000/HZ ms).
+ */
+ gating->delay_ms_pwr_save = jiffies_to_msecs(
+ max_t(unsigned long,
+ msecs_to_jiffies(UFSHCD_CLK_GATING_DELAY_MS_PWR_SAVE),
+ 2));
+ gating->delay_ms_perf = jiffies_to_msecs(
+ max_t(unsigned long,
+ msecs_to_jiffies(UFSHCD_CLK_GATING_DELAY_MS_PERF),
+ 2));
+
+ /* start with performance mode */
+ gating->delay_ms = gating->delay_ms_perf;
+
+ if (!ufshcd_is_clkscaling_supported(hba))
+ goto scaling_not_supported;
+
+ gating->delay_pwr_save_attr.show = ufshcd_clkgate_delay_pwr_save_show;
+ gating->delay_pwr_save_attr.store = ufshcd_clkgate_delay_pwr_save_store;
+ sysfs_attr_init(&gating->delay_pwr_save_attr.attr);
+ gating->delay_pwr_save_attr.attr.name = "clkgate_delay_ms_pwr_save";
+ gating->delay_pwr_save_attr.attr.mode = S_IRUGO | S_IWUSR;
+ if (device_create_file(hba->dev, &gating->delay_pwr_save_attr))
+ dev_err(hba->dev, "Failed to create sysfs for clkgate_delay_ms_pwr_save\n");
+
+ gating->delay_perf_attr.show = ufshcd_clkgate_delay_perf_show;
+ gating->delay_perf_attr.store = ufshcd_clkgate_delay_perf_store;
+ sysfs_attr_init(&gating->delay_perf_attr.attr);
+ gating->delay_perf_attr.attr.name = "clkgate_delay_ms_perf";
+ gating->delay_perf_attr.attr.mode = S_IRUGO | S_IWUSR;
+ if (device_create_file(hba->dev, &gating->delay_perf_attr))
+ dev_err(hba->dev, "Failed to create sysfs for clkgate_delay_ms_perf\n");
+
+ goto add_clkgate_enable;
+
+scaling_not_supported:
hba->clk_gating.delay_attr.show = ufshcd_clkgate_delay_show;
hba->clk_gating.delay_attr.store = ufshcd_clkgate_delay_store;
sysfs_attr_init(&hba->clk_gating.delay_attr.attr);
@@ -1451,12 +1541,13 @@ static void ufshcd_init_clk_gating(struct ufs_hba *hba)
if (device_create_file(hba->dev, &hba->clk_gating.delay_attr))
dev_err(hba->dev, "Failed to create sysfs for clkgate_delay\n");
- hba->clk_gating.enable_attr.show = ufshcd_clkgate_enable_show;
- hba->clk_gating.enable_attr.store = ufshcd_clkgate_enable_store;
- sysfs_attr_init(&hba->clk_gating.enable_attr.attr);
- hba->clk_gating.enable_attr.attr.name = "clkgate_enable";
- hba->clk_gating.enable_attr.attr.mode = S_IRUGO | S_IWUSR;
- if (device_create_file(hba->dev, &hba->clk_gating.enable_attr))
+add_clkgate_enable:
+ gating->enable_attr.show = ufshcd_clkgate_enable_show;
+ gating->enable_attr.store = ufshcd_clkgate_enable_store;
+ sysfs_attr_init(&clk_gating->enable_attr.attr);
+ gating->enable_attr.attr.name = "clkgate_enable";
+ gating->enable_attr.attr.mode = S_IRUGO | S_IWUSR;
+ if (device_create_file(hba->dev, &gating->enable_attr))
dev_err(hba->dev, "Failed to create sysfs for clkgate_enable\n");
}
@@ -1464,7 +1555,13 @@ static void ufshcd_exit_clk_gating(struct ufs_hba *hba)
{
if (!ufshcd_is_clkgating_allowed(hba))
return;
- device_remove_file(hba->dev, &hba->clk_gating.delay_attr);
+ if (ufshcd_is_clkscaling_supported(hba)) {
+ device_remove_file(hba->dev,
+ &hba->clk_gating.delay_pwr_save_attr);
+ device_remove_file(hba->dev, &hba->clk_gating.delay_perf_attr);
+ } else {
+ device_remove_file(hba->dev, &hba->clk_gating.delay_attr);
+ }
device_remove_file(hba->dev, &hba->clk_gating.enable_attr);
cancel_work_sync(&hba->clk_gating.ungate_work);
cancel_delayed_work_sync(&hba->clk_gating.gate_work);
@@ -6542,6 +6639,7 @@ static int ufshcd_probe_hba(struct ufs_hba *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;
+ hba->clk_scaling.is_scaled_up = true;
ufshcd_resume_clkscaling(hba);
hba->clk_scaling.is_allowed = true;
}
@@ -8360,6 +8458,17 @@ static int ufshcd_devfreq_scale(struct ufs_hba *hba, bool scale_up)
goto out;
}
}
+
+ if (!ret) {
+ hba->clk_scaling.is_scaled_up = scale_up;
+ if (scale_up)
+ hba->clk_gating.delay_ms =
+ hba->clk_gating.delay_ms_perf;
+ else
+ hba->clk_gating.delay_ms =
+ hba->clk_gating.delay_ms_pwr_save;
+ }
+
goto out;
scale_up_gear:
diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
index 3d1b1b317863..86b3d8b8f171 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/drivers/scsi/ufs/ufshcd.h
@@ -391,10 +391,14 @@ enum clk_gating_state {
* @ungate_work: worker to turn on clocks that will be used in case of
* interrupt context
* @state: the current clocks state
- * @delay_ms: gating delay in ms
+ * @delay_ms: current gating delay in ms
+ * @delay_ms_pwr_save: gating delay (in ms) in power save mode
+ * @delay_ms_perf: gating delay (in ms) in performance mode
* @is_suspended: clk gating is suspended when set to 1 which can be used
* during suspend/resume
- * @delay_attr: sysfs attribute to control delay_attr
+ * @delay_attr: sysfs attribute to control delay_ms if clock scaling is disabled
+ * @delay_pwr_save_attr: sysfs attribute to control delay_ms_pwr_save
+ * @delay_perf_attr: sysfs attribute to control delay_ms_perf
* @enable_attr: sysfs attribute to enable/disable clock gating
* @is_enabled: Indicates the current status of clock gating
* @active_reqs: number of requests that are pending and should be waited for
@@ -405,8 +409,12 @@ struct ufs_clk_gating {
struct work_struct ungate_work;
enum clk_gating_state state;
unsigned long delay_ms;
+ unsigned long delay_ms_pwr_save;
+ unsigned long delay_ms_perf;
bool is_suspended;
struct device_attribute delay_attr;
+ struct device_attribute delay_pwr_save_attr;
+ struct device_attribute delay_perf_attr;
struct device_attribute enable_attr;
bool is_enabled;
int active_reqs;
@@ -469,6 +477,7 @@ struct ufs_saved_pwr_info {
* @is_allowed: tracks if scaling is currently allowed or not
* @is_busy_started: tracks if busy period has started or not
* @is_suspended: tracks if devfreq is suspended or not
+ * @is_scaled_up: tracks if we are currently scaled up or scaled down
*/
struct ufs_clk_scaling {
int active_reqs;
@@ -483,6 +492,7 @@ struct ufs_clk_scaling {
bool is_allowed;
bool is_busy_started;
bool is_suspended;
+ bool is_scaled_up;
};
/**