diff mbox

[RESEND,v3,2/2] scsi: ufs: Implement Auto-Hibern8 setup

Message ID 20170726145503.15339-3-michalx.potomski@intel.com (mailing list archive)
State Deferred, archived
Headers show

Commit Message

Potomski, MichalX July 26, 2017, 2:55 p.m. UTC
From: Michał Potomski <michalx.potomski@intel.com>

Since Auto-Hibern8 feature has to be enabled by the user
proper API has been given via SysFS.

We expose this API to user-space, since we don't know
in driver, what kind of additional Power Management rules
user wants to use. Due to that we want to expose API, to
let user or high level S/W decide, whether it wants to
use Auto-Hibern8 feature for Power Saving and give him
"slider" to decide between performance and power efficiency.
This is important because different platforms using
the same UFS host and/or device might want different
options on this one, e.g. High-End Desktop PC might
want to have this feature disabled, while Mobile or
Server platforms might want to have this feature enabled,
but levels will vary depending on what's to be acheived.

Setting up this feature should be fully transparent to
driver, since all states changing coming from setup
of this feature shall be handled by UFS Host, without any
interactions from driver. Only observable side effects
will be throughtput loss for transfers sporadic in terms
of set up timer.

Signed-off-by: Michał Potomski <michalx.potomski@intel.com>
---
 Documentation/ABI/testing/sysfs-driver-scsi-ufs |  8 +++
 drivers/scsi/ufs/ufshcd.c                       | 78 +++++++++++++++++++++++++
 drivers/scsi/ufs/ufshci.h                       | 23 ++++++--
 3 files changed, 105 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-driver-scsi-ufs
diff mbox

Patch

