summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/scsi/ufs/ufshcd.c52
-rw-r--r--drivers/scsi/ufs/ufshcd.h7
-rw-r--r--drivers/scsi/ufs/ufshci.h14
3 files changed, 68 insertions, 5 deletions
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)