+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>
 
 
 #include <linux/err.h>
 #include <linux/string.h>
+#include <linux/bitfield.h>
 #include <asm/unaligned.h>
 
 #include "ufs.h"
                                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,
        &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
 };
 
 
 #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"
        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
        /* 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;
 
        /* 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:
                                                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);
 
 
        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;
 
 
 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,
 #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
  */