diff --git a/Documentation/ABI/testing/sysfs-driver-scsi-ufs b/Documentation/ABI/testing/sysfs-driver-scsi-ufs
new file mode 100644
index 000000000000..db500197f6a9
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-scsi-ufs
@@ -0,0 +1,8 @@ 
+What:		/sys/bus/pci/devices/<bus>:<vid>:<pid>.<n>/auto_hibern8
+Date:		July 2017
+Contact:	linux-scsi@vger.kernel.org
+Description:
+		This attribiute provides information on current setting of
+		Auto-Hibern8 UFS Host feature (read <unit: microseconds>)
+		and ability to set the feature on - by writing desired
+		timeout value <unit: microseconds>, or off (by writing '0').
diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index 5e0fee6ab0db..8c6ae132bda4 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -931,6 +931,31 @@  static int ufshcd_scale_clks(struct ufs_hba *hba, bool scale_up)
 }
 
 /**
+ * ufshcd_read_auto_hibern8_state - Reads hosts auto-hibern8 feature state
+ * @hba: per adapter instance
+ */
+u32 ufshcd_read_auto_hibern8_state(struct ufs_hba *hba)
+{
+	return ufshcd_readl(hba, REG_AUTO_HIBERN8_IDLE_TIMER);
+}
+
+/**
+ * ufshcd_setup_auto_hibern8 - Sets up hosts auto-hibern8 feature
+ * @hba: per adapter instance
+ * @scale: timer scale (1/10/100us/1/10/100ms)
+ * @timer_val: value to be multipled with scale (idle timeout)
+ */
+void ufshcd_setup_auto_hibern8(struct ufs_hba *hba, u8 scale, u16 timer_val)
+{
+	u32 val = (scale << UFSHCI_AHIBERN8_SCALE_OFFSET)
+			& UFSHCI_AHIBERN8_SCALE_MASK;
+
+	val |= timer_val & UFSHCI_AHIBERN8_TIMER_MASK;
+
+	ufshcd_writel(hba, val, REG_AUTO_HIBERN8_IDLE_TIMER);
+}
+
+/**
  * ufshcd_is_devfreq_scaling_required - check if scaling is required or not
  * @hba: per adapter instance
  * @scale_up: True if scaling up and false if scaling down
@@ -6583,12 +6608,65 @@  static ssize_t spm_lvl_store(struct device *dev,
 	return ufshcd_pm_lvl_store(dev, attr, buf, count, false);
 }
 
+#define UFS_AHIBERN8_SCALE_STEP_MAGNITUDE	10
+
+static ssize_t auto_hibern8_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct ufs_hba *hba = dev_get_drvdata(dev);
+	u32 val;
+	unsigned long timer;
+	u8 scale;
+
+	if (!(hba->capabilities & MASK_AUTO_HIBERN8_SUPPORT))
+		return -EOPNOTSUPP;
+
+	val = ufshcd_read_auto_hibern8_state(hba);
+	timer = val & UFSHCI_AHIBERN8_TIMER_MASK;
+	scale =	(val & UFSHCI_AHIBERN8_SCALE_MASK)
+			>> UFSHCI_AHIBERN8_SCALE_OFFSET;
+
+	for (; scale > 0; --scale)
+		timer *= UFS_AHIBERN8_SCALE_STEP_MAGNITUDE;
+
+	return snprintf(buf, PAGE_SIZE, "%ld\n", timer);
+}
+
+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 long timer;
+	u8 scale = UFSHCI_AHIBERN8_SCALE_1US;
+
+	if (!(hba->capabilities & MASK_AUTO_HIBERN8_SUPPORT))
+		return -EOPNOTSUPP;
+
+	if (kstrtoul(buf, 0, &timer))
+		return -EINVAL;
+
+	while (timer > UFSHCI_AHIBERN8_TIMER_MASK &&
+	       scale < UFSHCI_AHIBERN8_SCALE_MAX) {
+		timer /= UFS_AHIBERN8_SCALE_STEP_MAGNITUDE;
+		++scale;
+	}
+
+	if (scale >= UFSHCI_AHIBERN8_SCALE_MAX)
+		return -EINVAL;
+
+	ufshcd_setup_auto_hibern8(hba, scale, (u16) timer);
+
+	return count;
+}
+
 static DEVICE_ATTR_RW(rpm_lvl);
 static DEVICE_ATTR_RW(spm_lvl);
+static DEVICE_ATTR_RW(auto_hibern8);
 
 static struct device_attribute *ufshcd_dev_attrs[] = {
 	&dev_attr_rpm_lvl,
 	&dev_attr_spm_lvl,
+	&dev_attr_auto_hibern8,
 	NULL,
 };
 
diff --git a/drivers/scsi/ufs/ufshci.h b/drivers/scsi/ufs/ufshci.h
index f60145d4a66e..3bf8826318b0 100644
--- a/drivers/scsi/ufs/ufshci.h
+++ b/drivers/scsi/ufs/ufshci.h
@@ -48,7 +48,7 @@  enum {
 	REG_UFS_VERSION				= 0x08,
 	REG_CONTROLLER_DEV_ID			= 0x10,
 	REG_CONTROLLER_PROD_ID			= 0x14,
-	REG_AUTO_HIBERNATE_IDLE_TIMER		= 0x18,
+	REG_AUTO_HIBERN8_IDLE_TIMER		= 0x18,
 	REG_INTERRUPT_STATUS			= 0x20,
 	REG_INTERRUPT_ENABLE			= 0x24,
 	REG_CONTROLLER_STATUS			= 0x30,
@@ -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,
@@ -136,9 +137,9 @@  enum {
 #define CONTROLLER_FATAL_ERROR			UFS_BIT(16)
 #define SYSTEM_BUS_FATAL_ERROR			UFS_BIT(17)
 
-#define UFSHCD_UIC_PWR_MASK	(UIC_HIBERNATE_ENTER |\
-				UIC_HIBERNATE_EXIT |\
-				UIC_POWER_MODE)
+#define UFSHCD_UHS_MASK		(UIC_HIBERNATE_EXIT | UIC_HIBERNATE_ENTER)
+
+#define UFSHCD_UIC_PWR_MASK	(UFSHCD_UHS_MASK | UIC_POWER_MODE)
 
 #define UFSHCD_UIC_MASK		(UIC_COMMAND_COMPL | UFSHCD_UIC_PWR_MASK)
 
@@ -428,4 +429,18 @@  struct utp_task_req_desc {
 	__le32 task_rsp_upiu[TASK_RSP_UPIU_SIZE_DWORDS];
 };
 
+enum {
+	UFSHCI_AHIBERN8_SCALE_1US	= 0,
+	UFSHCI_AHIBERN8_SCALE_10US	= 1,
+	UFSHCI_AHIBERN8_SCALE_100US	= 2,
+	UFSHCI_AHIBERN8_SCALE_1MS	= 3,
+	UFSHCI_AHIBERN8_SCALE_10MS	= 4,
+	UFSHCI_AHIBERN8_SCALE_100MS	= 5,
+	UFSHCI_AHIBERN8_SCALE_MAX,
+};
+
+#define UFSHCI_AHIBERN8_TIMER_MASK		0x03ff
+#define UFSHCI_AHIBERN8_SCALE_MASK		0x1C00
+#define UFSHCI_AHIBERN8_SCALE_OFFSET		10
+
 #endif /* End of Header */