Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Adrian Hunter and committed by
Martin K. Petersen
ad448378 114c1aa2

+126
+14
Documentation/ABI/testing/sysfs-driver-ufs
··· 1 + What: /sys/bus/*/drivers/ufshcd/*/auto_hibern8 2 + Date: March 2018 3 + Contact: linux-scsi@vger.kernel.org 4 + Description: 5 + This file contains the auto-hibernate idle timer setting of a 6 + UFS host controller. A value of '0' means auto-hibernate is not 7 + enabled. Otherwise the value is the number of microseconds of 8 + idle time before the UFS host controller will autonomously put 9 + the link into hibernate state. That will save power at the 10 + expense of increased latency. Note that the hardware supports 11 + 10-bit values with a power-of-ten multiplier which allows a 12 + maximum value of 102300000. Refer to the UFS Host Controller 13 + Interface specification for more details. 14 + 1 15 What: /sys/bus/platform/drivers/ufshcd/*/device_descriptor/device_type 2 16 Date: February 2018 3 17 Contact: Stanislav Nijnikov <stanislav.nijnikov@wdc.com>
+76
drivers/scsi/ufs/ufs-sysfs.c
··· 3 3 4 4 #include <linux/err.h> 5 5 #include <linux/string.h> 6 + #include <linux/bitfield.h> 6 7 #include <asm/unaligned.h> 7 8 8 9 #include "ufs.h" ··· 118 117 ufs_pm_lvl_states[hba->spm_lvl].link_state)); 119 118 } 120 119 120 + static void ufshcd_auto_hibern8_update(struct ufs_hba *hba, u32 ahit) 121 + { 122 + unsigned long flags; 123 + 124 + if (!(hba->capabilities & MASK_AUTO_HIBERN8_SUPPORT)) 125 + return; 126 + 127 + spin_lock_irqsave(hba->host->host_lock, flags); 128 + if (hba->ahit == ahit) 129 + goto out_unlock; 130 + hba->ahit = ahit; 131 + if (!pm_runtime_suspended(hba->dev)) 132 + ufshcd_writel(hba, hba->ahit, REG_AUTO_HIBERNATE_IDLE_TIMER); 133 + out_unlock: 134 + spin_unlock_irqrestore(hba->host->host_lock, flags); 135 + } 136 + 137 + /* Convert Auto-Hibernate Idle Timer register value to microseconds */ 138 + static int ufshcd_ahit_to_us(u32 ahit) 139 + { 140 + int timer = FIELD_GET(UFSHCI_AHIBERN8_TIMER_MASK, ahit); 141 + int scale = FIELD_GET(UFSHCI_AHIBERN8_SCALE_MASK, ahit); 142 + 143 + for (; scale > 0; --scale) 144 + timer *= UFSHCI_AHIBERN8_SCALE_FACTOR; 145 + 146 + return timer; 147 + } 148 + 149 + /* Convert microseconds to Auto-Hibernate Idle Timer register value */ 150 + static u32 ufshcd_us_to_ahit(unsigned int timer) 151 + { 152 + unsigned int scale; 153 + 154 + for (scale = 0; timer > UFSHCI_AHIBERN8_TIMER_MASK; ++scale) 155 + timer /= UFSHCI_AHIBERN8_SCALE_FACTOR; 156 + 157 + return FIELD_PREP(UFSHCI_AHIBERN8_TIMER_MASK, timer) | 158 + FIELD_PREP(UFSHCI_AHIBERN8_SCALE_MASK, scale); 159 + } 160 + 161 + static ssize_t auto_hibern8_show(struct device *dev, 162 + struct device_attribute *attr, char *buf) 163 + { 164 + struct ufs_hba *hba = dev_get_drvdata(dev); 165 + 166 + if (!(hba->capabilities & MASK_AUTO_HIBERN8_SUPPORT)) 167 + return -EOPNOTSUPP; 168 + 169 + return snprintf(buf, PAGE_SIZE, "%d\n", ufshcd_ahit_to_us(hba->ahit)); 170 + } 171 + 172 + static ssize_t auto_hibern8_store(struct device *dev, 173 + struct device_attribute *attr, 174 + const char *buf, size_t count) 175 + { 176 + struct ufs_hba *hba = dev_get_drvdata(dev); 177 + unsigned int timer; 178 + 179 + if (!(hba->capabilities & MASK_AUTO_HIBERN8_SUPPORT)) 180 + return -EOPNOTSUPP; 181 + 182 + if (kstrtouint(buf, 0, &timer)) 183 + return -EINVAL; 184 + 185 + if (timer > UFSHCI_AHIBERN8_MAX) 186 + return -EINVAL; 187 + 188 + ufshcd_auto_hibern8_update(hba, ufshcd_us_to_ahit(timer)); 189 + 190 + return count; 191 + } 192 + 121 193 static DEVICE_ATTR_RW(rpm_lvl); 122 194 static DEVICE_ATTR_RO(rpm_target_dev_state); 123 195 static DEVICE_ATTR_RO(rpm_target_link_state); 124 196 static DEVICE_ATTR_RW(spm_lvl); 125 197 static DEVICE_ATTR_RO(spm_target_dev_state); 126 198 static DEVICE_ATTR_RO(spm_target_link_state); 199 + static DEVICE_ATTR_RW(auto_hibern8); 127 200 128 201 static struct attribute *ufs_sysfs_ufshcd_attrs[] = { 129 202 &dev_attr_rpm_lvl.attr, ··· 206 131 &dev_attr_spm_lvl.attr, 207 132 &dev_attr_spm_target_dev_state.attr, 208 133 &dev_attr_spm_target_link_state.attr, 134 + &dev_attr_auto_hibern8.attr, 209 135 NULL 210 136 }; 211 137
+26
drivers/scsi/ufs/ufshcd.c
··· 41 41 #include <linux/devfreq.h> 42 42 #include <linux/nls.h> 43 43 #include <linux/of.h> 44 + #include <linux/bitfield.h> 44 45 #include "ufshcd.h" 45 46 #include "ufs_quirks.h" 46 47 #include "unipro.h" ··· 3709 3708 return ret; 3710 3709 } 3711 3710 3711 + static void ufshcd_auto_hibern8_enable(struct ufs_hba *hba) 3712 + { 3713 + unsigned long flags; 3714 + 3715 + if (!(hba->capabilities & MASK_AUTO_HIBERN8_SUPPORT) || !hba->ahit) 3716 + return; 3717 + 3718 + spin_lock_irqsave(hba->host->host_lock, flags); 3719 + ufshcd_writel(hba, hba->ahit, REG_AUTO_HIBERNATE_IDLE_TIMER); 3720 + spin_unlock_irqrestore(hba->host->host_lock, flags); 3721 + } 3722 + 3712 3723 /** 3713 3724 * ufshcd_init_pwr_info - setting the POR (power on reset) 3714 3725 * values in hba power info ··· 6320 6307 /* UniPro link is active now */ 6321 6308 ufshcd_set_link_active(hba); 6322 6309 6310 + /* Enable Auto-Hibernate if configured */ 6311 + ufshcd_auto_hibern8_enable(hba); 6312 + 6323 6313 ret = ufshcd_verify_dev_init(hba); 6324 6314 if (ret) 6325 6315 goto out; ··· 7407 7391 7408 7392 /* Schedule clock gating in case of no access to UFS device yet */ 7409 7393 ufshcd_release(hba); 7394 + 7395 + /* Enable Auto-Hibernate if configured */ 7396 + ufshcd_auto_hibern8_enable(hba); 7397 + 7410 7398 goto out; 7411 7399 7412 7400 set_old_link_state: ··· 7853 7833 hba->spm_lvl = ufs_get_desired_pm_lvl_for_dev_link_state( 7854 7834 UFS_SLEEP_PWR_MODE, 7855 7835 UIC_LINK_HIBERN8_STATE); 7836 + 7837 + /* Set the default auto-hiberate idle timer value to 150 ms */ 7838 + if (hba->capabilities & MASK_AUTO_HIBERN8_SUPPORT) { 7839 + hba->ahit = FIELD_PREP(UFSHCI_AHIBERN8_TIMER_MASK, 150) | 7840 + FIELD_PREP(UFSHCI_AHIBERN8_SCALE_MASK, 3); 7841 + } 7856 7842 7857 7843 /* Hold auto suspend until async scan completes */ 7858 7844 pm_runtime_get_sync(dev);
+3
drivers/scsi/ufs/ufshcd.h
··· 531 531 struct device_attribute spm_lvl_attr; 532 532 int pm_op_in_progress; 533 533 534 + /* Auto-Hibernate Idle Timer register value */ 535 + u32 ahit; 536 + 534 537 struct ufshcd_lrb *lrb; 535 538 unsigned long lrb_in_use; 536 539
+7
drivers/scsi/ufs/ufshci.h
··· 86 86 enum { 87 87 MASK_TRANSFER_REQUESTS_SLOTS = 0x0000001F, 88 88 MASK_TASK_MANAGEMENT_REQUEST_SLOTS = 0x00070000, 89 + MASK_AUTO_HIBERN8_SUPPORT = 0x00800000, 89 90 MASK_64_ADDRESSING_SUPPORT = 0x01000000, 90 91 MASK_OUT_OF_ORDER_DATA_DELIVERY_SUPPORT = 0x02000000, 91 92 MASK_UIC_DME_TEST_MODE_SUPPORT = 0x04000000, ··· 119 118 */ 120 119 #define MANUFACTURE_ID_MASK UFS_MASK(0xFFFF, 0) 121 120 #define PRODUCT_ID_MASK UFS_MASK(0xFFFF, 16) 121 + 122 + /* AHIT - Auto-Hibernate Idle Timer */ 123 + #define UFSHCI_AHIBERN8_TIMER_MASK GENMASK(9, 0) 124 + #define UFSHCI_AHIBERN8_SCALE_MASK GENMASK(12, 10) 125 + #define UFSHCI_AHIBERN8_SCALE_FACTOR 10 126 + #define UFSHCI_AHIBERN8_MAX (1023 * 100000) 122 127 123 128 /* 124 129 * IS - Interrupt Status - 20h