diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index ec28f6214e7b..35575c071760 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -173,6 +173,9 @@ void ufshcd_update_query_stats(struct ufs_hba *hba, } #endif +#define PWR_INFO_MASK 0xF +#define PWR_RX_OFFSET 4 + #define UFSHCD_REQ_SENSE_SIZE 18 #define UFSHCD_ENABLE_INTRS (UTP_TRANSFER_REQ_COMPL |\ @@ -4653,8 +4656,9 @@ int ufshcd_change_power_mode(struct ufs_hba *hba, int ret = 0; /* if already configured to the requested pwr_mode */ - if (pwr_mode->gear_rx == hba->pwr_info.gear_rx && - pwr_mode->gear_tx == hba->pwr_info.gear_tx && + if (!hba->restore_needed && + pwr_mode->gear_rx == hba->pwr_info.gear_rx && + pwr_mode->gear_tx == hba->pwr_info.gear_tx && pwr_mode->lane_rx == hba->pwr_info.lane_rx && pwr_mode->lane_tx == hba->pwr_info.lane_tx && pwr_mode->pwr_rx == hba->pwr_info.pwr_rx && @@ -6275,6 +6279,52 @@ static void ufshcd_update_uic_reg_hist(struct ufs_uic_err_reg_hist *reg_hist, reg_hist->pos = (reg_hist->pos + 1) % UIC_ERR_REG_HIST_LENGTH; } +static void ufshcd_rls_handler(struct work_struct *work) +{ + struct ufs_hba *hba; + int ret = 0; + u32 mode; + + hba = container_of(work, struct ufs_hba, rls_work); + ufshcd_scsi_block_requests(hba); + pm_runtime_get_sync(hba->dev); + ret = ufshcd_wait_for_doorbell_clr(hba, U64_MAX); + if (ret) { + dev_err(hba->dev, + "Timed out (%d) waiting for DB to clear\n", + ret); + goto out; + } + + ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PWRMODE), &mode); + if (hba->pwr_info.pwr_rx != ((mode >> PWR_RX_OFFSET) & PWR_INFO_MASK)) + hba->restore_needed = true; + + if (hba->pwr_info.pwr_tx != (mode & PWR_INFO_MASK)) + hba->restore_needed = true; + + ufshcd_dme_get(hba, UIC_ARG_MIB(PA_RXGEAR), &mode); + if (hba->pwr_info.gear_rx != mode) + hba->restore_needed = true; + + ufshcd_dme_get(hba, UIC_ARG_MIB(PA_TXGEAR), &mode); + if (hba->pwr_info.gear_tx != mode) + hba->restore_needed = true; + + if (hba->restore_needed) + ret = ufshcd_config_pwr_mode(hba, &(hba->pwr_info)); + + if (ret) + dev_err(hba->dev, "%s: Failed setting power mode, err = %d\n", + __func__, ret); + else + hba->restore_needed = false; + +out: + ufshcd_scsi_unblock_requests(hba); + pm_runtime_put_sync(hba->dev); +} + /** * ufshcd_update_uic_error - check and set fatal UIC error flags. * @hba: per-adapter instance @@ -6314,6 +6364,8 @@ static irqreturn_t ufshcd_update_uic_error(struct ufs_hba *hba) hba->full_init_linereset = true; } } + if (!hba->full_init_linereset) + schedule_work(&hba->rls_work); } retval |= IRQ_HANDLED; } @@ -9922,6 +9974,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) /* Initialize work queues */ INIT_WORK(&hba->eh_work, ufshcd_err_handler); INIT_WORK(&hba->eeh_work, ufshcd_exception_event_handler); + INIT_WORK(&hba->rls_work, ufshcd_rls_handler); /* Initialize UIC command mutex */ mutex_init(&hba->uic_cmd_mutex); diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h index da3ad78d3405..dbc80848ed8b 100644 --- a/drivers/scsi/ufs/ufshcd.h +++ b/drivers/scsi/ufs/ufshcd.h @@ -854,6 +854,7 @@ struct ufs_hba { /* Work Queues */ struct work_struct eh_work; struct work_struct eeh_work; + struct work_struct rls_work; /* HBA Errors */ u32 errors; @@ -950,9 +951,10 @@ struct ufs_hba { bool full_init_linereset; struct pinctrl *pctrl; - + int latency_hist_enabled; struct io_latency_state io_lat_s; + bool restore_needed; }; static inline void ufshcd_mark_shutdown_ongoing(struct ufs_hba *hba)