Merge tag 'scsi-misc' of git://git.kernel.org/pub/scm/linux/kernel/git/jejb/scsi
[linux-2.6-block.git] / drivers / ufs / core / ufshcd.c
index 629442c6bc9cc7ce5730551364231f69c996c6b4..172d25fef740d354a73f156acdbc37a1febb9b00 100644 (file)
@@ -1286,12 +1286,14 @@ static int ufshcd_clock_scaling_prepare(struct ufs_hba *hba, u64 timeout_us)
         * clock scaling is in progress
         */
        ufshcd_scsi_block_requests(hba);
+       mutex_lock(&hba->wb_mutex);
        down_write(&hba->clk_scaling_lock);
 
        if (!hba->clk_scaling.is_allowed ||
            ufshcd_wait_for_doorbell_clr(hba, timeout_us)) {
                ret = -EBUSY;
                up_write(&hba->clk_scaling_lock);
+               mutex_unlock(&hba->wb_mutex);
                ufshcd_scsi_unblock_requests(hba);
                goto out;
        }
@@ -1303,12 +1305,16 @@ out:
        return ret;
 }
 
-static void ufshcd_clock_scaling_unprepare(struct ufs_hba *hba, bool writelock)
+static void ufshcd_clock_scaling_unprepare(struct ufs_hba *hba, int err, bool scale_up)
 {
-       if (writelock)
-               up_write(&hba->clk_scaling_lock);
-       else
-               up_read(&hba->clk_scaling_lock);
+       up_write(&hba->clk_scaling_lock);
+
+       /* Enable Write Booster if we have scaled up else disable it */
+       if (ufshcd_enable_wb_if_scaling_up(hba) && !err)
+               ufshcd_wb_toggle(hba, scale_up);
+
+       mutex_unlock(&hba->wb_mutex);
+
        ufshcd_scsi_unblock_requests(hba);
        ufshcd_release(hba);
 }
@@ -1325,7 +1331,6 @@ static void ufshcd_clock_scaling_unprepare(struct ufs_hba *hba, bool writelock)
 static int ufshcd_devfreq_scale(struct ufs_hba *hba, bool scale_up)
 {
        int ret = 0;
-       bool is_writelock = true;
 
        ret = ufshcd_clock_scaling_prepare(hba, 1 * USEC_PER_SEC);
        if (ret)
@@ -1354,15 +1359,8 @@ static int ufshcd_devfreq_scale(struct ufs_hba *hba, bool scale_up)
                }
        }
 
-       /* Enable Write Booster if we have scaled up else disable it */
-       if (ufshcd_enable_wb_if_scaling_up(hba)) {
-               downgrade_write(&hba->clk_scaling_lock);
-               is_writelock = false;
-               ufshcd_wb_toggle(hba, scale_up);
-       }
-
 out_unprepare:
-       ufshcd_clock_scaling_unprepare(hba, is_writelock);
+       ufshcd_clock_scaling_unprepare(hba, ret, scale_up);
        return ret;
 }
 
@@ -6172,11 +6170,21 @@ void ufshcd_schedule_eh_work(struct ufs_hba *hba)
        }
 }
 
+static void ufshcd_force_error_recovery(struct ufs_hba *hba)
+{
+       spin_lock_irq(hba->host->host_lock);
+       hba->force_reset = true;
+       ufshcd_schedule_eh_work(hba);
+       spin_unlock_irq(hba->host->host_lock);
+}
+
 static void ufshcd_clk_scaling_allow(struct ufs_hba *hba, bool allow)
 {
+       mutex_lock(&hba->wb_mutex);
        down_write(&hba->clk_scaling_lock);
        hba->clk_scaling.is_allowed = allow;
        up_write(&hba->clk_scaling_lock);
+       mutex_unlock(&hba->wb_mutex);
 }
 
 static void ufshcd_clk_scaling_suspend(struct ufs_hba *hba, bool suspend)
@@ -9449,6 +9457,15 @@ static int __ufshcd_wl_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
 
                if (!hba->dev_info.b_rpm_dev_flush_capable) {
                        ret = ufshcd_set_dev_pwr_mode(hba, req_dev_pwr_mode);
+                       if (ret && pm_op != UFS_SHUTDOWN_PM) {
+                               /*
+                                * If return err in suspend flow, IO will hang.
+                                * Trigger error handler and break suspend for
+                                * error recovery.
+                                */
+                               ufshcd_force_error_recovery(hba);
+                               ret = -EBUSY;
+                       }
                        if (ret)
                                goto enable_scaling;
                }
@@ -9460,6 +9477,15 @@ static int __ufshcd_wl_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
         */
        check_for_bkops = !ufshcd_is_ufs_dev_deepsleep(hba);
        ret = ufshcd_link_state_transition(hba, req_link_state, check_for_bkops);
+       if (ret && pm_op != UFS_SHUTDOWN_PM) {
+               /*
+                * If return err in suspend flow, IO will hang.
+                * Trigger error handler and break suspend for
+                * error recovery.
+                */
+               ufshcd_force_error_recovery(hba);
+               ret = -EBUSY;
+       }
        if (ret)
                goto set_dev_active;
 
@@ -10185,6 +10211,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq)
        /* Initialize mutex for exception event control */
        mutex_init(&hba->ee_ctrl_mutex);
 
+       mutex_init(&hba->wb_mutex);
        init_rwsem(&hba->clk_scaling_lock);
 
        ufshcd_init_clk_gating(hba);