mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Larisa Grigore <larisa.grigore@oss.nxp.com>
To: Larisa Grigore <larisa.grigore@oss.nxp.com>,
	NXP S32 Linux Team <s32@nxp.com>,
	Alim Akhtar <alim.akhtar@samsung.com>,
	Bart Van Assche <bvanassche@acm.org>,
	Bean Huo <beanhuo@micron.com>,
	Stanley Jhu <stanleyjhu@google.com>,
	Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>,
	"James E.J. Bottomley" <James.Bottomley@HansenPartnership.com>,
	"Martin K. Petersen" <mkp@kernel.org>,
	Sai Krishna Potthuri <sai.krishna.potthuri@amd.com>,
	Ajay Neeli <ajay.neeli@amd.com>,
	Manivannan Sadhasivam <mani@kernel.org>,
	Pedro Sousa <pedrom.sousa@synopsys.com>,
	Adrian Hunter <adrian.hunter@intel.com>,
	Can Guo <quic_cang@quicinc.com>,
	Asutosh Das <quic_asutoshd@quicinc.com>
Cc: imx@lists.linux.dev, linux-scsi@vger.kernel.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-arm-msm@vger.kernel.org, clizzi@redhat.com,
	aruizrui@redhat.com, eballetb@redhat.com, echanude@redhat.com
Subject: [PATCH v10 05/11] ufs: core: Add common Hibern8 TX FSM polling helper
Date: Tue,  3 Nov 2026 14:32:09 +0100	[thread overview]
Message-ID: <20261103133215.1385823-6-larisa.grigore@oss.nxp.com> (raw)
In-Reply-To: <20261103133215.1385823-1-larisa.grigore@oss.nxp.com>

Factor out the common logic used to poll TX_FSM_STATE until the TX lanes
enter Hibern8 into ufshcd_dme_check_tx_hibern8().

The HiSilicon and Qualcomm drivers currently implement similar checks
using the M-PHY TX_FSM_STATE attribute after PHY bring-up. Move this logic
to a common helper in ufshcd 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. 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.
A failed TX_FSM_STATE read that completes with a positive UIC result code
is reported as -EIO. This is an intentional, harmless change of the
returned error value; the pass/fail behaviour at the call sites is
unchanged.

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

Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
---
 drivers/ufs/core/ufshcd.c   | 97 +++++++++++++++++++++++++++++++++++++
 drivers/ufs/host/ufs-hisi.c | 48 +-----------------
 drivers/ufs/host/ufs-qcom.c | 42 +---------------
 include/ufs/ufshcd.h        |  2 +
 4 files changed, 101 insertions(+), 88 deletions(-)

diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
index d4addb7bdf7b..2779cbbfbc70 100644
--- a/drivers/ufs/core/ufshcd.c
+++ b/drivers/ufs/core/ufshcd.c
@@ -4494,6 +4494,103 @@ int ufshcd_dme_get_attr(struct ufs_hba *hba, u32 attr_sel,
 }
 EXPORT_SYMBOL_GPL(ufshcd_dme_get_attr);
 
