Re: [PATCH 03/10] ufs: host: Add common Hibern8 TX FSM polling helper

From: Larisa Ileana Grigore

Date: Thu Aug 27 2026 - 10:15:40 EST


On 8/27/2026 12:14 PM, Bough Chen wrote:
On Wed, Aug 26, 2026 at 03:40:41PM +0200, Larisa Grigore wrote:
Factor out the common logic used to poll TX_FSM_STATE until the TX lanes
enter Hibern8 into ufshcd_check_hibern8().

The HiSilicon and Qualcomm drivers currently implement similar checks
using the UniPro TX_FSM_STATE attribute after link/PHY bring-up. Move
this logic to a common helper in ufshcd-pltfrm so it can be shared
across host controller drivers.

Switch the HiSilicon and Qualcomm drivers to use the new helper and drop
their local implementations.

The code change the error return value for qcom and hisi, better to point this
out like the following, so that QCOM/HISI guys may pay attention on it:

"While at it, normalize the "TX lane failed to reach Hibern8" error path to
return -ETIMEDOUT instead of the raw TX_FSM_STATE value (qcom) or -1 (hisi),
so all callers get a proper errno. This is an intentional, harmless change of
the returned error value; the pass/fail behaviour at the call sites is
unchanged."

Regards
Haibo Chen

Thanks for taking the time to review this patch series! I’ll address it in the next revision.


This also prepares for reusing the same UniPro-specific Hibern8 check in
a subsequent commit.

Signed-off-by: Larisa Grigore <larisa.grigore@xxxxxxxxxxx>
---
drivers/ufs/host/ufs-hisi.c | 48 +------------------
drivers/ufs/host/ufs-qcom.c | 42 +----------------
drivers/ufs/host/ufshcd-pltfrm.c | 79 ++++++++++++++++++++++++++++++++
drivers/ufs/host/ufshcd-pltfrm.h | 2 +
4 files changed, 84 insertions(+), 87 deletions(-)

