From a4ef3c82e8ac9cf072e947f80b7f176df8698e71 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Thu, 9 Oct 2014 02:54:41 -0700 Subject: [PATCH] scsi: ufs: Add sysfs node to dynamically control hibern8 on idle Provide an option to enable/disable hibern8 on idle functionality during runtime. Write 1 or 0 to "hibern8_on_idle_enable" sysfs node to enable/disable hibern8 on idle functionality. Change-Id: Id4b6253c3c53ed71575c05596abbd4dd99821eff [subhashj@codeaurora.org: resolved trivial merge conflicts] Signed-off-by: Subhash Jadavani [venkatg@codeaurora.org: resolved trivial merge conflicts] Signed-off-by: Venkat Gopalakrishnan --- drivers/scsi/ufs/ufshcd.c | 97 +++++++++++++++++++++++++++++++++++++++-- include/linux/scsi/ufs/ufshcd.h | 6 +++ 2 files changed, 99 insertions(+), 4 deletions(-) diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index 13e411e2ac27..070bca13f481 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -274,6 +274,7 @@ static inline bool ufshcd_is_valid_pm_lvl(int lvl) return false; } +static irqreturn_t ufshcd_intr(int irq, void *__hba); static void ufshcd_tmc_handler(struct ufs_hba *hba); static void ufshcd_async_scan(void *data, async_cookie_t cookie); static int ufshcd_reset_and_restore(struct ufs_hba *hba); @@ -290,7 +291,7 @@ static inline void ufshcd_add_delay_before_dme_cmd(struct ufs_hba *hba); static int ufshcd_host_reset_and_restore(struct ufs_hba *hba); static void ufshcd_resume_clkscaling(struct ufs_hba *hba); static void ufshcd_suspend_clkscaling(struct ufs_hba *hba); -static irqreturn_t ufshcd_intr(int irq, void *__hba); +static void ufshcd_release_all(struct ufs_hba *hba); static inline int ufshcd_enable_irq(struct ufs_hba *hba) { @@ -912,7 +913,8 @@ static void ufshcd_gate_work(struct work_struct *work) spin_unlock_irqrestore(hba->host->host_lock, flags); - if (ufshcd_is_hibern8_on_idle_allowed(hba)) + if (ufshcd_is_hibern8_on_idle_allowed(hba) && + hba->hibern8_on_idle.is_enabled) /* * Hibern8 enter work (on Idle) needs clocks to be ON hence * make sure that it is flushed before turning off the clocks. @@ -1297,6 +1299,70 @@ unblock_reqs: scsi_unblock_requests(hba->host); } +static ssize_t ufshcd_hibern8_on_idle_delay_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->hibern8_on_idle.delay_ms); +} + +static ssize_t ufshcd_hibern8_on_idle_delay_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->hibern8_on_idle.delay_ms = value; + spin_unlock_irqrestore(hba->host->host_lock, flags); + return count; +} + +static ssize_t ufshcd_hibern8_on_idle_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ufs_hba *hba = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", + hba->hibern8_on_idle.is_enabled); +} + +static ssize_t ufshcd_hibern8_on_idle_enable_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; + u32 value; + + if (kstrtou32(buf, 0, &value)) + return -EINVAL; + + value = !!value; + if (value == hba->hibern8_on_idle.is_enabled) + goto out; + + if (value) { + /* + * As clock gating work would wait for the hibern8 enter work + * to finish, clocks would remain on during hibern8 enter work. + */ + ufshcd_hold(hba, false); + ufshcd_release_all(hba); + } else { + spin_lock_irqsave(hba->host->host_lock, flags); + hba->hibern8_on_idle.active_reqs++; + spin_unlock_irqrestore(hba->host->host_lock, flags); + } + + hba->hibern8_on_idle.is_enabled = value; +out: + return count; +} + static void ufshcd_init_hibern8_on_idle(struct ufs_hba *hba) { if (!ufshcd_is_hibern8_on_idle_allowed(hba)) @@ -1308,13 +1374,35 @@ static void ufshcd_init_hibern8_on_idle(struct ufs_hba *hba) hba->hibern8_on_idle.delay_ms = 10; hba->hibern8_on_idle.state = HIBERN8_EXITED; + hba->hibern8_on_idle.is_enabled = true; + + hba->hibern8_on_idle.delay_attr.show = + ufshcd_hibern8_on_idle_delay_show; + hba->hibern8_on_idle.delay_attr.store = + ufshcd_hibern8_on_idle_delay_store; + sysfs_attr_init(&hba->hibern8_on_idle.delay_attr.attr); + hba->hibern8_on_idle.delay_attr.attr.name = "hibern8_on_idle_delay_ms"; + hba->hibern8_on_idle.delay_attr.attr.mode = S_IRUGO | S_IWUSR; + if (device_create_file(hba->dev, &hba->hibern8_on_idle.delay_attr)) + dev_err(hba->dev, "Failed to create sysfs for hibern8_on_idle_delay\n"); + + hba->hibern8_on_idle.enable_attr.show = + ufshcd_hibern8_on_idle_enable_show; + hba->hibern8_on_idle.enable_attr.store = + ufshcd_hibern8_on_idle_enable_store; + sysfs_attr_init(&hba->hibern8_on_idle.enable_attr.attr); + hba->hibern8_on_idle.enable_attr.attr.name = "hibern8_on_idle_enable"; + hba->hibern8_on_idle.enable_attr.attr.mode = S_IRUGO | S_IWUSR; + if (device_create_file(hba->dev, &hba->hibern8_on_idle.enable_attr)) + dev_err(hba->dev, "Failed to create sysfs for hibern8_on_idle_enable\n"); } static void ufshcd_exit_hibern8_on_idle(struct ufs_hba *hba) { if (!ufshcd_is_hibern8_on_idle_allowed(hba)) return; - /* Don't have anything to do for now */ + device_remove_file(hba->dev, &hba->hibern8_on_idle.delay_attr); + device_remove_file(hba->dev, &hba->hibern8_on_idle.enable_attr); } static void ufshcd_hold_all(struct ufs_hba *hba) @@ -6322,7 +6410,8 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op) * If we can't transition into any of the low power modes * just gate the clocks. */ - WARN_ON(hba->hibern8_on_idle.active_reqs); + WARN_ON(hba->hibern8_on_idle.is_enabled && + hba->hibern8_on_idle.active_reqs); ufshcd_hold_all(hba); hba->clk_gating.is_suspended = true; hba->hibern8_on_idle.is_suspended = true; diff --git a/include/linux/scsi/ufs/ufshcd.h b/include/linux/scsi/ufs/ufshcd.h index a80130c959eb..c83a58831b25 100644 --- a/include/linux/scsi/ufs/ufshcd.h +++ b/include/linux/scsi/ufs/ufshcd.h @@ -362,6 +362,9 @@ enum ufshcd_hibern8_on_idle_state { * during suspend/resume * @active_reqs: number of requests that are pending and should be waited for * completion before scheduling delayed "enter_work". + * @delay_attr: sysfs attribute to control delay_attr + * @enable_attr: sysfs attribute to enable/disable hibern8 on idle + * @is_enabled: Indicates the current status of hibern8 */ struct ufs_hibern8_on_idle { struct delayed_work enter_work; @@ -370,6 +373,9 @@ struct ufs_hibern8_on_idle { unsigned long delay_ms; bool is_suspended; int active_reqs; + struct device_attribute delay_attr; + struct device_attribute enable_attr; + bool is_enabled; }; struct ufs_clk_scaling { -- 2.11.0