+/**
+ * ufshcd_tx_lanes_in_hibern8 - Check TX_FSM_STATE of all TX lanes once
+ * @hba: host controller instance
+ * @num_lanes: number of TX lanes to check
+ *
+ * Read TX_FSM_STATE for every lane and verify it reached Hibern8.
+ *
+ * Return: 0 if all lanes are in Hibern8, -EAGAIN if at least one lane is
+ * not in Hibern8 yet, or a negative errno if reading TX_FSM_STATE failed.
+ */
+static int ufshcd_tx_lanes_in_hibern8(struct ufs_hba *hba,
+				      unsigned int num_lanes)
+{
+	u32 tx_fsm_val = 0;
+	unsigned int i;
+	int err;
+
+	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);
+			/* A positive value is a UIC result code, not an errno. */
+			return err > 0 ? -EIO : err;
+		}
+
+		if (tx_fsm_val != UFS_TX_STATE_HIBERN8)
+			return -EAGAIN;
+	}
+
+	return 0;
+}
+
+/**
+ * ufshcd_dme_check_tx_hibern8 - Check if all TX lanes entered Hibern8 state
+ * @hba: host controller instance
+ * @num_lanes: number of TX lanes to check
+ * @timeout_ms: overall timeout in milliseconds shared by all lanes
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int ufshcd_dme_check_tx_hibern8(struct ufs_hba *hba, unsigned int num_lanes,
+				unsigned int timeout_ms)
+{
+	unsigned int i;
+	int err, ret;
+	u32 state;
+
+	if (!num_lanes)
+		return -EINVAL;
+
+	/*
+	 * Poll every lane until ufshcd_tx_lanes_in_hibern8() stops returning
+	 * -EAGAIN, i.e. all lanes reached HIBERN8 (err == 0) or a real error
+	 * was hit. A read error has already been reported by the helper.
+	 */
+	ret = read_poll_timeout(ufshcd_tx_lanes_in_hibern8, err,
+				err != -EAGAIN, 200,
+				(u64)timeout_ms * USEC_PER_MSEC, false,
+				hba, num_lanes);
+	if (!ret)
+		return err;
+
+	/*
+	 * Timed out. The poll helper stops at the first lane that is not in
+	 * HIBERN8, so read every lane again to report each one that is stuck,
+	 * together with its TX_FSM_STATE.
+	 */
+	ret = 0;
+	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)),
+				     &state);
+		if (err) {
+			dev_err(hba->dev,
+				"%s: unable to get TX_FSM_STATE for lane %u, err %d\n",
+				__func__, i, err);
+			/* A positive value is a UIC result code, not an errno. */
+			return err > 0 ? -EIO : err;
+		}
+		if (state != UFS_TX_STATE_HIBERN8) {
+			dev_err(hba->dev,
+				"%s: TX lane %u stuck in TX_FSM_STATE %u\n",
+				__func__, i, state);
+			ret = -ETIMEDOUT;
+		}
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(ufshcd_dme_check_tx_hibern8);
+
 /**
  * ufshcd_dme_rmw - get modify set a DME attribute
  * @hba: per adapter instance
diff --git a/drivers/ufs/host/ufs-hisi.c b/drivers/ufs/host/ufs-hisi.c
index fb44a776e9e7..b84075dfe00d 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 == UFS_TX_STATE_HIBERN8 &&
-			tx_fsm_val_1 == UFS_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 != UFS_TX_STATE_HIBERN8 ||
-			 tx_fsm_val_1 != UFS_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,7 @@ 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);
-	if (err)
-		dev_err(hba->dev, "ufs_hisi_check_hibern8 error\n");
+	err = ufshcd_dme_check_tx_hibern8(hba, 2, HBRN8_POLL_TOUT_MS);
 
 	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 e5676b86de23..d53ead8e48ad 100644
--- a/drivers/ufs/host/ufs-qcom.c
+++ b/drivers/ufs/host/ufs-qcom.c
@@ -413,46 +413,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 == UFS_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 != UFS_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);
@@ -638,7 +598,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_dme_check_tx_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/include/ufs/ufshcd.h b/include/ufs/ufshcd.h
index be62165b785e..09ad11f7165f 100644
--- a/include/ufs/ufshcd.h
+++ b/include/ufs/ufshcd.h
@@ -1505,6 +1505,8 @@ int ufshcd_make_hba_operational(struct ufs_hba *hba);
 void ufshcd_remove(struct ufs_hba *);
 int ufshcd_uic_hibern8_enter(struct ufs_hba *hba);
 int ufshcd_uic_hibern8_exit(struct ufs_hba *hba);
+int ufshcd_dme_check_tx_hibern8(struct ufs_hba *hba, unsigned int num_lanes,
+				unsigned int timeout_ms);
 void ufshcd_delay_us(unsigned long us, unsigned long tolerance);
 void ufshcd_parse_dev_ref_clk_freq(struct ufs_hba *hba, struct clk *refclk);
 void ufshcd_update_evt_hist(struct ufs_hba *hba, u32 id, u32 val);
-- 
2.43.0


  parent reply	other threads:[~2026-11-03 13:32 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-03 13:32 [PATCH v10 00/11] Add S32N79RDB UFS support Larisa Grigore
2026-11-03 13:32 ` [PATCH v10 01/11] ufs: core: Return a negative errno from WLUN suspend/resume Larisa Grigore
2026-11-03 17:53   ` Bart Van Assche
2026-11-03 13:32 ` [PATCH v10 02/11] ufs: dwc: Return a negative error if the link is down Larisa Grigore
2026-11-03 13:32 ` [PATCH v10 03/11] ufs: unipro: Move Tx/Rx FSM state definitions to unipro.h Larisa Grigore
2026-11-03 17:54   ` Bart Van Assche
2026-11-03 13:32 ` [PATCH v10 04/11] ufs: unipro: Add TX/RX FSM state attributes Larisa Grigore
2026-11-03 17:54   ` Bart Van Assche
2026-11-03 13:32 ` Larisa Grigore [this message]
2026-11-03 13:32 ` [PATCH v10 06/11] ufs: dwc: Move Versal2 M-PHY CREG access helpers into ufshcd-dwc Larisa Grigore
2026-11-03 13:32 ` [PATCH v10 07/11] ufs: dwc: Export common clock divider and link status helpers Larisa Grigore
2026-11-03 13:32 ` [PATCH v10 08/11] dt-bindings: ufs: Add NXP S32N79 UFS host controller Larisa Grigore
2026-11-03 13:32 ` [PATCH v10 09/11] ufs: dwc: Sort CBCRCTRL and CBREFCLKCTRL2 by offset Larisa Grigore
2026-11-03 13:32 ` [PATCH v10 10/11] ufs: s32n7: Add NXP S32N79 UFS host controller driver Larisa Grigore
2026-11-03 17:57   ` Bart Van Assche
2026-11-03 13:32 ` [PATCH v10 11/11] MAINTAINERS: Add NXP S32N7 UFS host controller entry Larisa Grigore

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261103133215.1385823-6-larisa.grigore@oss.nxp.com \
    --to=larisa.grigore@oss.nxp.com \
    --cc=James.Bottomley@HansenPartnership.com \
    --cc=adrian.hunter@intel.com \
    --cc=ajay.neeli@amd.com \
    --cc=alim.akhtar@samsung.com \
    --cc=aruizrui@redhat.com \
    --cc=beanhuo@micron.com \
    --cc=bvanassche@acm.org \
    --cc=clizzi@redhat.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=eballetb@redhat.com \
    --cc=echanude@redhat.com \
    --cc=imx@lists.linux.dev \
    --cc=krzk+dt@kernel.org \
    --cc=linux-arm-msm@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-scsi@vger.kernel.org \
    --cc=mani@kernel.org \
    --cc=mkp@kernel.org \
    --cc=pedrom.sousa@synopsys.com \
    --cc=quic_asutoshd@quicinc.com \
    --cc=quic_cang@quicinc.com \
    --cc=robh@kernel.org \
    --cc=s32@nxp.com \
    --cc=sai.krishna.potthuri@amd.com \
    --cc=stanleyjhu@google.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®