@@ -4305,6 +4305,30 @@ int ufshcd_uic_hibern8_exit(struct ufs_hba *hba)
}
EXPORT_SYMBOL_GPL(ufshcd_uic_hibern8_exit);
+static int ufshcd_update_preserves_write_order(struct ufs_hba *hba,
+ bool preserves_write_order)
+{
+ struct scsi_device *sdev;
+
+ if (!preserves_write_order) {
+ shost_for_each_device(sdev, hba->host) {
+ struct request_queue *q = sdev->request_queue;
+
+ /*
+ * Refuse to enable auto-hibernation if no I/O scheduler
+ * is present. This code does not check whether the
+ * attached I/O scheduler serializes zoned writes
+ * (ELEVATOR_F_ZBD_SEQ_WRITE) because this cannot be
+ * checked from outside the block layer core.
+ */
+ if (blk_queue_is_zoned(q) && !q->elevator)
+ return -EPERM;
+ }
+ }
+
+ return 0;
+}
+
static void ufshcd_configure_auto_hibern8(struct ufs_hba *hba)
{
if (!ufshcd_is_auto_hibern8_supported(hba))
@@ -4313,13 +4337,42 @@ static void ufshcd_configure_auto_hibern8(struct ufs_hba *hba)
ufshcd_writel(hba, hba->ahit, REG_AUTO_HIBERNATE_IDLE_TIMER);
}
+/**
+ * ufshcd_auto_hibern8_update() - Modify the auto-hibernation control register
+ * @hba: per-adapter instance
+ * @ahit: New auto-hibernate settings. Includes the scale and the value of the
+ * auto-hibernation timer. See also the UFSHCI_AHIBERN8_TIMER_MASK and
+ * UFSHCI_AHIBERN8_SCALE_MASK constants.
+ *
+ * Notes:
+ * - UFSHCI controllers do not preserve the command order in legacy mode
+ * if auto-hibernation is enabled. If the command order is not preserved, an
+ * I/O scheduler that serializes zoned writes (mq-deadline) is required if a
+ * zoned logical unit is present. Enabling auto-hibernation without attaching
+ * the mq-deadline scheduler first may cause unaligned write errors for the
+ * zoned logical unit if a zoned logical unit is present.
+ * - Calls of this function must be serialized.
+ */
int ufshcd_auto_hibern8_update(struct ufs_hba *hba, u32 ahit)
{
const u32 cur_ahit = READ_ONCE(hba->ahit);
+ bool prev_state, new_state;
+ int ret;
if (!ufshcd_is_auto_hibern8_supported(hba) || cur_ahit == ahit)
return 0;
+ prev_state = FIELD_GET(UFSHCI_AHIBERN8_TIMER_MASK, cur_ahit);
+ new_state = FIELD_GET(UFSHCI_AHIBERN8_TIMER_MASK, ahit);
+
+ if (!is_mcq_enabled(hba) && !prev_state && new_state) {
+ /*
+ * Auto-hibernation will be enabled for legacy UFSHCI mode.
+ */
+ ret = ufshcd_update_preserves_write_order(hba, false);
+ if (ret)
+ return ret;
+ }
WRITE_ONCE(hba->ahit, ahit);
if (!pm_runtime_suspended(&hba->ufs_device_wlun->sdev_gendev)) {
ufshcd_rpm_get_sync(hba);
@@ -4328,6 +4381,13 @@ int ufshcd_auto_hibern8_update(struct ufs_hba *hba, u32 ahit)
ufshcd_release(hba);
ufshcd_rpm_put_sync(hba);
}
+ if (!is_mcq_enabled(hba) && prev_state && !new_state) {
+ /*
+ * Auto-hibernation has been disabled.
+ */
+ ret = ufshcd_update_preserves_write_order(hba, true);
+ WARN_ON_ONCE(ret);
+ }
return 0;
}