diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index 6863cc1013f8..607ed45742c4 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -1570,6 +1570,16 @@ static void ufshcd_exit_clk_gating(struct ufs_hba *hba) cancel_delayed_work_sync(&hba->clk_gating.gate_work); } +static void ufshcd_set_auto_hibern8_timer(struct ufs_hba *hba, u32 delay) +{ + ufshcd_rmwl(hba, AUTO_HIBERN8_TIMER_SCALE_MASK | + AUTO_HIBERN8_IDLE_TIMER_MASK, + AUTO_HIBERN8_TIMER_SCALE_1_MS | delay, + REG_AUTO_HIBERN8_IDLE_TIMER); + /* Make sure the timer gets applied before further operations */ + mb(); +} + /** * ufshcd_hibern8_hold - Make sure that link is not in hibern8. * @@ -1799,6 +1809,13 @@ static ssize_t ufshcd_hibern8_on_idle_delay_store(struct device *dev, spin_lock_irqsave(hba->host->host_lock, flags); hba->hibern8_on_idle.delay_ms = value; spin_unlock_irqrestore(hba->host->host_lock, flags); + + /* Update auto hibern8 timer value if supported */ + if (ufshcd_is_auto_hibern8_supported(hba) && + hba->hibern8_on_idle.is_enabled) + ufshcd_set_auto_hibern8_timer(hba, + hba->hibern8_on_idle.delay_ms); + return count; } @@ -1825,6 +1842,13 @@ static ssize_t ufshcd_hibern8_on_idle_enable_store(struct device *dev, if (value == hba->hibern8_on_idle.is_enabled) goto out; + /* Update auto hibern8 timer value if supported */ + if (ufshcd_is_auto_hibern8_supported(hba)) { + ufshcd_set_auto_hibern8_timer(hba, + value ? hba->hibern8_on_idle.delay_ms : value); + goto update; + } + if (value) { /* * As clock gating work would wait for the hibern8 enter work @@ -1838,6 +1862,7 @@ static ssize_t ufshcd_hibern8_on_idle_enable_store(struct device *dev, spin_unlock_irqrestore(hba->host->host_lock, flags); } +update: hba->hibern8_on_idle.is_enabled = value; out: return count; @@ -1848,12 +1873,23 @@ static void ufshcd_init_hibern8_on_idle(struct ufs_hba *hba) /* initialize the state variable here */ hba->hibern8_on_idle.state = HIBERN8_EXITED; - if (!ufshcd_is_hibern8_on_idle_allowed(hba)) + if (!ufshcd_is_hibern8_on_idle_allowed(hba) && + !ufshcd_is_auto_hibern8_supported(hba)) return; - INIT_DELAYED_WORK(&hba->hibern8_on_idle.enter_work, - ufshcd_hibern8_enter_work); - INIT_WORK(&hba->hibern8_on_idle.exit_work, ufshcd_hibern8_exit_work); + if (ufshcd_is_auto_hibern8_supported(hba)) { + hba->hibern8_on_idle.state = AUTO_HIBERN8; + /* + * Disable SW hibern8 enter on idle in case + * auto hibern8 is supported + */ + hba->caps &= ~UFSHCD_CAP_HIBERN8_ENTER_ON_IDLE; + } else { + INIT_DELAYED_WORK(&hba->hibern8_on_idle.enter_work, + ufshcd_hibern8_enter_work); + INIT_WORK(&hba->hibern8_on_idle.exit_work, + ufshcd_hibern8_exit_work); + } hba->hibern8_on_idle.delay_ms = 10; hba->hibern8_on_idle.is_enabled = true; @@ -1881,7 +1917,8 @@ static void ufshcd_init_hibern8_on_idle(struct ufs_hba *hba) static void ufshcd_exit_hibern8_on_idle(struct ufs_hba *hba) { - if (!ufshcd_is_hibern8_on_idle_allowed(hba)) + if (!ufshcd_is_hibern8_on_idle_allowed(hba) && + !ufshcd_is_auto_hibern8_supported(hba)) return; device_remove_file(hba->dev, &hba->hibern8_on_idle.delay_attr); device_remove_file(hba->dev, &hba->hibern8_on_idle.enable_attr); @@ -6702,6 +6739,11 @@ static int ufshcd_probe_hba(struct ufs_hba *hba) if (ret) goto out; + /* Enable auto hibern8 if supported */ + if (ufshcd_is_auto_hibern8_supported(hba)) + ufshcd_set_auto_hibern8_timer(hba, + hba->hibern8_on_idle.delay_ms); + /* Debug counters initialization */ ufshcd_clear_dbg_ufs_stats(hba); /* set the default level for urgent bkops */ diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h index 899debcab2c3..be8928bedb0b 100644 --- a/drivers/scsi/ufs/ufshcd.h +++ b/drivers/scsi/ufs/ufshcd.h @@ -74,6 +74,7 @@ #define UFSHCD_DRIVER_VERSION "0.3" #define UFS_BIT(x) BIT(x) +#define UFS_MASK(x, y) (x << ((y) % BITS_PER_LONG)) struct ufs_hba; @@ -429,6 +430,7 @@ enum ufshcd_hibern8_on_idle_state { HIBERN8_EXITED, REQ_HIBERN8_ENTER, REQ_HIBERN8_EXIT, + AUTO_HIBERN8, }; /** @@ -929,6 +931,11 @@ static inline bool ufshcd_is_intr_aggr_allowed(struct ufs_hba *hba) return false; } +static inline bool ufshcd_is_auto_hibern8_supported(struct ufs_hba *hba) +{ + return !!(hba->capabilities & MASK_AUTO_HIBERN8_SUPPORT); +} + static inline bool ufshcd_is_crypto_supported(struct ufs_hba *hba) { return !!(hba->capabilities & MASK_CRYPTO_SUPPORT); diff --git a/drivers/scsi/ufs/ufshci.h b/drivers/scsi/ufs/ufshci.h index f38405b830ec..d65dad03bdd2 100644 --- a/drivers/scsi/ufs/ufshci.h +++ b/drivers/scsi/ufs/ufshci.h @@ -48,6 +48,7 @@ enum { REG_UFS_VERSION = 0x08, REG_CONTROLLER_DEV_ID = 0x10, REG_CONTROLLER_PROD_ID = 0x14, + REG_AUTO_HIBERN8_IDLE_TIMER = 0x18, REG_INTERRUPT_STATUS = 0x20, REG_INTERRUPT_ENABLE = 0x24, REG_CONTROLLER_STATUS = 0x30, @@ -85,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, @@ -117,6 +119,18 @@ enum { #define MANUFACTURE_ID_MASK UFS_MASK(0xFFFF, 0) #define PRODUCT_ID_MASK UFS_MASK(0xFFFF, 16) +/* + * AHIT - Auto-Hibernate Idle Timer 18h + */ +#define AUTO_HIBERN8_IDLE_TIMER_MASK UFS_MASK(0x3FF, 0) +#define AUTO_HIBERN8_TIMER_SCALE_MASK UFS_MASK(0x7, 10) +#define AUTO_HIBERN8_TIMER_SCALE_1_US UFS_MASK(0x0, 10) +#define AUTO_HIBERN8_TIMER_SCALE_10_US UFS_MASK(0x1, 10) +#define AUTO_HIBERN8_TIMER_SCALE_100_US UFS_MASK(0x2, 10) +#define AUTO_HIBERN8_TIMER_SCALE_1_MS UFS_MASK(0x3, 10) +#define AUTO_HIBERN8_TIMER_SCALE_10_MS UFS_MASK(0x4, 10) +#define AUTO_HIBERN8_TIMER_SCALE_100_MS UFS_MASK(0x5, 10) + /* IS - Interrupt status (20h) / IE - Interrupt enable (24h) */ #define UTP_TRANSFER_REQ_COMPL UFS_BIT(0) #define UIC_DME_END_PT_RESET UFS_BIT(1)