diff --git a/drivers/ufs/host/ufs-hisi.c b/drivers/ufs/host/ufs-hisi.c
index bd223bda1ce2..76d77bc30322 100644
--- a/drivers/ufs/host/ufs-hisi.c
+++ b/drivers/ufs/host/ufs-hisi.c
@@ -22,50 +22,6 @@
#include <ufs/ufshci.h>
#include <ufs/ufs_quirks.h>
-static int ufs_hisi_check_hibern8(struct ufs_hba *hba)
-{
- int err = 0;
- u32 tx_fsm_val_0 = 0;
- u32 tx_fsm_val_1 = 0;
- unsigned long timeout = jiffies + msecs_to_jiffies(HBRN8_POLL_TOUT_MS);
-
- do {
- err = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(TX_FSM_STATE, 0),
- &tx_fsm_val_0);
- err |= ufshcd_dme_get(hba,
- UIC_ARG_MIB_SEL(TX_FSM_STATE, 1), &tx_fsm_val_1);
- if (err || (tx_fsm_val_0 == TX_STATE_HIBERN8 &&
- tx_fsm_val_1 == TX_STATE_HIBERN8))
- break;
-
- /* sleep for max. 200us */
- usleep_range(100, 200);
- } while (time_before(jiffies, timeout));
-
- /*
- * we might have scheduled out for long during polling so
- * check the state again.
- */
- if (time_after(jiffies, timeout)) {
- err = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(TX_FSM_STATE, 0),
- &tx_fsm_val_0);
- err |= ufshcd_dme_get(hba,
- UIC_ARG_MIB_SEL(TX_FSM_STATE, 1), &tx_fsm_val_1);
- }
-
- if (err) {
- dev_err(hba->dev, "%s: unable to get TX_FSM_STATE, err %d\n",
- __func__, err);
- } else if (tx_fsm_val_0 != TX_STATE_HIBERN8 ||
- tx_fsm_val_1 != TX_STATE_HIBERN8) {
- err = -1;
- dev_err(hba->dev, "%s: invalid TX_FSM_STATE, lane0 = %d, lane1 = %d\n",
- __func__, tx_fsm_val_0, tx_fsm_val_1);
- }
-
- return err;
-}
-
static void ufs_hisi_clk_init(struct ufs_hba *hba)
{
struct ufs_hisi_host *host = ufshcd_get_variant(hba);
@@ -224,9 +180,9 @@ static int ufs_hisi_link_startup_pre_change(struct ufs_hba *hba)
/* Unipro VS_mphy_disable */
ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xD0C1, 0x0), 0x0);
- err = ufs_hisi_check_hibern8(hba);
+ err = ufshcd_check_hibern8(hba, 2, HBRN8_POLL_TOUT_MS);
if (err)
- dev_err(hba->dev, "ufs_hisi_check_hibern8 error\n");
+ dev_err(hba->dev, "hibern8 TX FSM check failed\n");
if (!(host->caps & UFS_HISI_CAP_PHY10nm))
ufshcd_writel(hba, UFS_HCLKDIV_NORMAL_VALUE, UFS_REG_HCLKDIV);
diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
index b2081ed6197a..c94488661bcf 100644
--- a/drivers/ufs/host/ufs-qcom.c
+++ b/drivers/ufs/host/ufs-qcom.c
@@ -382,46 +382,6 @@ static int ufs_qcom_init_lane_clks(struct ufs_qcom_host *host)
return 0;
}
-static int ufs_qcom_check_hibern8(struct ufs_hba *hba)
-{
- int err;
- u32 tx_fsm_val;
- unsigned long timeout = jiffies + msecs_to_jiffies(HBRN8_POLL_TOUT_MS);
-
- do {
- err = ufshcd_dme_get(hba,
- UIC_ARG_MIB_SEL(TX_FSM_STATE,
- UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)),
- &tx_fsm_val);
- if (err || tx_fsm_val == TX_STATE_HIBERN8)
- break;
-
- /* sleep for max. 200us */
- usleep_range(100, 200);
- } while (time_before(jiffies, timeout));
-
- /*
- * we might have scheduled out for long during polling so
- * check the state again.
- */
- if (time_after(jiffies, timeout))
- err = ufshcd_dme_get(hba,
- UIC_ARG_MIB_SEL(TX_FSM_STATE,
- UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)),
- &tx_fsm_val);
-
- if (err) {
- dev_err(hba->dev, "%s: unable to get TX_FSM_STATE, err %d\n",
- __func__, err);
- } else if (tx_fsm_val != TX_STATE_HIBERN8) {
- err = tx_fsm_val;
- dev_err(hba->dev, "%s: invalid TX_FSM_STATE = %d\n",
- __func__, err);
- }
-
- return err;
-}
-
static void ufs_qcom_select_unipro_mode(struct ufs_qcom_host *host)
{
ufshcd_rmwl(host->hba, QUNIPRO_SEL, QUNIPRO_SEL, REG_UFS_CFG1);
@@ -607,7 +567,7 @@ static int ufs_qcom_hce_enable_notify(struct ufs_hba *hba,
break;
case POST_CHANGE:
/* check if UFS PHY moved from DISABLED to HIBERN8 */
- err = ufs_qcom_check_hibern8(hba);
+ err = ufshcd_check_hibern8(hba, 1, HBRN8_POLL_TOUT_MS);
ufs_qcom_enable_hw_clk_gating(hba);
ufs_qcom_ice_enable(host);
ufs_qcom_config_ice_allocator(host);
diff --git a/drivers/ufs/host/ufshcd-pltfrm.c b/drivers/ufs/host/ufshcd-pltfrm.c
index c2dafb583cf5..034ee988924a 100644
--- a/drivers/ufs/host/ufshcd-pltfrm.c
+++ b/drivers/ufs/host/ufshcd-pltfrm.c
@@ -9,6 +9,7 @@
*/
#include <linux/clk.h>
+#include <linux/delay.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
@@ -563,6 +564,84 @@ void ufshcd_pltfrm_remove(struct platform_device *pdev)
}
EXPORT_SYMBOL_GPL(ufshcd_pltfrm_remove);
+/**
+ * ufshcd_check_hibern8 - Check if all TX lanes entered Hibern8 state
+ * @hba: host controller instance
+ * @num_lanes: number of TX lanes to check
+ * @timeout_ms: timeout in milliseconds for all lanes
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int ufshcd_check_hibern8(struct ufs_hba *hba, unsigned int num_lanes,
+ unsigned int timeout_ms)
+{
+ unsigned long timeout;
+ u32 tx_fsm_val = 0;
+ unsigned int i;
+ bool success;
+ int err;
+
+ if (!num_lanes)
+ return -EINVAL;
+
+ timeout = jiffies + msecs_to_jiffies(timeout_ms);
+
+ do {
+ success = true;
+
+ for (i = 0; i < num_lanes; i++) {
+ err = ufshcd_dme_get(hba,
+ UIC_ARG_MIB_SEL(TX_FSM_STATE,
+ UIC_ARG_MPHY_TX_GEN_SEL_INDEX(i)),
+ &tx_fsm_val);
+ if (err) {
+ dev_err(hba->dev,
+ "%s: unable to get TX_FSM_STATE for lane %u, err %d\n",
+ __func__, i, err);
+ return err;
+ }
+
+ if (tx_fsm_val != TX_STATE_HIBERN8) {
+ success = false;
+ break;
+ }
+ }
+
+ if (success)
+ return 0;
+
+ /* sleep for max. 200us */
+ usleep_range(100, 200);
+ } while (time_before(jiffies, timeout));
+
+ /*
+ * We might have been scheduled out for long during polling, so do
+ * one final check before reporting timeout.
+ */
+ for (i = 0; i < num_lanes; i++) {
+ err = ufshcd_dme_get(hba,
+ UIC_ARG_MIB_SEL(TX_FSM_STATE,
+ UIC_ARG_MPHY_TX_GEN_SEL_INDEX(i)),
+ &tx_fsm_val);
+ if (err) {
+ dev_err(hba->dev,
+ "%s: unable to get TX_FSM_STATE for lane %u, err %d\n",
+ __func__, i, err);
+ return err;
+ }
+
+ if (tx_fsm_val != TX_STATE_HIBERN8) {
+ dev_err(hba->dev,
+ "%s: timeout waiting for lane %u to enter HIBERN8, TX_FSM_STATE=%u\n",
+ __func__, i, tx_fsm_val);
+ return -ETIMEDOUT;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ufshcd_check_hibern8);
+
MODULE_AUTHOR("Santosh Yaragnavi <santosh.sy@xxxxxxxxxxx>");
MODULE_AUTHOR("Vinayak Holikatti <h.vinayak@xxxxxxxxxxx>");
MODULE_DESCRIPTION("UFS host controller Platform bus based glue driver");
diff --git a/drivers/ufs/host/ufshcd-pltfrm.h b/drivers/ufs/host/ufshcd-pltfrm.h
index 0a18a8aed94d..ec2a8013e975 100644
--- a/drivers/ufs/host/ufshcd-pltfrm.h
+++ b/drivers/ufs/host/ufshcd-pltfrm.h
@@ -35,5 +35,7 @@ int ufshcd_pltfrm_init(struct platform_device *pdev,
void ufshcd_pltfrm_remove(struct platform_device *pdev);
int ufshcd_populate_vreg(struct device *dev, const char *name,
struct ufs_vreg **out_vreg, bool skip_current);
+int ufshcd_check_hibern8(struct ufs_hba *hba, unsigned int num_lanes,
+ unsigned int timeout_ms);
#endif /* UFSHCD_PLTFRM_H_ */
--
2.43.0