On 18/02/18 11:45, Avri Altman wrote: > > >> -----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? As the SysFS documentation says "Note that the hardware supports 10-bit values with a power-of-ten multiplier ... Refer to the UFS Host Controller Interface specification for more details.", so 150,000 us is still a value of 150 with a power-of-ten multiplier of 3. > > >> + 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 > >