From bee947b93ddd31550e9a4eb17cf2f0180396a611 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Wed, 22 Mar 2017 17:00:54 -0700 Subject: scsi: ufs: prevent auto-hibern8 trigger during clock frequency scaling If auto hibern8 is supported then we should manually put the link in hibern8 before clock frequency scaling sequence to avoid auto hibern8 racing during clock frequency scaling sequence. Othewise, we may see UFS command or hibern8 issues after the scaling sequence completion. Change-Id: I6a126029be6fb03ba4b387fbdfa89e068882a54f Signed-off-by: Subhash Jadavani --- drivers/scsi/ufs/ufshcd.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index c891fc8d34a3..2c46fd61fa47 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -9292,10 +9292,29 @@ static int ufshcd_devfreq_scale(struct ufs_hba *hba, bool scale_up) goto clk_scaling_unprepare; } + /* + * If auto hibern8 is supported then put the link in + * hibern8 manually, this is to avoid auto hibern8 + * racing during clock frequency scaling sequence. + */ + if (ufshcd_is_auto_hibern8_supported(hba)) { + ret = ufshcd_uic_hibern8_enter(hba); + if (ret) + /* link will be bad state so no need to scale_up_gear */ + return ret; + } + ret = ufshcd_scale_clks(hba, scale_up); if (ret) goto scale_up_gear; + if (ufshcd_is_auto_hibern8_supported(hba)) { + ret = ufshcd_uic_hibern8_exit(hba); + if (ret) + /* link will be bad state so no need to scale_up_gear */ + return ret; + } + /* scale up the gear after scaling up clocks */ if (scale_up) { ret = ufshcd_scale_gear(hba, true); -- cgit v1.2.3 From d0960c5f3000b0a6a79ec4307ba24dff23ff8b1f Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Thu, 23 Mar 2017 12:55:25 -0700 Subject: scsi: ufs: scale up the gear in 2 steps Some UFS devices may stop responding after switching from HS-G1 to HS-G3. Also, it is found that these devices work fine if we do 2 steps switch: HS-G1 to HS-G2 followed by HS-G2 to HS-G3. This change introduce UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH quirk for these devices to apply this 2 steps gear switch workaround. Change-Id: I38eec2e1bfa842169cbfec441d900a807e715f8c Signed-off-by: Subhash Jadavani --- drivers/scsi/ufs/ufs_quirks.c | 16 +++++++++++++++- drivers/scsi/ufs/ufs_quirks.h | 10 +++++++++- drivers/scsi/ufs/ufshcd.c | 33 +++++++++++++++++++++++++++++---- 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/drivers/scsi/ufs/ufs_quirks.c b/drivers/scsi/ufs/ufs_quirks.c index 176c3888aa34..7a501d6d7c84 100644 --- a/drivers/scsi/ufs/ufs_quirks.c +++ b/drivers/scsi/ufs/ufs_quirks.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * Copyright (c) 2013-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -30,6 +30,20 @@ static struct ufs_card_fix ufs_fixups[] = { UFS_DEVICE_QUIRK_HOST_PA_TACTIVATE), UFS_FIX(UFS_VENDOR_HYNIX, UFS_ANY_MODEL, UFS_DEVICE_QUIRK_HOST_PA_SAVECONFIGTIME), + UFS_FIX(UFS_VENDOR_HYNIX, "hB8aL1", + UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH), + UFS_FIX(UFS_VENDOR_HYNIX, "hC8aL1", + UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH), + UFS_FIX(UFS_VENDOR_HYNIX, "hD8aL1", + UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH), + UFS_FIX(UFS_VENDOR_HYNIX, "hC8aM1", + UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH), + UFS_FIX(UFS_VENDOR_HYNIX, "h08aM1", + UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH), + UFS_FIX(UFS_VENDOR_HYNIX, "hC8GL1", + UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH), + UFS_FIX(UFS_VENDOR_HYNIX, "hC8HL1", + UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH), END_FIX }; diff --git a/drivers/scsi/ufs/ufs_quirks.h b/drivers/scsi/ufs/ufs_quirks.h index b8ab5948f12d..3102517e841c 100644 --- a/drivers/scsi/ufs/ufs_quirks.h +++ b/drivers/scsi/ufs/ufs_quirks.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. +/* Copyright (c) 2014-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -139,6 +139,14 @@ struct ufs_card_fix { */ #define UFS_DEVICE_QUIRK_HOST_PA_SAVECONFIGTIME (1 << 7) +/* + * Some UFS devices may stop responding after switching from HS-G1 to HS-G3. + * Also, it is found that these devices work fine if we do 2 steps switch: + * HS-G1 to HS-G2 followed by HS-G2 to HS-G3. Enabling this quirk for such + * device would apply this 2 steps gear switch workaround. + */ +#define UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH (1 << 8) + struct ufs_hba; void ufs_advertise_fixup_device(struct ufs_hba *hba); #endif /* UFS_QUIRKS_H_ */ diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index 2c46fd61fa47..a02c8a2c756c 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -509,7 +509,7 @@ out: /* replace non-printable or non-ASCII characters with spaces */ static inline void ufshcd_remove_non_printable(char *val) { - if (!val) + if (!val || !*val) return; if (*val < 0x20 || *val > 0x7e) @@ -3651,7 +3651,7 @@ int ufshcd_read_string_desc(struct ufs_hba *hba, int desc_index, u8 *buf, goto out; } - buff_ascii = kmalloc(ascii_len, GFP_KERNEL); + buff_ascii = kzalloc(ascii_len, GFP_KERNEL); if (!buff_ascii) { dev_err(hba->dev, "%s: Failed allocating %d bytes\n", __func__, ascii_len); @@ -9207,6 +9207,31 @@ static int ufshcd_scale_gear(struct ufs_hba *hba, bool scale_up) if (scale_up) { memcpy(&new_pwr_info, &hba->clk_scaling.saved_pwr_info.info, sizeof(struct ufs_pa_layer_attr)); + /* + * Some UFS devices may stop responding after switching from + * HS-G1 to HS-G3. Also, it is found that these devices work + * fine if we do 2 steps switch: HS-G1 to HS-G2 followed by + * HS-G2 to HS-G3. If UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH + * quirk is enabled for such devices, this 2 steps gear switch + * workaround will be applied. + */ + if ((hba->dev_quirks & UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH) + && (hba->pwr_info.gear_tx == UFS_HS_G1) + && (new_pwr_info.gear_tx == UFS_HS_G3)) { + /* scale up to G2 first */ + new_pwr_info.gear_tx = UFS_HS_G2; + new_pwr_info.gear_rx = UFS_HS_G2; + ret = ufshcd_change_power_mode(hba, &new_pwr_info); + if (ret) + goto out; + + /* scale up to G3 now */ + new_pwr_info.gear_tx = UFS_HS_G3; + new_pwr_info.gear_rx = UFS_HS_G3; + ret = ufshcd_change_power_mode(hba, &new_pwr_info); + if (ret) + goto out; + } } else { memcpy(&new_pwr_info, &hba->pwr_info, sizeof(struct ufs_pa_layer_attr)); @@ -9226,10 +9251,10 @@ static int ufshcd_scale_gear(struct ufs_hba *hba, bool scale_up) new_pwr_info.pwr_rx = FASTAUTO_MODE; } } + ret = ufshcd_change_power_mode(hba, &new_pwr_info); } - ret = ufshcd_change_power_mode(hba, &new_pwr_info); - +out: if (ret) dev_err(hba->dev, "%s: failed err %d, old gear: (tx %d rx %d), new gear: (tx %d rx %d), scale_up = %d", __func__, ret, -- cgit v1.2.3