aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/ABI/testing/sysfs-driver-ufs14
-rw-r--r--drivers/scsi/ufs/ufs-sysfs.c76
-rw-r--r--drivers/scsi/ufs/ufshcd.c26
-rw-r--r--drivers/scsi/ufs/ufshcd.h3
-rw-r--r--drivers/scsi/ufs/ufshci.h7
5 files changed, 126 insertions, 0 deletions
diff --git a/Documentation/ABI/testing/sysfs-driver-ufs b/Documentation/ABI/testing/sysfs-driver-ufs
index 83735f79e572..016724ec26d5 100644
--- a/Documentation/ABI/testing/sysfs-driver-ufs
+++ b/Documentation/ABI/testing/sysfs-driver-ufs
@@ -1,3 +1,17 @@
+What: /sys/bus/*/drivers/ufshcd/*/auto_hibern8
+Date: March 2018
+Contact: linux-scsi@vger.kernel.org
+Description:
+ This file contains the auto-hibernate idle timer setting of a
+ UFS host controller. A value of '0' means auto-hibernate is not
+ enabled. Otherwise the value is 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. Note that the hardware supports
+ 10-bit values with a power-of-ten multiplier which allows a
+ maximum value of 102300000. Refer to the UFS Host Controller
+ Interface specification for more details.
+
What: /sys/bus/platform/drivers/ufshcd/*/device_descriptor/device_type
Date: February 2018
Contact: Stanislav Nijnikov <stanislav.nijnikov@wdc.com>
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
};
diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index 3abcd31646eb..facee2b97926 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -41,6 +41,7 @@
#include <linux/devfreq.h>
#include <linux/nls.h>
#include <linux/of.h>
+#include <linux/bitfield.h>
#include "ufshcd.h"
#include "ufs_quirks.h"
#include "unipro.h"
@@ -3708,6 +3709,18 @@ static int ufshcd_uic_hibern8_exit(struct ufs_hba *hba)
return ret;
}
+static void ufshcd_auto_hibern8_enable(struct ufs_hba *hba)
+{
+ unsigned long flags;
+
+ if (!(hba->capabilities & MASK_AUTO_HIBERN8_SUPPORT) || !hba->ahit)
+ return;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ ufshcd_writel(hba, hba->ahit, REG_AUTO_HIBERNATE_IDLE_TIMER);
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+}
+
/**
* ufshcd_init_pwr_info - setting the POR (power on reset)
* values in hba power info
@@ -6307,6 +6320,9 @@ static int ufshcd_probe_hba(struct ufs_hba *hba)
/* UniPro link is active now */
ufshcd_set_link_active(hba);
+ /* Enable Auto-Hibernate if configured */
+ ufshcd_auto_hibern8_enable(hba);
+
ret = ufshcd_verify_dev_init(hba);
if (ret)
goto out;
@@ -7391,6 +7407,10 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
/* Schedule clock gating in case of no access to UFS device yet */
ufshcd_release(hba);
+
+ /* Enable Auto-Hibernate if configured */
+ ufshcd_auto_hibern8_enable(hba);
+
goto out;
set_old_link_state:
@@ -7834,6 +7854,12 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq)
UFS_SLEEP_PWR_MODE,
UIC_LINK_HIBERN8_STATE);
+ /* Set the default auto-hiberate idle timer value to 150 ms */
+ if (hba->capabilities & MASK_AUTO_HIBERN8_SUPPORT) {
+ hba->ahit = FIELD_PREP(UFSHCI_AHIBERN8_TIMER_MASK, 150) |
+ FIELD_PREP(UFSHCI_AHIBERN8_SCALE_MASK, 3);
+ }
+
/* 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 deb3c5d382e9..8110dcd04d22 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/drivers/scsi/ufs/ufshcd.h
@@ -531,6 +531,9 @@ struct ufs_hba {
struct device_attribute spm_lvl_attr;
int pm_op_in_progress;
+ /* Auto-Hibernate Idle Timer register value */
+ u32 ahit;
+
struct ufshcd_lrb *lrb;
unsigned long lrb_in_use;
diff --git a/drivers/scsi/ufs/ufshci.h b/drivers/scsi/ufs/ufshci.h
index 1a1b5d9fe514..bb5d9c7f3353 100644
--- a/drivers/scsi/ufs/ufshci.h
+++ b/drivers/scsi/ufs/ufshci.h
@@ -86,6 +86,7 @@ enum {
enum {
MASK_TRANSFER_REQUESTS_SLOTS = 0x0000001F,
MASK_TASK_MANAGEMENT_REQUEST_SLOTS = 0x00070000,
+ MASK_AUTO_HIBERN8_SUPPORT = 0x00800000,
MASK_64_ADDRESSING_SUPPORT = 0x01000000,
MASK_OUT_OF_ORDER_DATA_DELIVERY_SUPPORT = 0x02000000,
MASK_UIC_DME_TEST_MODE_SUPPORT = 0x04000000,
@@ -119,6 +120,12 @@ enum {
#define MANUFACTURE_ID_MASK UFS_MASK(0xFFFF, 0)
#define PRODUCT_ID_MASK UFS_MASK(0xFFFF, 16)
+/* AHIT - Auto-Hibernate Idle Timer */
+#define UFSHCI_AHIBERN8_TIMER_MASK GENMASK(9, 0)
+#define UFSHCI_AHIBERN8_SCALE_MASK GENMASK(12, 10)
+#define UFSHCI_AHIBERN8_SCALE_FACTOR 10
+#define UFSHCI_AHIBERN8_MAX (1023 * 100000)
+
/*
* IS - Interrupt Status - 20h
*/