diff options
author | Adrian Hunter <adrian.hunter@intel.com> | 2018-03-20 16:07:38 +0300 |
---|---|---|
committer | Martin K. Petersen <martin.petersen@oracle.com> | 2018-03-22 04:21:25 +0300 |
commit | ad448378825f5746c5fa37718724bc8f4e7b6945 (patch) | |
tree | 0ebb00a59acf4e870228d90900fe8c39b5dfbc30 /drivers/scsi/ufs/ufs-sysfs.c | |
parent | 114c1aa210494a02c26aa33f793e5b641df01989 (diff) | |
download | linux-ad448378825f5746c5fa37718724bc8f4e7b6945.tar.xz |
scsi: ufs: Add support for Auto-Hibernate Idle Timer
UFS host controllers may support an autonomous power management feature
called the Auto-Hibernate Idle Timer. The timer is set to the number of
microseconds of idle time before the UFS host controller will autonomously
put the link into Hibernate state. That will save power at the expense of
increased latency. Any access to the host controller interface registers
will automatically put the link out of Hibernate state. So once configured,
the feature is transparent to the driver.
Expose the Auto-Hibernate Idle Timer value via SysFS to allow users to
choose between power efficiency or lower latency. Set a default value of
150 ms.
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Acked-by: Stanislav Nijnikov <stanislav.nijnikov@wdc.com>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
Diffstat (limited to 'drivers/scsi/ufs/ufs-sysfs.c')
-rw-r--r-- | drivers/scsi/ufs/ufs-sysfs.c | 76 |
1 files changed, 76 insertions, 0 deletions
diff --git a/drivers/scsi/ufs/ufs-sysfs.c b/drivers/scsi/ufs/ufs-sysfs.c index 4ff9e0b7eba1..8d9332bb7d0c 100644 --- a/drivers/scsi/ufs/ufs-sysfs.c +++ b/drivers/scsi/ufs/ufs-sysfs.c @@ -3,6 +3,7 @@ #include <linux/err.h> #include <linux/string.h> +#include <linux/bitfield.h> #include <asm/unaligned.h> #include "ufs.h" @@ -117,12 +118,86 @@ static ssize_t spm_target_link_state_show(struct device *dev, ufs_pm_lvl_states[hba->spm_lvl].link_state)); } +static void ufshcd_auto_hibern8_update(struct ufs_hba *hba, u32 ahit) +{ + unsigned long flags; + + if (!(hba->capabilities & MASK_AUTO_HIBERN8_SUPPORT)) + return; + + spin_lock_irqsave(hba->host->host_lock, flags); + if (hba->ahit == ahit) + goto out_unlock; + hba->ahit = ahit; + if (!pm_runtime_suspended(hba->dev)) + ufshcd_writel(hba, hba->ahit, REG_AUTO_HIBERNATE_IDLE_TIMER); +out_unlock: + spin_unlock_irqrestore(hba->host->host_lock, flags); +} + +/* Convert Auto-Hibernate Idle Timer register value to microseconds */ +static int ufshcd_ahit_to_us(u32 ahit) +{ + int timer = FIELD_GET(UFSHCI_AHIBERN8_TIMER_MASK, ahit); + int scale = FIELD_GET(UFSHCI_AHIBERN8_SCALE_MASK, ahit); + + for (; scale > 0; --scale) + timer *= UFSHCI_AHIBERN8_SCALE_FACTOR; + + return timer; +} + +/* Convert microseconds to Auto-Hibernate Idle Timer register value */ +static u32 ufshcd_us_to_ahit(unsigned int timer) +{ + unsigned int scale; + + for (scale = 0; timer > UFSHCI_AHIBERN8_TIMER_MASK; ++scale) + timer /= UFSHCI_AHIBERN8_SCALE_FACTOR; + + return FIELD_PREP(UFSHCI_AHIBERN8_TIMER_MASK, timer) | + FIELD_PREP(UFSHCI_AHIBERN8_SCALE_MASK, scale); +} + +static ssize_t auto_hibern8_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ufs_hba *hba = dev_get_drvdata(dev); + + if (!(hba->capabilities & MASK_AUTO_HIBERN8_SUPPORT)) + return -EOPNOTSUPP; + + return snprintf(buf, PAGE_SIZE, "%d\n", ufshcd_ahit_to_us(hba->ahit)); +} + +static ssize_t auto_hibern8_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ufs_hba *hba = dev_get_drvdata(dev); + unsigned int timer; + + if (!(hba->capabilities & MASK_AUTO_HIBERN8_SUPPORT)) + return -EOPNOTSUPP; + + if (kstrtouint(buf, 0, &timer)) + return -EINVAL; + + if (timer > UFSHCI_AHIBERN8_MAX) + return -EINVAL; + + ufshcd_auto_hibern8_update(hba, ufshcd_us_to_ahit(timer)); + + return count; +} + static DEVICE_ATTR_RW(rpm_lvl); static DEVICE_ATTR_RO(rpm_target_dev_state); static DEVICE_ATTR_RO(rpm_target_link_state); static DEVICE_ATTR_RW(spm_lvl); static DEVICE_ATTR_RO(spm_target_dev_state); static DEVICE_ATTR_RO(spm_target_link_state); +static DEVICE_ATTR_RW(auto_hibern8); static struct attribute *ufs_sysfs_ufshcd_attrs[] = { &dev_attr_rpm_lvl.attr, @@ -131,6 +206,7 @@ static struct attribute *ufs_sysfs_ufshcd_attrs[] = { &dev_attr_spm_lvl.attr, &dev_attr_spm_target_dev_state.attr, &dev_attr_spm_target_link_state.attr, + &dev_attr_auto_hibern8.attr, NULL }; |