> -----Original Message----- > From: linux-scsi-owner@xxxxxxxxxxxxxxx [mailto:linux-scsi- > owner@xxxxxxxxxxxxxxx] On Behalf Of Adrian Hunter > Sent: Friday, February 16, 2018 2:01 PM > To: Vinayak Holikatti <vinholikatti@xxxxxxxxx>; Martin K. Petersen > <martin.petersen@xxxxxxxxxx>; James E.J. Bottomley > <jejb@xxxxxxxxxxxxxxxxxx> > Cc: Stanislav Nijnikov <Stanislav.Nijnikov@xxxxxxx>; Jaegeuk Kim > <jaegeuk@xxxxxxxxxx>; Bart Van Assche <Bart.VanAssche@xxxxxxx>; linux- > scsi@xxxxxxxxxxxxxxx; linux-kernel@xxxxxxxxxxxxxxx; Michal Potomski > <michalx.potomski@xxxxxxxxx>; Szymon Mielczarek > <szymonx.mielczarek@xxxxxxxxx> > Subject: [PATCH 1/1] 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@xxxxxxxxx> > --- > Documentation/ABI/testing/sysfs-driver-ufs | 15 ++++++ > drivers/scsi/ufs/ufs-sysfs.c | 77 ++++++++++++++++++++++++++++++ > drivers/scsi/ufs/ufshcd.c | 26 ++++++++++ > drivers/scsi/ufs/ufshcd.h | 3 ++ > drivers/scsi/ufs/ufshci.h | 7 +++ > 5 files changed, 128 insertions(+) > > diff --git a/Documentation/ABI/testing/sysfs-driver-ufs > b/Documentation/ABI/testing/sysfs-driver-ufs > index 07f1c2f8dbfc..c7f9441079eb 100644 > --- a/Documentation/ABI/testing/sysfs-driver-ufs > +++ b/Documentation/ABI/testing/sysfs-driver-ufs > @@ -1,3 +1,18 @@ > +What: /sys/bus/*/drivers/ufshcd/*/auto_hibern8 > +Date: February 2018 > +Contact: linux-scsi@xxxxxxxxxxxxxxx > +Description: > + This file contains the auto-hibernate idle timer setting of a > + UFS host controller. A value of '-1' means auto-hibernate is > not > + supported. 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@xxxxxxx> > diff --git a/drivers/scsi/ufs/ufs-sysfs.c b/drivers/scsi/ufs/ufs-sysfs.c index > cd7174d2d225..a0e38776dc92 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" > @@ -123,12 +124,88 @@ static ssize_t spm_lvl_store(struct device *dev, > return ufs_sysfs_pm_lvl_store(dev, attr, buf, count, false); } > > +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); > + int timer = -1; > + > + if (hba->capabilities & MASK_AUTO_HIBERN8_SUPPORT) > + timer = ufshcd_ahit_to_us(hba->ahit); > + > + return snprintf(buf, PAGE_SIZE, "%d\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 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_RW(spm_lvl); > +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_auto_hibern8.attr, > NULL > }; > > diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index > 5d874e303d4e..d5fc2fa65495 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" > @@ -3709,6 +3710,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 > @@ -6315,6 +6328,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; > @@ -7399,6 +7415,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: > @@ -7843,6 +7863,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 */ Your commit said you are setting an idle timer in microseconds, Better use usec to avoid confusion? > + 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 > a57d9bdebfed..763a8e6c98ee 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 > */ > -- > 1.9.1