* [PATCH v10 00/11] Add S32N79RDB UFS support
@ 2026-11-03 13:32 Larisa Grigore
2026-11-03 13:32 ` [PATCH v10 01/11] ufs: core: Return a negative errno from WLUN suspend/resume Larisa Grigore
` (10 more replies)
0 siblings, 11 replies; 12+ messages in thread
From: Larisa Grigore @ 2026-11-03 13:32 UTC (permalink / raw)
To: Larisa Grigore, NXP S32 Linux Team, Alim Akhtar, Bart Van Assche,
Bean Huo, Stanley Jhu, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, James E.J. Bottomley, Martin K. Petersen,
Sai Krishna Potthuri, Ajay Neeli, Manivannan Sadhasivam,
Pedro Sousa, Adrian Hunter, Can Guo, Asutosh Das
Cc: imx, linux-scsi, devicetree, linux-kernel, linux-arm-msm, clizzi,
aruizrui, eballetb, echanude
This series adds support for the UFS host controller found on the NXP S32N79
platform. The controller is based on the Synopsys DesignWare (DWC) UFS host
architecture and requires S32N79-specific M-PHY initialization/calibration
sequence. It is compliant with JESD-223D "Universal Flash Storage Host
Controller Interface" v3.0, and supports up to HS-Gear 4 speed grade. It is
compatible with UFS 2.1, 3.0 and 3.1 devices. The MIPI UniPro is compliant with
"MIPI Alliance Specification for Unified Protocol", v1.8. The MIPI M-PHY is
compliant to version 4.1.
In order to keep the S32N79 platform driver small and avoid duplicating common
DWC code across platform glue drivers, the series also refactors and exports a
few reusable DWC helpers. In particular, M-PHY CREG (CBCREG*) register access
helpers are moved from the existing AMD Versal2 glue driver into the common
ufshcd-dwc layer, and are then used by the platform drivers. The series also
introduces a generic Hibern8 TX-FSM polling helper so that multiple platforms
can reuse the same pre-link checks.
Device tree binding documentation for the S32N79 UFS controller is included.
The SoC and board DTS updates that instantiate and enable the controller are
no longer part of this series and will be posted separately.
Changes in v10:
All patches:
- Dropped the "scsi:" subject prefix.
ufs: core: Return a negative errno from WLUN suspend/resume:
- New patch. __ufshcd_wl_suspend() and __ufshcd_wl_resume() may return a
positive result code, e.g. from ufshcd_link_startup(). The runtime PM core
treats it as a failure, but rpm_get_suppliers() only checks for a negative
value, so the LUs consuming the WLUN are reported as resumed while the host
is in the error state. Convert positive values to -EIO at the WLUN PM
callback boundary, as ufshcd_init() already does. This replaces the
Fixes: rationale previously carried by the dwc link_is_up() patch and
covers every source of positive values, not only the DWC glue.
ufs: dwc: Return a negative error if the link is down:
- Dropped the Fixes: tag. The PM-boundary issue is now fixed in the core;
this patch is a cleanup so the helper returns an errno before it is
exported.
ufs: unipro: Add TX/RX FSM state attributes:
- Fixed the commit message: AMD Versal2 has no header with these
definitions, only users in the .c file.
ufs: core: Add common Hibern8 TX FSM polling helper:
- Changed the subject prefix from "ufs: host:" to "ufs: core:".
- Fixed the helper name in the commit message.
- Renamed the internal per-lane poller from ufshcd_poll_tx_hibern8_lanes()
to ufshcd_tx_lanes_in_hibern8().
- Log the TX_FSM_STATE of each lane that did not reach Hibern8 on timeout.
- Moved the prototype to the non-extern declarations and dropped "extern".
- Report a positive UIC result from DME_GET as -EIO, so the helper really
returns a negative errno as documented.
- Fixed checkpatch --strict alignment checks.
- Dropped the Reviewed-by tags of Frank Li and Stanley Jhu, as the patch
changed significantly.
ufs: dwc: Move Versal2 M-PHY CREG access helpers into ufshcd-dwc:
- Reworded the commit message and explained the static array race.
- Use the "name() - description" kernel-doc style like the rest of the
file.
dt-bindings: ufs: Add NXP S32N79 UFS host controller:
- Restricted lanes-per-direction to 2, matching the driver.
- Explained in the commit message why clock-names and the single 0 Hz
operating-points-v2 entry are required.
ufs: s32n7: Add NXP S32N79 UFS host controller driver:
- Changed the subject prefix to "ufs: s32n7:".
- Added <linux/bitfield.h> and <linux/bits.h>.
- Reject lanes-per-direction other than 2.
- Dropped redundant error messages and the remove() wrapper.
- Used u32 for the ufshcd_dme_get() result, aligned register offset style,
clarified the SCM_STATUS comments.
- Documented the PERFORM_LINK_STARTUP_ONCE quirk and spm_lvl in the commit
message, and updated the HS rate-select bullet.
- Documented in the commit message that the DWC BUSTHRTL power gating is
disabled because it is not supported on this platform.
Changes in v9:
ufs: unipro: Move Tx/Rx FSM state definitions to unipro.h:
- Prefixed the RX_STATE_* and TX_STATE_* enum constants with UFS_ to avoid
polluting the global namespace, and updated the existing users
(AMD Versal2, HiSilicon, Qualcomm).
ufs: host: Add common Hibern8 TX FSM polling helper:
- Use the renamed UFS_TX_STATE_HIBERN8 constant.
dt-bindings: ufs: Add NXP S32N79 UFS host controller:
- Added Reviewed-by: Frank Li.
MAINTAINERS: Add NXP S32N7 UFS host controller entry:
- Added the imx@lists.linux.dev mailing list.
arm64: dts: freescale: s32n79: Add UFS host controller:
arm64: dts: freescale: s32n79-rdb: Enable UFS:
- Dropped from this series, as requested; they will be sent separately.
Changes in v8:
ufs: host: Add common Hibern8 TX FSM polling helper:
- Added Reviewed-by: Stanley Jhu.
scsi: ufs: Add NXP S32N79 UFS host controller driver:
- Let the UFS core manage the core reference clock: declare it in DT via
clock-names/operating-points-v2 so the core adds it to hba->clk_list_head
and gates it across system suspend and runtime idle. Dropped the manual
devm_clk_get_enabled() and the struct s32n7_ufs core_clk member, and now
read the clock rate from the core clock list.
- Dropped the ufs_s32n7_calculate_us_tick() wrapper and inlined the division.
- Dropped the unused struct s32n7_ufs hba member.
dt-bindings: ufs: Add NXP S32N79 UFS host controller:
- Added the required clock-names and operating-points-v2 properties.
- Dropped the Reviewed-by and Acked-by tags.
arm64: dts: freescale: s32n79: Add UFS host controller:
- Added clock-names = "core" and an operating-points-v2 table with
opp-hz = <0> (fixed-rate clock) so the UFS core manages (and gates) the
core reference clock.
Changes in v7:
scsi: ufs: Add NXP S32N79 UFS host controller driver:
- Wrapped .prepare/.complete in pm_sleep_ptr() instead of pm_ptr().
- Fixed code alignment issues.
Changes in v6:
scsi: ufs: dwc: Return a negative error if the link is down:
- Translate a positive UIC command result from ufshcd_dme_get() into -EIO.
- Declare dme_result as u32 to match the ufshcd_dme_get() parameter type.
scsi: ufs: dwc: Sort CBCRCTRL and CBREFCLKCTRL2 by offset:
- Added Reviewed-by: Frank Li.
scsi: ufs: Add NXP S32N79 UFS host controller driver:
- Added Reviewed-by: Frank Li.
Changes in v5:
scsi: ufs: dwc: Sort CBCRCTRL and CBREFCLKCTRL2 by offset:
- New patch. Split the macro reorder into its own cleanup.
ufs: host: Add common Hibern8 TX FSM polling helper:
- Return the real poller error on timeout, not a forced -ETIMEDOUT.
dt-bindings: ufs: Add NXP S32N79 UFS host controller:
- Dropped the jedec,ufs-2.0 fallback.
- Removed the select: block and the clocks description.
scsi: ufs: Add NXP S32N79 UFS host controller driver:
- Documented the timeout values as driver-defined bounds.
- Wrapped .prepare/.complete in pm_ptr().
arm64: dts: freescale: s32n79: Add UFS host controller:
- Dropped the jedec,ufs-2.0 fallback.
Changes in v4:
scsi: ufs: dwc: Return a negative error if the link is down:
- New patch. ufshcd_dwc_link_is_up() now returns a negative errno on failure
instead of a positive value.
ufs: unipro: Move Tx/Rx FSM state definitions to unipro.h
- Added Reviewed-by: Frank Li.
ufs: host: Add common Hibern8 TX FSM polling helper:
- Refactored ufshcd_dme_check_tx_hibern8() to use read_poll_timeout().
scsi: ufs: Move Versal2 M-PHY CREG access helpers into ufshcd-dwc:
- Added Reviewed-by: Frank Li.
dt-bindings: ufs: Add NXP S32N79 UFS host controller:
- Added Reviewed-by: Frank Li.
Changes in v3:
ufs: host: Add common Hibern8 TX FSM polling helper:
- Use -EAGAIN while polling and only normalize to -ETIMEDOUT on timeout, so
real attribute-read errors are returned instead of masked.
- Drop the stray delay.h include in ufshcd-pltfrm.c; the helper lives in
ufshcd.c.
- Drop the now-redundant dev_err() after the check in the HiSilicon driver.
scsi: ufs: Add NXP S32N79 UFS host controller driver:
- Use devm_clk_get_enabled() to enable the core reference clock.
- Rename the family-level identifiers from the s32n_ prefix to s32n7_
(structs and functions), keeping the s32n79_ prefix only for the
chip-specific data.
Changes in v2:
ufs: unipro: Move Tx/Rx FSM state definitions to unipro.h:
- Improved the enum comments. They now cite the MIPI M-PHY v6.0 tables.
ufs: unipro: Add TX/RX FSM state attributes:
- Added Reviewed-by: Frank Li.
ufs: host: Add common Hibern8 TX FSM polling helper:
- Moved the helper into the UFS core (ufshcd.c).
- Split out an inner per-lane poller, ufshcd_poll_tx_hibern8_lanes().
- Expanded the commit message to document the -ETIMEDOUT normalization done
when converting the qcom/hisi checks to the helper. No code behaviour change.
scsi: ufs: Move Versal2 M-PHY CREG access helpers into ufshcd-dwc:
- Made the DME attribute arrays const. They are now initialized inline instead
of by later field assignments.
scsi: ufs: dwc: Export common clock divider and link status helpers:
- Added Reviewed-by: Frank Li.
dt-bindings: ufs: Add NXP S32N79 UFS host controller:
- Renamed the patch subject and simplified the commit message.
- Renamed the binding file to nxp,s32n79-ufshc.yaml so it matches the
nxp,s32n79-ufshc compatible string.
- Removed the nxp,mphy-boot-mode property.
- Made reg-names and clocks required.
- Removed the conditional if/then/else schema.
- Removed clock-names.
- Changed the compatible from jedec,ufs-3.0 to jedec,ufs-2.0.
- Updated the example clocks to <&clks 0x92>.
scsi: ufs: Add NXP S32N79 UFS host controller driver:
- Removed the nxp,mphy-boot-mode handling and the mphy_boot_mode enum. The
M-PHY firmware always runs from ROM now.
- Added a struct s32n_soc_data for SoC match data.
- Moved the calibration writes out to static const tables.
- Renamed struct phy_reg_cfg to s32n_phy_reg.
- Moved the rate-select sequence out of link_startup_post_change() into a new
pwr_change_notify() pre-change path.
- Used devm_clk_get(dev, NULL) and dropped the redundant core_clk NULL check.
- Switched to *_PM_OPS() macros and pm_ptr() for the PM ops.
- Clarified the Hibern8 poll timeout comment.
- Enabled Auto-Hibern8 instead of setting UFSHCD_QUIRK_BROKEN_AUTO_HIBERN8.
- Reworked and improved the M-PHY initialization sequence.
arm64: dts: freescale: s32n79: Add UFS host controller:
- Changed the compatible from jedec,ufs-3.0 to jedec,ufs-2.0.
- Removed clock-names.
arm64: dts: freescale: s32n79-rdb: Enable UFS:
- Removed the nxp,mphy-boot-mode property.
Larisa Grigore (11):
ufs: core: Return a negative errno from WLUN suspend/resume
ufs: dwc: Return a negative error if the link is down
ufs: unipro: Move Tx/Rx FSM state definitions to unipro.h
ufs: unipro: Add TX/RX FSM state attributes
ufs: core: Add common Hibern8 TX FSM polling helper
ufs: dwc: Move Versal2 M-PHY CREG access helpers into ufshcd-dwc
ufs: dwc: Export common clock divider and link status helpers
dt-bindings: ufs: Add NXP S32N79 UFS host controller
ufs: dwc: Sort CBCRCTRL and CBREFCLKCTRL2 by offset
ufs: s32n7: Add NXP S32N79 UFS host controller driver
MAINTAINERS: Add NXP S32N7 UFS host controller entry
.../bindings/ufs/nxp,s32n79-ufshc.yaml | 68 +++
MAINTAINERS | 9 +
drivers/ufs/core/ufshcd.c | 101 +++-
drivers/ufs/host/Kconfig | 12 +
drivers/ufs/host/Makefile | 1 +
drivers/ufs/host/ufs-amd-versal2.c | 97 +---
drivers/ufs/host/ufs-hisi.c | 48 +-
drivers/ufs/host/ufs-hisi.h | 6 -
drivers/ufs/host/ufs-nxp-s32n7.c | 537 ++++++++++++++++++
drivers/ufs/host/ufs-qcom.c | 42 +-
drivers/ufs/host/ufs-qcom.h | 2 -
drivers/ufs/host/ufshcd-dwc.c | 86 ++-
drivers/ufs/host/ufshcd-dwc.h | 39 +-
drivers/ufs/host/ufshci-dwc.h | 6 +
include/ufs/ufshcd.h | 2 +
include/ufs/unipro.h | 22 +
16 files changed, 876 insertions(+), 202 deletions(-)
create mode 100644 Documentation/devicetree/bindings/ufs/nxp,s32n79-ufshc.yaml
create mode 100644 drivers/ufs/host/ufs-nxp-s32n7.c
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v10 01/11] ufs: core: Return a negative errno from WLUN suspend/resume
2026-11-03 13:32 [PATCH v10 00/11] Add S32N79RDB UFS support Larisa Grigore
@ 2026-11-03 13:32 ` Larisa Grigore
2026-11-03 13:32 ` [PATCH v10 02/11] ufs: dwc: Return a negative error if the link is down Larisa Grigore
` (9 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Larisa Grigore @ 2026-11-03 13:32 UTC (permalink / raw)
To: Larisa Grigore, NXP S32 Linux Team, Alim Akhtar, Bart Van Assche,
Bean Huo, Stanley Jhu, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, James E.J. Bottomley, Martin K. Petersen,
Sai Krishna Potthuri, Ajay Neeli, Manivannan Sadhasivam,
Pedro Sousa, Adrian Hunter, Can Guo, Asutosh Das
Cc: imx, linux-scsi, devicetree, linux-kernel, linux-arm-msm, clizzi,
aruizrui, eballetb, echanude
__ufshcd_wl_suspend() and __ufshcd_wl_resume() may return a positive result
code, e.g. from ufshcd_link_startup() when DME link startup fails while
restoring the link.
The runtime PM core treats any non-zero value as a failure of the WLUN,
but rpm_get_suppliers() only checks for a negative value. The LUs
consuming the WLUN are thus reported as resumed and receive I/O while
the WLUN is still suspended.
Convert positive values to -EIO, as ufshcd_init() already does.
Do the same in __ufshcd_wl_suspend() for consistency, so that callers
always get a negative errno.
Fixes: b294ff3e3449 ("scsi: ufs: core: Enable power management for wlun")
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
---
drivers/ufs/core/ufshcd.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
index ee643830a57a..d4addb7bdf7b 100644
--- a/drivers/ufs/core/ufshcd.c
+++ b/drivers/ufs/core/ufshcd.c
@@ -10545,7 +10545,7 @@ static int __ufshcd_wl_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
ufshcd_release(hba);
}
hba->pm_op_in_progress = false;
- return ret;
+ return ret > 0 ? -EIO : ret;
}
#ifdef CONFIG_PM
@@ -10646,7 +10646,7 @@ static int __ufshcd_wl_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
hba->clk_gating.is_suspended = false;
ufshcd_release(hba);
hba->pm_op_in_progress = false;
- return ret;
+ return ret > 0 ? -EIO : ret;
}
static int ufshcd_wl_runtime_suspend(struct device *dev)
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v10 02/11] ufs: dwc: Return a negative error if the link is down
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 13:32 ` Larisa Grigore
2026-11-03 13:32 ` [PATCH v10 03/11] ufs: unipro: Move Tx/Rx FSM state definitions to unipro.h Larisa Grigore
` (8 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Larisa Grigore @ 2026-11-03 13:32 UTC (permalink / raw)
To: Larisa Grigore, NXP S32 Linux Team, Alim Akhtar, Bart Van Assche,
Bean Huo, Stanley Jhu, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, James E.J. Bottomley, Martin K. Petersen,
Sai Krishna Potthuri, Ajay Neeli, Manivannan Sadhasivam,
Pedro Sousa, Adrian Hunter, Can Guo, Asutosh Das
Cc: imx, linux-scsi, devicetree, linux-kernel, linux-arm-msm, clizzi,
aruizrui, eballetb, echanude, Frank Li
ufshcd_dwc_link_is_up() returns 1 when the UniPro link is not up, and it
ignores the return value of ufshcd_dme_get(), so a failed read of
VS_POWERSTATE is also reported as "link down" with the value 1.
A subsequent patch exports this helper for use by other DWC glue
drivers. Before doing so, make it follow the usual kernel convention:
return -ENOLINK when the link is down, and report a failed
ufshcd_dme_get() as a negative errno instead of its positive UIC result.
Declare dme_result as u32 to match the ufshcd_dme_get() parameter type.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
---
drivers/ufs/host/ufshcd-dwc.c | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/drivers/ufs/host/ufshcd-dwc.c b/drivers/ufs/host/ufshcd-dwc.c
index 21b1cf912dcc..71433b4d04aa 100644
--- a/drivers/ufs/host/ufshcd-dwc.c
+++ b/drivers/ufs/host/ufshcd-dwc.c
@@ -48,20 +48,23 @@ static void ufshcd_dwc_program_clk_div(struct ufs_hba *hba, u32 divider_val)
* ufshcd_dwc_link_is_up() - check if link is up.
* @hba: private structure pointer
*
- * Return: 0 on success, non-zero value on failure.
+ * Return: 0 on success, a negative errno value on failure.
*/
static int ufshcd_dwc_link_is_up(struct ufs_hba *hba)
{
- int dme_result = 0;
+ u32 dme_result = 0;
+ int ret;
- ufshcd_dme_get(hba, UIC_ARG_MIB(VS_POWERSTATE), &dme_result);
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(VS_POWERSTATE), &dme_result);
+ if (ret)
+ return ret > 0 ? -EIO : ret;
if (dme_result == UFSHCD_LINK_IS_UP) {
ufshcd_set_link_active(hba);
return 0;
}
- return 1;
+ return -ENOLINK;
}
/**
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v10 03/11] ufs: unipro: Move Tx/Rx FSM state definitions to unipro.h
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 13:32 ` [PATCH v10 02/11] ufs: dwc: Return a negative error if the link is down Larisa Grigore
@ 2026-11-03 13:32 ` Larisa Grigore
2026-11-03 13:32 ` [PATCH v10 04/11] ufs: unipro: Add TX/RX FSM state attributes Larisa Grigore
` (7 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Larisa Grigore @ 2026-11-03 13:32 UTC (permalink / raw)
To: Larisa Grigore, NXP S32 Linux Team, Alim Akhtar, Bart Van Assche,
Bean Huo, Stanley Jhu, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, James E.J. Bottomley, Martin K. Petersen,
Sai Krishna Potthuri, Ajay Neeli, Manivannan Sadhasivam,
Pedro Sousa, Adrian Hunter, Can Guo, Asutosh Das
Cc: imx, linux-scsi, devicetree, linux-kernel, linux-arm-msm, clizzi,
aruizrui, eballetb, echanude, Frank Li
Move the Tx/Rx FSM state definitions from ufshcd-dwc.h to
include/ufs/unipro.h.
These state values are UniPro/M-PHY related and are not specific to
the DesignWare UFS host controller implementation, so unipro.h is a
more appropriate location for sharing them.
As part of this move, drop the driver-private TX_FSM_HIBERN8 define in
ufs-hisi and ufs-qcom and repoint their Hibern8 checks at the shared
UFS_TX_STATE_HIBERN8 enum constant. This is a no-op change.
Since the definitions become visible to every user of the shared
header, prefix the enum constants with UFS_ (e.g. RX_STATE_HIBERN8
becomes UFS_RX_STATE_HIBERN8) to avoid polluting the global namespace,
and update the existing AMD Versal2 users accordingly.
This also prepares for using these definitions in a subsequent commit.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
---
drivers/ufs/host/ufs-amd-versal2.c | 8 ++++----
drivers/ufs/host/ufs-hisi.c | 8 ++++----
drivers/ufs/host/ufs-hisi.h | 1 -
drivers/ufs/host/ufs-qcom.c | 4 ++--
drivers/ufs/host/ufs-qcom.h | 1 -
drivers/ufs/host/ufshcd-dwc.h | 19 -------------------
include/ufs/unipro.h | 20 ++++++++++++++++++++
7 files changed, 30 insertions(+), 31 deletions(-)
diff --git a/drivers/ufs/host/ufs-amd-versal2.c b/drivers/ufs/host/ufs-amd-versal2.c
index dff0c2c95486..7c0f102ffe7b 100644
--- a/drivers/ufs/host/ufs-amd-versal2.c
+++ b/drivers/ufs/host/ufs-amd-versal2.c
@@ -118,8 +118,8 @@ static int ufs_versal2_enable_phy(struct ufs_hba *hba)
if (ret)
return ret;
- if (reg == TX_STATE_HIBERN8 || reg == TX_STATE_SLEEP ||
- reg == TX_STATE_LSBURST)
+ if (reg == UFS_TX_STATE_HIBERN8 || reg == UFS_TX_STATE_SLEEP ||
+ reg == UFS_TX_STATE_LSBURST)
break;
time_left--;
@@ -138,8 +138,8 @@ static int ufs_versal2_enable_phy(struct ufs_hba *hba)
if (ret)
return ret;
- if (reg == RX_STATE_HIBERN8 || reg == RX_STATE_SLEEP ||
- reg == RX_STATE_LSBURST)
+ if (reg == UFS_RX_STATE_HIBERN8 || reg == UFS_RX_STATE_SLEEP ||
+ reg == UFS_RX_STATE_LSBURST)
break;
time_left--;
diff --git a/drivers/ufs/host/ufs-hisi.c b/drivers/ufs/host/ufs-hisi.c
index 993e20ac211d..aaf66bfc3e70 100644
--- a/drivers/ufs/host/ufs-hisi.c
+++ b/drivers/ufs/host/ufs-hisi.c
@@ -34,8 +34,8 @@ static int ufs_hisi_check_hibern8(struct ufs_hba *hba)
&tx_fsm_val_0);
err |= ufshcd_dme_get(hba,
UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE, 1), &tx_fsm_val_1);
- if (err || (tx_fsm_val_0 == TX_FSM_HIBERN8 &&
- tx_fsm_val_1 == TX_FSM_HIBERN8))
+ if (err || (tx_fsm_val_0 == UFS_TX_STATE_HIBERN8 &&
+ tx_fsm_val_1 == UFS_TX_STATE_HIBERN8))
break;
/* sleep for max. 200us */
@@ -56,8 +56,8 @@ static int ufs_hisi_check_hibern8(struct ufs_hba *hba)
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_FSM_HIBERN8 ||
- tx_fsm_val_1 != TX_FSM_HIBERN8) {
+ } 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);
diff --git a/drivers/ufs/host/ufs-hisi.h b/drivers/ufs/host/ufs-hisi.h
index 5a90c0f4e90c..6af6a268458d 100644
--- a/drivers/ufs/host/ufs-hisi.h
+++ b/drivers/ufs/host/ufs-hisi.h
@@ -43,7 +43,6 @@
* M-TX Configuration Attributes for Hixxxx
*/
#define MPHY_TX_FSM_STATE 0x41
-#define TX_FSM_HIBERN8 0x1
/*
* Hixxxx UFS HC specific Registers
diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
index 27c87e62f8e9..8a2e0c63fb03 100644
--- a/drivers/ufs/host/ufs-qcom.c
+++ b/drivers/ufs/host/ufs-qcom.c
@@ -424,7 +424,7 @@ static int ufs_qcom_check_hibern8(struct ufs_hba *hba)
UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE,
UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)),
&tx_fsm_val);
- if (err || tx_fsm_val == TX_FSM_HIBERN8)
+ if (err || tx_fsm_val == UFS_TX_STATE_HIBERN8)
break;
/* sleep for max. 200us */
@@ -444,7 +444,7 @@ static int ufs_qcom_check_hibern8(struct ufs_hba *hba)
if (err) {
dev_err(hba->dev, "%s: unable to get TX_FSM_STATE, err %d\n",
__func__, err);
- } else if (tx_fsm_val != TX_FSM_HIBERN8) {
+ } 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);
diff --git a/drivers/ufs/host/ufs-qcom.h b/drivers/ufs/host/ufs-qcom.h
index 944b2e532a09..1bda960c1d87 100644
--- a/drivers/ufs/host/ufs-qcom.h
+++ b/drivers/ufs/host/ufs-qcom.h
@@ -11,7 +11,6 @@
#include <ufs/ufshcd.h>
#define MPHY_TX_FSM_STATE 0x41
-#define TX_FSM_HIBERN8 0x1
#define HBRN8_POLL_TOUT_MS 100
#define DEFAULT_CLK_RATE_HZ 1000000
#define MAX_SUPP_MAC 64
diff --git a/drivers/ufs/host/ufshcd-dwc.h b/drivers/ufs/host/ufshcd-dwc.h
index c618bb914904..a5e40e7604a1 100644
--- a/drivers/ufs/host/ufshcd-dwc.h
+++ b/drivers/ufs/host/ufshcd-dwc.h
@@ -39,25 +39,6 @@
#define RX_AFE_CTLE_IDAC(n) (0x4001 + ((n) * 0x100))
#define FW_CALIB_CCFG(n) (0x404D + ((n) * 0x100))
-/* Tx/Rx FSM state */
-enum rx_fsm_state {
- RX_STATE_DISABLED = 0,
- RX_STATE_HIBERN8 = 1,
- RX_STATE_SLEEP = 2,
- RX_STATE_STALL = 3,
- RX_STATE_LSBURST = 4,
- RX_STATE_HSBURST = 5,
-};
-
-enum tx_fsm_state {
- TX_STATE_DISABLED = 0,
- TX_STATE_HIBERN8 = 1,
- TX_STATE_SLEEP = 2,
- TX_STATE_STALL = 3,
- TX_STATE_LSBURST = 4,
- TX_STATE_HSBURST = 5,
-};
-
struct ufshcd_dme_attr_val {
u32 attr_sel;
u32 mib_val;
diff --git a/include/ufs/unipro.h b/include/ufs/unipro.h
index 9c168703b104..16b2051f7241 100644
--- a/include/ufs/unipro.h
+++ b/include/ufs/unipro.h
@@ -466,4 +466,24 @@ enum {
CPORT_CONNECTED,
};
+/* RX_FSM_State encoding, MIPI M-PHY v6.0 Table 50 (M-RX Status Attributes) */
+enum ufs_rx_fsm_state {
+ UFS_RX_STATE_DISABLED = 0,
+ UFS_RX_STATE_HIBERN8 = 1,
+ UFS_RX_STATE_SLEEP = 2,
+ UFS_RX_STATE_STALL = 3,
+ UFS_RX_STATE_LSBURST = 4,
+ UFS_RX_STATE_HSBURST = 5,
+};
+
+/* TX_FSM_State encoding, MIPI M-PHY v6.0 Table 47 (M-TX Status Attributes) */
+enum ufs_tx_fsm_state {
+ UFS_TX_STATE_DISABLED = 0,
+ UFS_TX_STATE_HIBERN8 = 1,
+ UFS_TX_STATE_SLEEP = 2,
+ UFS_TX_STATE_STALL = 3,
+ UFS_TX_STATE_LSBURST = 4,
+ UFS_TX_STATE_HSBURST = 5,
+};
+
#endif /* _UNIPRO_H_ */
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v10 04/11] ufs: unipro: Add TX/RX FSM state attributes
2026-11-03 13:32 [PATCH v10 00/11] Add S32N79RDB UFS support Larisa Grigore
` (2 preceding siblings ...)
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 13:32 ` Larisa Grigore
2026-11-03 13:32 ` [PATCH v10 05/11] ufs: core: Add common Hibern8 TX FSM polling helper Larisa Grigore
` (6 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Larisa Grigore @ 2026-11-03 13:32 UTC (permalink / raw)
To: Larisa Grigore, NXP S32 Linux Team, Alim Akhtar, Bart Van Assche,
Bean Huo, Stanley Jhu, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, James E.J. Bottomley, Martin K. Petersen,
Sai Krishna Potthuri, Ajay Neeli, Manivannan Sadhasivam,
Pedro Sousa, Adrian Hunter, Can Guo, Asutosh Das
Cc: imx, linux-scsi, devicetree, linux-kernel, linux-arm-msm, clizzi,
aruizrui, eballetb, echanude, Frank Li
Add TX_FSM_STATE and RX_FSM_STATE to include/ufs/unipro.h, as these are
UniPro M-PHY attribute definitions rather than host controller specific
ones.
Remove the duplicated local definitions from the HiSilicon, Qualcomm and
DesignWare UFS headers, and update the HiSilicon, Qualcomm and AMD Versal2
users accordingly.
This also prepares for using these definitions in a subsequent commit.
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
---
drivers/ufs/host/ufs-amd-versal2.c | 4 ++--
drivers/ufs/host/ufs-hisi.c | 8 ++++----
drivers/ufs/host/ufs-hisi.h | 5 -----
drivers/ufs/host/ufs-qcom.c | 4 ++--
drivers/ufs/host/ufs-qcom.h | 1 -
drivers/ufs/host/ufshcd-dwc.h | 4 ----
include/ufs/unipro.h | 2 ++
7 files changed, 10 insertions(+), 18 deletions(-)
diff --git a/drivers/ufs/host/ufs-amd-versal2.c b/drivers/ufs/host/ufs-amd-versal2.c
index 7c0f102ffe7b..93755befb880 100644
--- a/drivers/ufs/host/ufs-amd-versal2.c
+++ b/drivers/ufs/host/ufs-amd-versal2.c
@@ -112,7 +112,7 @@ static int ufs_versal2_enable_phy(struct ufs_hba *hba)
u32 time_left, mibsel;
time_left = TIMEOUT_MICROSEC;
- mibsel = UIC_ARG_MIB_SEL(MTX_FSM_STATE, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(offset));
+ mibsel = UIC_ARG_MIB_SEL(TX_FSM_STATE, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(offset));
do {
ret = ufshcd_dme_get(hba, mibsel, ®);
if (ret)
@@ -132,7 +132,7 @@ static int ufs_versal2_enable_phy(struct ufs_hba *hba)
}
time_left = TIMEOUT_MICROSEC;
- mibsel = UIC_ARG_MIB_SEL(MRX_FSM_STATE, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(offset));
+ mibsel = UIC_ARG_MIB_SEL(RX_FSM_STATE, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(offset));
do {
ret = ufshcd_dme_get(hba, mibsel, ®);
if (ret)
diff --git a/drivers/ufs/host/ufs-hisi.c b/drivers/ufs/host/ufs-hisi.c
index aaf66bfc3e70..fb44a776e9e7 100644
--- a/drivers/ufs/host/ufs-hisi.c
+++ b/drivers/ufs/host/ufs-hisi.c
@@ -30,10 +30,10 @@ static int ufs_hisi_check_hibern8(struct ufs_hba *hba)
unsigned long timeout = jiffies + msecs_to_jiffies(HBRN8_POLL_TOUT_MS);
do {
- err = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE, 0),
+ 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(MPHY_TX_FSM_STATE, 1), &tx_fsm_val_1);
+ 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;
@@ -47,10 +47,10 @@ static int ufs_hisi_check_hibern8(struct ufs_hba *hba)
* check the state again.
*/
if (time_after(jiffies, timeout)) {
- err = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE, 0),
+ 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(MPHY_TX_FSM_STATE, 1), &tx_fsm_val_1);
+ UIC_ARG_MIB_SEL(TX_FSM_STATE, 1), &tx_fsm_val_1);
}
if (err) {
diff --git a/drivers/ufs/host/ufs-hisi.h b/drivers/ufs/host/ufs-hisi.h
index 6af6a268458d..08dccfbec404 100644
--- a/drivers/ufs/host/ufs-hisi.h
+++ b/drivers/ufs/host/ufs-hisi.h
@@ -39,11 +39,6 @@
#define MASK_UFS_DEVICE_RESET (0x1 << 16)
#define BIT_UFS_DEVICE_RESET (0x1)
-/*
- * M-TX Configuration Attributes for Hixxxx
- */
-#define MPHY_TX_FSM_STATE 0x41
-
/*
* Hixxxx UFS HC specific Registers
*/
diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
index 8a2e0c63fb03..e5676b86de23 100644
--- a/drivers/ufs/host/ufs-qcom.c
+++ b/drivers/ufs/host/ufs-qcom.c
@@ -421,7 +421,7 @@ static int ufs_qcom_check_hibern8(struct ufs_hba *hba)
do {
err = ufshcd_dme_get(hba,
- UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE,
+ 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)
@@ -437,7 +437,7 @@ static int ufs_qcom_check_hibern8(struct ufs_hba *hba)
*/
if (time_after(jiffies, timeout))
err = ufshcd_dme_get(hba,
- UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE,
+ UIC_ARG_MIB_SEL(TX_FSM_STATE,
UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)),
&tx_fsm_val);
diff --git a/drivers/ufs/host/ufs-qcom.h b/drivers/ufs/host/ufs-qcom.h
index 1bda960c1d87..0b062cf912c5 100644
--- a/drivers/ufs/host/ufs-qcom.h
+++ b/drivers/ufs/host/ufs-qcom.h
@@ -10,7 +10,6 @@
#include <soc/qcom/ice.h>
#include <ufs/ufshcd.h>
-#define MPHY_TX_FSM_STATE 0x41
#define HBRN8_POLL_TOUT_MS 100
#define DEFAULT_CLK_RATE_HZ 1000000
#define MAX_SUPP_MAC 64
diff --git a/drivers/ufs/host/ufshcd-dwc.h b/drivers/ufs/host/ufshcd-dwc.h
index a5e40e7604a1..0406f2bb576b 100644
--- a/drivers/ufs/host/ufshcd-dwc.h
+++ b/drivers/ufs/host/ufshcd-dwc.h
@@ -27,10 +27,6 @@
#define CBREFREFCLK_GATE_OVR_EN BIT(7)
-/* M-PHY Attributes */
-#define MTX_FSM_STATE 0x41
-#define MRX_FSM_STATE 0xC1
-
/* M-PHY registers */
#define RX_OVRD_IN_1(n) (0x3006 + ((n) * 0x100))
#define RX_PCS_OUT(n) (0x300F + ((n) * 0x100))
diff --git a/include/ufs/unipro.h b/include/ufs/unipro.h
index 16b2051f7241..7adba11f4700 100644
--- a/include/ufs/unipro.h
+++ b/include/ufs/unipro.h
@@ -34,6 +34,7 @@
#define TX_PWM_G6_G7_SYNC_LENGTH 0x0034
#define TX_HS_DEEMPHASIS_SETTING 0x0037
#define TX_HS_PRESHOOT_SETTING 0x003B
+#define TX_FSM_STATE 0x0041
#define TX_REFCLKFREQ 0x00EB
#define TX_CFGCLKFREQVAL 0x00EC
#define CFGEXTRATTR 0x00F0
@@ -72,6 +73,7 @@
#define CFGRXCDR8 0x00BA
#define CFGRXOVR8 0x00BD
#define CFGRXOVR6 0x00BF
+#define RX_FSM_STATE 0x00C1
#define RX_FOM 0x00C2
#define RXDIRECTCTRL2 0x00C7
#define CFGRXOVR4 0x00E9
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v10 05/11] ufs: core: Add common Hibern8 TX FSM polling helper
2026-11-03 13:32 [PATCH v10 00/11] Add S32N79RDB UFS support Larisa Grigore
` (3 preceding siblings ...)
2026-11-03 13:32 ` [PATCH v10 04/11] ufs: unipro: Add TX/RX FSM state attributes Larisa Grigore
@ 2026-11-03 13:32 ` Larisa Grigore
2026-11-03 13:32 ` [PATCH v10 06/11] ufs: dwc: Move Versal2 M-PHY CREG access helpers into ufshcd-dwc Larisa Grigore
` (5 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Larisa Grigore @ 2026-11-03 13:32 UTC (permalink / raw)
To: Larisa Grigore, NXP S32 Linux Team, Alim Akhtar, Bart Van Assche,
Bean Huo, Stanley Jhu, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, James E.J. Bottomley, Martin K. Petersen,
Sai Krishna Potthuri, Ajay Neeli, Manivannan Sadhasivam,
Pedro Sousa, Adrian Hunter, Can Guo, Asutosh Das
Cc: imx, linux-scsi, devicetree, linux-kernel, linux-arm-msm, clizzi,
aruizrui, eballetb, echanude
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
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v10 06/11] ufs: dwc: Move Versal2 M-PHY CREG access helpers into ufshcd-dwc
2026-11-03 13:32 [PATCH v10 00/11] Add S32N79RDB UFS support Larisa Grigore
` (4 preceding siblings ...)
2026-11-03 13:32 ` [PATCH v10 05/11] ufs: core: Add common Hibern8 TX FSM polling helper Larisa Grigore
@ 2026-11-03 13:32 ` Larisa Grigore
2026-11-03 13:32 ` [PATCH v10 07/11] ufs: dwc: Export common clock divider and link status helpers Larisa Grigore
` (4 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Larisa Grigore @ 2026-11-03 13:32 UTC (permalink / raw)
To: Larisa Grigore, NXP S32 Linux Team, Alim Akhtar, Bart Van Assche,
Bean Huo, Stanley Jhu, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, James E.J. Bottomley, Martin K. Petersen,
Sai Krishna Potthuri, Ajay Neeli, Manivannan Sadhasivam,
Pedro Sousa, Adrian Hunter, Can Guo, Asutosh Das
Cc: imx, linux-scsi, devicetree, linux-kernel, linux-arm-msm, clizzi,
aruizrui, eballetb, echanude, Frank Li
The AMD Versal Gen 2 UFS platform driver carries local helpers to access
M-PHY control registers through the UniPro CREG interface.
Move these helpers into the common Synopsys DWC layer as
ufshcd_dwc_phy_reg_read() and ufshcd_dwc_phy_reg_write(). This avoids code
duplication in DWC-based glue drivers and provides a single implementation
for future reuse.
Drop the static storage class from phy_write_attrs[] and phy_read_attrs[].
In the Versal2 driver these were static arrays whose mib_val fields were
modified on every call, so two concurrent callers (e.g. two host
instances) could overwrite each other's address/value. Building the
arrays on the stack with the values filled in at initialization removes
this shared mutable state.
Update the AMD Versal2 glue driver to use the new ufshcd-dwc helpers.
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
---
drivers/ufs/host/ufs-amd-versal2.c | 85 ++++++------------------------
drivers/ufs/host/ufshcd-dwc.c | 69 ++++++++++++++++++++++++
drivers/ufs/host/ufshcd-dwc.h | 2 +
3 files changed, 88 insertions(+), 68 deletions(-)
diff --git a/drivers/ufs/host/ufs-amd-versal2.c b/drivers/ufs/host/ufs-amd-versal2.c
index 93755befb880..38ba155ce41a 100644
--- a/drivers/ufs/host/ufs-amd-versal2.c
+++ b/drivers/ufs/host/ufs-amd-versal2.c
@@ -43,57 +43,6 @@ struct ufs_versal2_host {
u8 ctlecompval1;
};
-static int ufs_versal2_phy_reg_write(struct ufs_hba *hba, u32 addr, u32 val)
-{
- static struct ufshcd_dme_attr_val phy_write_attrs[] = {
- { UIC_ARG_MIB(CBCREGADDRLSB), 0, DME_LOCAL },
- { UIC_ARG_MIB(CBCREGADDRMSB), 0, DME_LOCAL },
- { UIC_ARG_MIB(CBCREGWRLSB), 0, DME_LOCAL },
- { UIC_ARG_MIB(CBCREGWRMSB), 0, DME_LOCAL },
- { UIC_ARG_MIB(CBCREGRDWRSEL), 1, DME_LOCAL },
- { UIC_ARG_MIB(VS_MPHYCFGUPDT), 1, DME_LOCAL }
- };
-
- phy_write_attrs[0].mib_val = (u8)addr;
- phy_write_attrs[1].mib_val = (u8)(addr >> 8);
- phy_write_attrs[2].mib_val = (u8)val;
- phy_write_attrs[3].mib_val = (u8)(val >> 8);
-
- return ufshcd_dwc_dme_set_attrs(hba, phy_write_attrs, ARRAY_SIZE(phy_write_attrs));
-}
-
-static int ufs_versal2_phy_reg_read(struct ufs_hba *hba, u32 addr, u32 *val)
-{
- u32 mib_val;
- int ret;
- static struct ufshcd_dme_attr_val phy_read_attrs[] = {
- { UIC_ARG_MIB(CBCREGADDRLSB), 0, DME_LOCAL },
- { UIC_ARG_MIB(CBCREGADDRMSB), 0, DME_LOCAL },
- { UIC_ARG_MIB(CBCREGRDWRSEL), 0, DME_LOCAL },
- { UIC_ARG_MIB(VS_MPHYCFGUPDT), 1, DME_LOCAL }
- };
-
- phy_read_attrs[0].mib_val = (u8)addr;
- phy_read_attrs[1].mib_val = (u8)(addr >> 8);
-
- ret = ufshcd_dwc_dme_set_attrs(hba, phy_read_attrs, ARRAY_SIZE(phy_read_attrs));
- if (ret)
- return ret;
-
- ret = ufshcd_dme_get(hba, UIC_ARG_MIB(CBCREGRDLSB), &mib_val);
- if (ret)
- return ret;
-
- *val = mib_val;
- ret = ufshcd_dme_get(hba, UIC_ARG_MIB(CBCREGRDMSB), &mib_val);
- if (ret)
- return ret;
-
- *val |= (mib_val << 8);
-
- return 0;
-}
-
static int ufs_versal2_enable_phy(struct ufs_hba *hba)
{
u32 offset, reg;
@@ -162,64 +111,64 @@ static int ufs_versal2_setup_phy(struct ufs_hba *hba)
u32 reg;
/* Bypass RX-AFE offset calibrations (ATT/CTLE) */
- ret = ufs_versal2_phy_reg_read(hba, FAST_FLAGS(0), ®);
+ ret = ufshcd_dwc_phy_reg_read(hba, FAST_FLAGS(0), ®);
if (ret)
return ret;
reg |= MPHY_FAST_RX_AFE_CAL;
- ret = ufs_versal2_phy_reg_write(hba, FAST_FLAGS(0), reg);
+ ret = ufshcd_dwc_phy_reg_write(hba, FAST_FLAGS(0), reg);
if (ret)
return ret;
- ret = ufs_versal2_phy_reg_read(hba, FAST_FLAGS(1), ®);
+ ret = ufshcd_dwc_phy_reg_read(hba, FAST_FLAGS(1), ®);
if (ret)
return ret;
reg |= MPHY_FAST_RX_AFE_CAL;
- ret = ufs_versal2_phy_reg_write(hba, FAST_FLAGS(1), reg);
+ ret = ufshcd_dwc_phy_reg_write(hba, FAST_FLAGS(1), reg);
if (ret)
return ret;
/* Program ATT and CTLE compensation values */
if (host->attcompval0) {
- ret = ufs_versal2_phy_reg_write(hba, RX_AFE_ATT_IDAC(0), host->attcompval0);
+ ret = ufshcd_dwc_phy_reg_write(hba, RX_AFE_ATT_IDAC(0), host->attcompval0);
if (ret)
return ret;
}
if (host->attcompval1) {
- ret = ufs_versal2_phy_reg_write(hba, RX_AFE_ATT_IDAC(1), host->attcompval1);
+ ret = ufshcd_dwc_phy_reg_write(hba, RX_AFE_ATT_IDAC(1), host->attcompval1);
if (ret)
return ret;
}
if (host->ctlecompval0) {
- ret = ufs_versal2_phy_reg_write(hba, RX_AFE_CTLE_IDAC(0), host->ctlecompval0);
+ ret = ufshcd_dwc_phy_reg_write(hba, RX_AFE_CTLE_IDAC(0), host->ctlecompval0);
if (ret)
return ret;
}
if (host->ctlecompval1) {
- ret = ufs_versal2_phy_reg_write(hba, RX_AFE_CTLE_IDAC(1), host->ctlecompval1);
+ ret = ufshcd_dwc_phy_reg_write(hba, RX_AFE_CTLE_IDAC(1), host->ctlecompval1);
if (ret)
return ret;
}
- ret = ufs_versal2_phy_reg_read(hba, FW_CALIB_CCFG(0), ®);
+ ret = ufshcd_dwc_phy_reg_read(hba, FW_CALIB_CCFG(0), ®);
if (ret)
return ret;
reg |= MPHY_FW_CALIB_CFG_VAL;
- ret = ufs_versal2_phy_reg_write(hba, FW_CALIB_CCFG(0), reg);
+ ret = ufshcd_dwc_phy_reg_write(hba, FW_CALIB_CCFG(0), reg);
if (ret)
return ret;
- ret = ufs_versal2_phy_reg_read(hba, FW_CALIB_CCFG(1), ®);
+ ret = ufshcd_dwc_phy_reg_read(hba, FW_CALIB_CCFG(1), ®);
if (ret)
return ret;
reg |= MPHY_FW_CALIB_CFG_VAL;
- return ufs_versal2_phy_reg_write(hba, FW_CALIB_CCFG(1), reg);
+ return ufshcd_dwc_phy_reg_write(hba, FW_CALIB_CCFG(1), reg);
}
static int ufs_versal2_phy_init(struct ufs_hba *hba)
@@ -384,7 +333,7 @@ static int ufs_versal2_phy_ratesel(struct ufs_hba *hba, u32 activelanes, u32 rx_
for (lane = 0; lane < activelanes; lane++) {
time_left = TIMEOUT_MICROSEC;
- ret = ufs_versal2_phy_reg_read(hba, RX_OVRD_IN_1(lane), ®);
+ ret = ufshcd_dwc_phy_reg_read(hba, RX_OVRD_IN_1(lane), ®);
if (ret)
return ret;
@@ -394,12 +343,12 @@ static int ufs_versal2_phy_ratesel(struct ufs_hba *hba, u32 activelanes, u32 rx_
else
reg &= ~MPHY_RX_OVRD_VAL;
- ret = ufs_versal2_phy_reg_write(hba, RX_OVRD_IN_1(lane), reg);
+ ret = ufshcd_dwc_phy_reg_write(hba, RX_OVRD_IN_1(lane), reg);
if (ret)
return ret;
do {
- ret = ufs_versal2_phy_reg_read(hba, RX_PCS_OUT(lane), ®);
+ ret = ufshcd_dwc_phy_reg_read(hba, RX_PCS_OUT(lane), ®);
if (ret)
return ret;
@@ -461,12 +410,12 @@ static int ufs_versal2_pwr_change_notify(struct ufs_hba *hba, enum ufs_notify_ch
/* Remove rx_req override */
for (lane = 0; lane < dev_req_params->lane_tx; lane++) {
- ret = ufs_versal2_phy_reg_read(hba, RX_OVRD_IN_1(lane), ®);
+ ret = ufshcd_dwc_phy_reg_read(hba, RX_OVRD_IN_1(lane), ®);
if (ret)
return ret;
reg &= ~MPHY_RX_OVRD_EN;
- ret = ufs_versal2_phy_reg_write(hba, RX_OVRD_IN_1(lane), reg);
+ ret = ufshcd_dwc_phy_reg_write(hba, RX_OVRD_IN_1(lane), reg);
if (ret)
return ret;
}
diff --git a/drivers/ufs/host/ufshcd-dwc.c b/drivers/ufs/host/ufshcd-dwc.c
index 71433b4d04aa..37876e863672 100644
--- a/drivers/ufs/host/ufshcd-dwc.c
+++ b/drivers/ufs/host/ufshcd-dwc.c
@@ -144,6 +144,75 @@ int ufshcd_dwc_link_startup_notify(struct ufs_hba *hba,
}
EXPORT_SYMBOL(ufshcd_dwc_link_startup_notify);
+/**
+ * ufshcd_dwc_phy_reg_write() - Write a DWC M-PHY CREG register
+ * @hba: private structure pointer
+ * @addr: M-PHY CREG register address
+ * @val: value to write
+ *
+ * Write a 16-bit M-PHY CREG register through the Synopsys DesignWare
+ * UniPro indirect register access interface.
+ *
+ * Return: 0 on success, non-zero value on failure.
+ */
+int ufshcd_dwc_phy_reg_write(struct ufs_hba *hba, u32 addr, u32 val)
+{
+ const struct ufshcd_dme_attr_val phy_write_attrs[] = {
+ { UIC_ARG_MIB(CBCREGADDRLSB), (u8)addr, DME_LOCAL },
+ { UIC_ARG_MIB(CBCREGADDRMSB), (u8)(addr >> 8), DME_LOCAL },
+ { UIC_ARG_MIB(CBCREGWRLSB), (u8)val, DME_LOCAL },
+ { UIC_ARG_MIB(CBCREGWRMSB), (u8)(val >> 8), DME_LOCAL },
+ { UIC_ARG_MIB(CBCREGRDWRSEL), 1, DME_LOCAL },
+ { UIC_ARG_MIB(VS_MPHYCFGUPDT), 1, DME_LOCAL }
+ };
+
+ return ufshcd_dwc_dme_set_attrs(hba, phy_write_attrs,
+ ARRAY_SIZE(phy_write_attrs));
+}
+EXPORT_SYMBOL(ufshcd_dwc_phy_reg_write);
+
+/**
+ * ufshcd_dwc_phy_reg_read() - Read a DWC M-PHY CREG register
+ * @hba: private structure pointer
+ * @addr: M-PHY CREG register address
+ * @val: pointer where the read value is stored
+ *
+ * Read a 16-bit M-PHY CREG register through the Synopsys DesignWare
+ * UniPro indirect register access interface.
+ *
+ * Return: 0 on success, non-zero value on failure.
+ */
+int ufshcd_dwc_phy_reg_read(struct ufs_hba *hba, u32 addr, u32 *val)
+{
+ const struct ufshcd_dme_attr_val phy_read_attrs[] = {
+ { UIC_ARG_MIB(CBCREGADDRLSB), (u8)addr, DME_LOCAL },
+ { UIC_ARG_MIB(CBCREGADDRMSB), (u8)(addr >> 8), DME_LOCAL },
+ { UIC_ARG_MIB(CBCREGRDWRSEL), 0, DME_LOCAL },
+ { UIC_ARG_MIB(VS_MPHYCFGUPDT), 1, DME_LOCAL }
+ };
+ u32 mib_val;
+ int ret;
+
+ ret = ufshcd_dwc_dme_set_attrs(hba, phy_read_attrs,
+ ARRAY_SIZE(phy_read_attrs));
+ if (ret)
+ return ret;
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(CBCREGRDLSB), &mib_val);
+ if (ret)
+ return ret;
+
+ *val = mib_val;
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(CBCREGRDMSB), &mib_val);
+ if (ret)
+ return ret;
+
+ *val |= (mib_val << 8);
+
+ return 0;
+}
+EXPORT_SYMBOL(ufshcd_dwc_phy_reg_read);
+
MODULE_AUTHOR("Joao Pinto <Joao.Pinto@synopsys.com>");
MODULE_DESCRIPTION("UFS Host driver for Synopsys Designware Core");
MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/ufs/host/ufshcd-dwc.h b/drivers/ufs/host/ufshcd-dwc.h
index 0406f2bb576b..d8673cd54d4c 100644
--- a/drivers/ufs/host/ufshcd-dwc.h
+++ b/drivers/ufs/host/ufshcd-dwc.h
@@ -45,4 +45,6 @@ int ufshcd_dwc_link_startup_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status);
int ufshcd_dwc_dme_set_attrs(struct ufs_hba *hba,
const struct ufshcd_dme_attr_val *v, int n);
+int ufshcd_dwc_phy_reg_write(struct ufs_hba *hba, u32 addr, u32 val);
+int ufshcd_dwc_phy_reg_read(struct ufs_hba *hba, u32 addr, u32 *val);
#endif /* End of Header */
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v10 07/11] ufs: dwc: Export common clock divider and link status helpers
2026-11-03 13:32 [PATCH v10 00/11] Add S32N79RDB UFS support Larisa Grigore
` (5 preceding siblings ...)
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 ` Larisa Grigore
2026-11-03 13:32 ` [PATCH v10 08/11] dt-bindings: ufs: Add NXP S32N79 UFS host controller Larisa Grigore
` (3 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Larisa Grigore @ 2026-11-03 13:32 UTC (permalink / raw)
To: Larisa Grigore, NXP S32 Linux Team, Alim Akhtar, Bart Van Assche,
Bean Huo, Stanley Jhu, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, James E.J. Bottomley, Martin K. Petersen,
Sai Krishna Potthuri, Ajay Neeli, Manivannan Sadhasivam,
Pedro Sousa, Adrian Hunter, Can Guo, Asutosh Das
Cc: imx, linux-scsi, devicetree, linux-kernel, linux-arm-msm, clizzi,
aruizrui, eballetb, echanude, Frank Li
Make ufshcd_dwc_program_clk_div() and ufshcd_dwc_link_is_up() available to
DWC-based platform glue drivers. These helpers are needed by platform
drivers that must program the controller HCLK divider and verify link state
during their vendor-specific initialization flows.
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
---
drivers/ufs/host/ufshcd-dwc.c | 6 ++++--
drivers/ufs/host/ufshcd-dwc.h | 2 ++
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/drivers/ufs/host/ufshcd-dwc.c b/drivers/ufs/host/ufshcd-dwc.c
index 37876e863672..a8c3d9b47de8 100644
--- a/drivers/ufs/host/ufshcd-dwc.c
+++ b/drivers/ufs/host/ufshcd-dwc.c
@@ -39,10 +39,11 @@ EXPORT_SYMBOL(ufshcd_dwc_dme_set_attrs);
* @divider_val: clock divider value to be programmed
*
*/
-static void ufshcd_dwc_program_clk_div(struct ufs_hba *hba, u32 divider_val)
+void ufshcd_dwc_program_clk_div(struct ufs_hba *hba, u32 divider_val)
{
ufshcd_writel(hba, divider_val, DWC_UFS_REG_HCLKDIV);
}
+EXPORT_SYMBOL(ufshcd_dwc_program_clk_div);
/**
* ufshcd_dwc_link_is_up() - check if link is up.
@@ -50,7 +51,7 @@ static void ufshcd_dwc_program_clk_div(struct ufs_hba *hba, u32 divider_val)
*
* Return: 0 on success, a negative errno value on failure.
*/
-static int ufshcd_dwc_link_is_up(struct ufs_hba *hba)
+int ufshcd_dwc_link_is_up(struct ufs_hba *hba)
{
u32 dme_result = 0;
int ret;
@@ -66,6 +67,7 @@ static int ufshcd_dwc_link_is_up(struct ufs_hba *hba)
return -ENOLINK;
}
+EXPORT_SYMBOL(ufshcd_dwc_link_is_up);
/**
* ufshcd_dwc_connection_setup() - configure unipro attributes.
diff --git a/drivers/ufs/host/ufshcd-dwc.h b/drivers/ufs/host/ufshcd-dwc.h
index d8673cd54d4c..53b523ef59df 100644
--- a/drivers/ufs/host/ufshcd-dwc.h
+++ b/drivers/ufs/host/ufshcd-dwc.h
@@ -47,4 +47,6 @@ int ufshcd_dwc_dme_set_attrs(struct ufs_hba *hba,
const struct ufshcd_dme_attr_val *v, int n);
int ufshcd_dwc_phy_reg_write(struct ufs_hba *hba, u32 addr, u32 val);
int ufshcd_dwc_phy_reg_read(struct ufs_hba *hba, u32 addr, u32 *val);
+int ufshcd_dwc_link_is_up(struct ufs_hba *hba);
+void ufshcd_dwc_program_clk_div(struct ufs_hba *hba, u32 divider_val);
#endif /* End of Header */
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v10 08/11] dt-bindings: ufs: Add NXP S32N79 UFS host controller
2026-11-03 13:32 [PATCH v10 00/11] Add S32N79RDB UFS support Larisa Grigore
` (6 preceding siblings ...)
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 ` Larisa Grigore
2026-11-03 13:32 ` [PATCH v10 09/11] ufs: dwc: Sort CBCRCTRL and CBREFCLKCTRL2 by offset Larisa Grigore
` (2 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Larisa Grigore @ 2026-11-03 13:32 UTC (permalink / raw)
To: Larisa Grigore, NXP S32 Linux Team, Alim Akhtar, Bart Van Assche,
Bean Huo, Stanley Jhu, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, James E.J. Bottomley, Martin K. Petersen,
Sai Krishna Potthuri, Ajay Neeli, Manivannan Sadhasivam,
Pedro Sousa, Adrian Hunter, Can Guo, Asutosh Das
Cc: imx, linux-scsi, devicetree, linux-kernel, linux-arm-msm, clizzi,
aruizrui, eballetb, echanude, Frank Li
Document the UFS host controller on the NXP S32N79 SoC.
The S32N79 M-PHY has two data lanes per direction and the driver programs
both of them, so restrict lanes-per-direction to 2.
The UFS core manages only the clocks added by the platform glue. Since
freq-table-hz is deprecated, the binding requires operating-points-v2 and
clock-names. As S32N79 does not scale the UFS clock, the OPP table contains
a single 0 Hz entry, which keeps the provider-configured rate while
allowing the clock to be enabled and gated.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
---
.../bindings/ufs/nxp,s32n79-ufshc.yaml | 68 +++++++++++++++++++
1 file changed, 68 insertions(+)
create mode 100644 Documentation/devicetree/bindings/ufs/nxp,s32n79-ufshc.yaml
diff --git a/Documentation/devicetree/bindings/ufs/nxp,s32n79-ufshc.yaml b/Documentation/devicetree/bindings/ufs/nxp,s32n79-ufshc.yaml
new file mode 100644
index 000000000000..541b59b8c89a
--- /dev/null
+++ b/Documentation/devicetree/bindings/ufs/nxp,s32n79-ufshc.yaml
@@ -0,0 +1,68 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/ufs/nxp,s32n79-ufshc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NXP S32N79 Universal Flash Storage (UFS) Controller
+
+maintainers:
+ - Larisa Grigore <larisa.grigore@oss.nxp.com>
+
+properties:
+ compatible:
+ const: nxp,s32n79-ufshc
+
+ reg:
+ items:
+ - description: UFS Host Controller registers
+ - description: System Control Module registers
+
+ reg-names:
+ items:
+ - const: ufshc
+ - const: scm
+
+ clocks:
+ maxItems: 1
+
+ clock-names:
+ items:
+ - const: core
+
+ lanes-per-direction:
+ const: 2
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - clocks
+ - clock-names
+ - operating-points-v2
+
+allOf:
+ - $ref: ufs-common.yaml
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ ufshc@4ac80000 {
+ compatible = "nxp,s32n79-ufshc";
+ reg = <0x4ac80000 0x1000>, <0x4ac40000 0x1000>;
+ reg-names = "ufshc", "scm";
+ interrupt-parent = <&irqsteer_coss>;
+ interrupts = <211>;
+ clocks = <&clks 0x92>;
+ clock-names = "core";
+ operating-points-v2 = <&ufs_opp_table>;
+
+ ufs_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+ opp-0 {
+ opp-hz = /bits/ 64 <0>;
+ };
+ };
+ };
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v10 09/11] ufs: dwc: Sort CBCRCTRL and CBREFCLKCTRL2 by offset
2026-11-03 13:32 [PATCH v10 00/11] Add S32N79RDB UFS support Larisa Grigore
` (7 preceding siblings ...)
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 ` 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 13:32 ` [PATCH v10 11/11] MAINTAINERS: Add NXP S32N7 UFS host controller entry Larisa Grigore
10 siblings, 0 replies; 12+ messages in thread
From: Larisa Grigore @ 2026-11-03 13:32 UTC (permalink / raw)
To: Larisa Grigore, NXP S32 Linux Team, Alim Akhtar, Bart Van Assche,
Bean Huo, Stanley Jhu, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, James E.J. Bottomley, Martin K. Petersen,
Sai Krishna Potthuri, Ajay Neeli, Manivannan Sadhasivam,
Pedro Sousa, Adrian Hunter, Can Guo, Asutosh Das
Cc: imx, linux-scsi, devicetree, linux-kernel, linux-arm-msm, clizzi,
aruizrui, eballetb, echanude, Frank Li
The RMMI attribute defines in ufshcd-dwc.h are otherwise listed in
ascending register-offset order, but CBREFCLKCTRL2 (0x8132) and
CBCRCTRL (0x811F) were placed at the top out of order. Move them down
into their sorted position next to the other CBCREG* attributes.
Pure cleanup, no functional change.
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
---
drivers/ufs/host/ufshcd-dwc.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/ufs/host/ufshcd-dwc.h b/drivers/ufs/host/ufshcd-dwc.h
index 53b523ef59df..1abf2b1deac4 100644
--- a/drivers/ufs/host/ufshcd-dwc.h
+++ b/drivers/ufs/host/ufshcd-dwc.h
@@ -13,8 +13,6 @@
#include <ufs/ufshcd.h>
/* RMMI Attributes */
-#define CBREFCLKCTRL2 0x8132
-#define CBCRCTRL 0x811F
#define CBC10DIRECTCONF2 0x810E
#define CBRATESEL 0x8114
#define CBCREGADDRLSB 0x8116
@@ -24,6 +22,8 @@
#define CBCREGRDLSB 0x811A
#define CBCREGRDMSB 0x811B
#define CBCREGRDWRSEL 0x811C
+#define CBCRCTRL 0x811F
+#define CBREFCLKCTRL2 0x8132
#define CBREFREFCLK_GATE_OVR_EN BIT(7)
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v10 10/11] ufs: s32n7: Add NXP S32N79 UFS host controller driver
2026-11-03 13:32 [PATCH v10 00/11] Add S32N79RDB UFS support Larisa Grigore
` (8 preceding siblings ...)
2026-11-03 13:32 ` [PATCH v10 09/11] ufs: dwc: Sort CBCRCTRL and CBREFCLKCTRL2 by offset Larisa Grigore
@ 2026-11-03 13:32 ` Larisa Grigore
2026-11-03 13:32 ` [PATCH v10 11/11] MAINTAINERS: Add NXP S32N7 UFS host controller entry Larisa Grigore
10 siblings, 0 replies; 12+ messages in thread
From: Larisa Grigore @ 2026-11-03 13:32 UTC (permalink / raw)
To: Larisa Grigore, NXP S32 Linux Team, Alim Akhtar, Bart Van Assche,
Bean Huo, Stanley Jhu, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, James E.J. Bottomley, Martin K. Petersen,
Sai Krishna Potthuri, Ajay Neeli, Manivannan Sadhasivam,
Pedro Sousa, Adrian Hunter, Can Guo, Asutosh Das
Cc: imx, linux-scsi, devicetree, linux-kernel, linux-arm-msm, clizzi,
aruizrui, eballetb, echanude, Frank Li
Add support for the UFS host controller on the NXP S32N79 SoC, built on
the Synopsys DesignWare (DWC) UFS architecture, using the UFSHCD DWC and
UFSHCD platform glue.
This controller requires S32N79-specific initialization prior to
UIC_LINKSTARTUP, including:
- programming SCM.ONE_US_TICK and HC.HCLKDIV based on the core clock,
- applying the vendor-defined M-PHY calibration sequence,
- selecting the M-PHY rate series and applying the RX rate-select
override before each switch to an HS power mode, and
- clearing the DWC BUSTHRTL low-power gating enables (LP_PGE and
LP_AH8_PGE) after each HCE enable, because power gating is not
supported on this platform.
The M-PHY supports several boot modes for loading its firmware. Only the
mode where the firmware runs from the internal ROM is supported for now.
The connection setup done after link startup follows the Synopsys
recommended sequence for this HC/M-PHY integration.
UFSHCD_QUIRK_PERFORM_LINK_STARTUP_ONCE is set because the link_startup
PRE_CHANGE hook resets, re-initializes and re-calibrates the M-PHY. A
single DME_LINKSTARTUP is sufficient on this platform, and issuing it a
second time would needlessly repeat this sequence on an already
established link.
During system suspend the firmware removes power from the whole UFS
block, so neither the device nor the link state can be retained. Set
spm_lvl to UFS_PM_LVL_5 (device powered off, link off) so the core does a
full re-initialization on resume.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
---
drivers/ufs/host/Kconfig | 12 +
drivers/ufs/host/Makefile | 1 +
drivers/ufs/host/ufs-nxp-s32n7.c | 537 +++++++++++++++++++++++++++++++
drivers/ufs/host/ufshcd-dwc.h | 10 +
drivers/ufs/host/ufshci-dwc.h | 6 +
5 files changed, 566 insertions(+)
create mode 100644 drivers/ufs/host/ufs-nxp-s32n7.c
diff --git a/drivers/ufs/host/Kconfig b/drivers/ufs/host/Kconfig
index ff170c0b6da0..e8ee7dd05f1a 100644
--- a/drivers/ufs/host/Kconfig
+++ b/drivers/ufs/host/Kconfig
@@ -168,3 +168,15 @@ config SCSI_UFS_AMD_VERSAL2
Select this if you have UFS controller on AMD Versal Gen 2 SoC.
If unsure, say N.
+
+config SCSI_UFS_S32N7
+ tristate "NXP S32N7 platform driver"
+ depends on SCSI_UFSHCD_PLATFORM && (ARCH_S32 || COMPILE_TEST)
+ help
+ This selects the S32N7 specific additions on top of the UFSHCD DWC
+ and UFSHCD platform driver. UFS host on S32N79 needs some vendor
+ specific configurations like PHY and vendor specific register accesses
+ before accessing the hardware.
+
+ Select this if you have UFS controller on an S32N7 based board.
+ If unsure, say N.
diff --git a/drivers/ufs/host/Makefile b/drivers/ufs/host/Makefile
index 7d8db67eb23c..a2f4a1d0b3ae 100644
--- a/drivers/ufs/host/Makefile
+++ b/drivers/ufs/host/Makefile
@@ -4,6 +4,7 @@ CONTEXT_ANALYSIS := y
obj-$(CONFIG_SCSI_UFS_DWC_TC_PCI) += tc-dwc-g210-pci.o ufshcd-dwc.o tc-dwc-g210.o
obj-$(CONFIG_SCSI_UFS_DWC_TC_PLATFORM) += tc-dwc-g210-pltfrm.o ufshcd-dwc.o tc-dwc-g210.o
+obj-$(CONFIG_SCSI_UFS_S32N7) += ufs-nxp-s32n7.o ufshcd-dwc.o
obj-$(CONFIG_SCSI_UFS_CDNS_PLATFORM) += cdns-pltfrm.o
obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-qcom.o
obj-$(CONFIG_SCSI_UFS_EXYNOS) += ufs-exynos.o
diff --git a/drivers/ufs/host/ufs-nxp-s32n7.c b/drivers/ufs/host/ufs-nxp-s32n7.c
new file mode 100644
index 000000000000..3fe873bcd797
--- /dev/null
+++ b/drivers/ufs/host/ufs-nxp-s32n7.c
@@ -0,0 +1,537 @@
+// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause)
+/*
+ * Copyright 2026 NXP
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+
+#include "ufshcd-pltfrm.h"
+#include "ufshcd-dwc.h"
+#include "ufshci-dwc.h"
+
+/* SCM Register Offsets. */
+#define SCM_ONE_US_TICK 0x2C
+#define SCM_STATUS 0x30
+
+/* SCM_ONE_US_TICK Register. */
+#define SCM_ONE_US_TICK_MASK GENMASK(8, 0)
+
+/* SCM_STATUS (MPHY_RAM_CONFIG_STATUS in the RM) register fields. */
+#define SCM_STATUS_PHY_RESET_MASK BIT(0)
+#define SCM_STATUS_SRAM_BYPASS_MASK BIT(1)
+#define SCM_STATUS_SRAM_INIT_DONE_MASK BIT(16)
+
+#define SCM_STATUS_PHY_RESET(val) \
+ FIELD_PREP(SCM_STATUS_PHY_RESET_MASK, val)
+
+#define SCM_STATUS_SRAM_BYPASS(val) \
+ FIELD_PREP(SCM_STATUS_SRAM_BYPASS_MASK, val)
+
+/* Driver-defined upper bound for the M-PHY SRAM init to complete: 3 s. */
+#define CFG_MPHY_INIT_TIMEOUT_VALUE_US 3000000
+/* Driver-defined poll interval while waiting for SRAM_INIT_DONE: 1 ms. */
+#define CFG_MPHY_INIT_POLL_INTERVAL_US 1000
+
+/* Clock validation limits. */
+#define MAX_VALID_ONE_US_TICK SCM_ONE_US_TICK_MASK
+
+/* CPort definitions */
+#define CPORT_0 0
+
+/* Driver-defined upper bound for completing a Hibern8 transition: 1 s. */
+#define HBRN8_POLL_TOUT_MS 1000
+
+/* The M-PHY sequences below program exactly two lanes per direction. */
+#define S32N7_NUM_LANES 2
+
+/**
+ * struct s32n7_phy_reg - PHY CR (config-register) write entry
+ * @reg: Register offset/address
+ * @val: Value to write
+ */
+struct s32n7_phy_reg {
+ u32 reg;
+ u32 val;
+};
+
+/**
+ * struct s32n7_soc_data - SoC-specific configuration data
+ * @vops: UFS host controller variant operations
+ * @reset_config: MPHY reset DME attributes (programmed while PHY held in reset)
+ * @reset_config_count: number of entries in @reset_config
+ * @calib_mphy_disabled: CR calibration writes performed while the MPHY is disabled
+ * @calib_mphy_disabled_count: number of entries in @calib_mphy_disabled
+ * @calib_mphy_enabled: CR calibration writes performed while the MPHY is enabled
+ * @calib_mphy_enabled_count: number of entries in @calib_mphy_enabled
+ */
+struct s32n7_soc_data {
+ const struct ufs_hba_variant_ops *vops;
+ const struct ufshcd_dme_attr_val *reset_config;
+ size_t reset_config_count;
+ const struct s32n7_phy_reg *calib_mphy_disabled;
+ size_t calib_mphy_disabled_count;
+ const struct s32n7_phy_reg *calib_mphy_enabled;
+ size_t calib_mphy_enabled_count;
+};
+
+/**
+ * struct s32n7_ufs - S32N7 UFS host controller data
+ * @reg_scm: SCM register base address
+ * @soc_data: SoC-specific configuration data
+ */
+struct s32n7_ufs {
+ void __iomem *reg_scm;
+ const struct s32n7_soc_data *soc_data;
+};
+
+/**
+ * ufs_s32n7_phy_write_sequence - Write a sequence of PHY registers
+ * @hba: UFS host controller instance
+ * @cfg: Array of register configurations
+ * @count: Number of entries in the array
+ *
+ * Return: 0 on success, error code on failure
+ */
+static int ufs_s32n7_phy_write_sequence(struct ufs_hba *hba,
+ const struct s32n7_phy_reg *cfg,
+ size_t count)
+{
+ int ret;
+ size_t i;
+
+ for (i = 0; i < count; i++) {
+ ret = ufshcd_dwc_phy_reg_write(hba, cfg[i].reg, cfg[i].val);
+ if (ret) {
+ dev_err(hba->dev,
+ "Failed to write PHY reg 0x%x = 0x%x (step %zu)\n",
+ cfg[i].reg, cfg[i].val, i);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int ufs_s32n7_configure_clocks(struct ufs_hba *hba)
+{
+ struct s32n7_ufs *ufs = ufshcd_get_variant(hba);
+ struct device *dev = hba->dev;
+ struct ufs_clk_info *clki;
+ unsigned long clk_rate = 0;
+ unsigned long one_us_tick;
+
+ /*
+ * The core reference clock is managed by the UFS core (it is listed in
+ * the DT via clock-names/operating-points-v2 and added to
+ * hba->clk_list_head). Read its rate from there to derive
+ * ONE_US_TICK/HCLKDIV.
+ */
+ list_for_each_entry(clki, &hba->clk_list_head, list) {
+ if (!IS_ERR_OR_NULL(clki->clk) &&
+ !strcmp(clki->name, "core")) {
+ clk_rate = clk_get_rate(clki->clk);
+ break;
+ }
+ }
+
+ if (!clk_rate) {
+ dev_err(dev, "Failed to get valid clock rate.\n");
+ return -EINVAL;
+ }
+
+ one_us_tick = clk_rate / USEC_PER_SEC;
+ if (one_us_tick == 0 || one_us_tick > MAX_VALID_ONE_US_TICK) {
+ dev_err(dev, "Invalid one_us_tick value: %lu (clk_rate: %lu Hz).\n",
+ one_us_tick, clk_rate);
+ return -EINVAL;
+ }
+
+ dev_dbg(dev, "Core clock rate: %lu Hz, one_us_tick = %lu.\n",
+ clk_rate, one_us_tick);
+
+ /*
+ * Configure the micro-second tick rate generator based on core
+ * clock rate.
+ */
+ writel(one_us_tick, ufs->reg_scm + SCM_ONE_US_TICK);
+ ufshcd_dwc_program_clk_div(hba, one_us_tick);
+
+ return 0;
+}
+
+static int ufs_s32n7_hce_enable_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ u32 reg;
+
+ if (status != POST_CHANGE)
+ return 0;
+
+ /*
+ * Clear the DWC low-power gating (BUSTHRTL) so a doorbell write can
+ * wake the link out of hibernate. LP_PGE (generic low-power) and
+ * LP_AH8_PGE (Auto-Hibern8) both reset to 1.
+ */
+ reg = ufshcd_readl(hba, DWC_UFS_REG_BUSTHRTL);
+ reg &= ~(DWC_UFS_BUSTHRTL_LP_PGE | DWC_UFS_BUSTHRTL_LP_AH8_PGE);
+ ufshcd_writel(hba, reg, DWC_UFS_REG_BUSTHRTL);
+
+ return 0;
+}
+
+static int ufs_s32n7_phy_initial_calib(struct ufs_hba *hba)
+{
+ struct s32n7_ufs *ufs = ufshcd_get_variant(hba);
+ int ret;
+
+ /* Calibrations that must run while the MPHY is disabled. */
+ ret = ufs_s32n7_phy_write_sequence(hba, ufs->soc_data->calib_mphy_disabled,
+ ufs->soc_data->calib_mphy_disabled_count);
+ if (ret)
+ return ret;
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VS_MPHYCFGUPDT, 0), 0x1);
+ if (ret)
+ return ret;
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VS_MPHYDISABLE, 0), 0x0);
+ if (ret)
+ return ret;
+
+ /* Calibrations that must run while the MPHY is enabled. */
+ ret = ufs_s32n7_phy_write_sequence(hba, ufs->soc_data->calib_mphy_enabled,
+ ufs->soc_data->calib_mphy_enabled_count);
+ if (ret)
+ return ret;
+
+ return ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VS_MPHYCFGUPDT, 0), 0x1);
+}
+
+static int ufs_s32n7_link_startup_pre_change(struct ufs_hba *hba)
+{
+ struct s32n7_ufs *ufs = ufshcd_get_variant(hba);
+ struct device *dev = hba->dev;
+ int ret;
+ u32 reg;
+
+ ret = ufs_s32n7_configure_clocks(hba);
+ if (ret)
+ return ret;
+
+ /* Reset SCM_STATUS to its default value; keep the MPHY in reset. */
+ writel(SCM_STATUS_PHY_RESET(1), ufs->reg_scm + SCM_STATUS);
+
+ /*
+ * The M-PHY firmware runs from the internal ROM:
+ * SCM_STATUS.SRAM_BYPASS = 1
+ * SCM_STATUS.SRAM_EXT_LD_DONE = 0
+ */
+ writel(SCM_STATUS_PHY_RESET(1) | SCM_STATUS_SRAM_BYPASS(1),
+ ufs->reg_scm + SCM_STATUS);
+
+ /* MPHY reset configuration: vendor attributes set while PHY in reset. */
+ ret = ufshcd_dwc_dme_set_attrs(hba, ufs->soc_data->reset_config,
+ ufs->soc_data->reset_config_count);
+ if (ret)
+ return ret;
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VS_MPHYCFGUPDT, 0), 0x1);
+ if (ret)
+ return ret;
+
+ /* Release PHY_RESET. */
+ writel(readl(ufs->reg_scm + SCM_STATUS) & ~SCM_STATUS_PHY_RESET_MASK,
+ ufs->reg_scm + SCM_STATUS);
+
+ /* Wait until SRAM_INIT_DONE = 1. */
+ ret = readl_poll_timeout(ufs->reg_scm + SCM_STATUS, reg,
+ reg & SCM_STATUS_SRAM_INIT_DONE_MASK,
+ CFG_MPHY_INIT_POLL_INTERVAL_US,
+ CFG_MPHY_INIT_TIMEOUT_VALUE_US);
+ if (ret) {
+ dev_err(dev, "UFS MPHY init not done!\n");
+ return ret;
+ }
+
+ /* Start of initial calibration */
+ ret = ufs_s32n7_phy_initial_calib(hba);
+ if (ret) {
+ dev_err(dev, "UFS MPHY initial calibration failed!\n");
+ return ret;
+ }
+
+ /* End of Gear1 settings */
+
+ return ufshcd_dme_check_tx_hibern8(hba, hba->lanes_per_direction,
+ HBRN8_POLL_TOUT_MS);
+}
+
+static int ufs_s32n7_link_startup_post_change(struct ufs_hba *hba)
+{
+ static const struct ufshcd_dme_attr_val cport_setup[] = {
+ { UIC_ARG_MIB_SEL(T_CONNECTIONSTATE, CPORT_0),
+ CPORT_IDLE, DME_LOCAL },
+ { UIC_ARG_MIB_SEL(T_CPORTFLAGS, CPORT_0),
+ CPORT_DEF_FLAGS, DME_LOCAL },
+ { UIC_ARG_MIB_SEL(T_CONNECTIONSTATE, CPORT_0),
+ CPORT_CONNECTED, DME_LOCAL },
+ };
+ u32 data = 0;
+ int ret;
+
+ /* Network layer: set Local DeviceID. */
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(N_DEVICEID, 0), 0x0);
+ if (ret)
+ return ret;
+
+ /* Set Connection State to IDLE (it allows CPort Attributes to be set). */
+ ret = ufshcd_dwc_dme_set_attrs(hba, cport_setup, ARRAY_SIZE(cport_setup));
+ if (ret)
+ return ret;
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(T_CONNECTIONSTATE, CPORT_0), &data);
+ if (ret)
+ return ret;
+
+ if (data != CPORT_CONNECTED) {
+ dev_err(hba->dev, "CPort 0 not connected (state %u)\n", data);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int ufs_s32n7_link_startup_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ int err;
+
+ if (status == PRE_CHANGE)
+ return ufs_s32n7_link_startup_pre_change(hba);
+
+ /* POST_CHANGE */
+ err = ufshcd_dwc_link_is_up(hba);
+ if (err) {
+ dev_err(hba->dev, "Link is not up.\n");
+ return err;
+ }
+
+ return ufs_s32n7_link_startup_post_change(hba);
+}
+
+static int ufs_s32n7_pwr_change_pre_change(struct ufs_hba *hba,
+ const struct ufs_pa_layer_attr *pwr)
+{
+ /*
+ * RX rate-select override sequence: assert the override, then deassert
+ * the value, then drop the override enable so normal operation resumes.
+ */
+ static const struct s32n7_phy_reg pwr_change_phy_regs[] = {
+ { RX_OVRD_IN_1(0), 0xc },
+ { RX_OVRD_IN_1(1), 0xc },
+ { RX_OVRD_IN_1(0), 0x8 },
+ { RX_OVRD_IN_1(1), 0x8 },
+ { RX_OVRD_IN_1(0), 0x0 },
+ { RX_OVRD_IN_1(1), 0x0 },
+ };
+ u32 rate;
+ int ret;
+
+ /* Rate change only applies to the HS power modes. */
+ if (!ufshcd_is_hs_mode(pwr))
+ return 0;
+
+ /*
+ * Select the M-PHY rate (rate series) that matches the mode the core
+ * has negotiated with the device.
+ */
+ rate = (pwr->hs_rate == PA_HS_MODE_B) ? CB_RATE_B : CB_RATE_A;
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(CBRATESEL, 0), rate);
+ if (ret)
+ return ret;
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VS_MPHYCFGUPDT, 0), 0x1);
+ if (ret)
+ return ret;
+
+ /* Apply the RX rate-select override now that the target rate is set. */
+ return ufs_s32n7_phy_write_sequence(hba, pwr_change_phy_regs,
+ ARRAY_SIZE(pwr_change_phy_regs));
+}
+
+static int ufs_s32n7_pwr_change_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status,
+ struct ufs_pa_layer_attr *pwr)
+{
+ int err = 0;
+
+ if (status == PRE_CHANGE) {
+ err = ufs_s32n7_pwr_change_pre_change(hba, pwr);
+ if (err)
+ dev_err(hba->dev, "Power mode pre-change failed (%d).\n",
+ err);
+ }
+
+ return err;
+}
+
+static int ufs_s32n7_init(struct ufs_hba *hba)
+{
+ struct device *dev = hba->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct s32n7_ufs *ufs;
+
+ ufs = devm_kzalloc(dev, sizeof(*ufs), GFP_KERNEL);
+ if (!ufs)
+ return -ENOMEM;
+
+ ufs->soc_data = device_get_match_data(dev);
+ if (!ufs->soc_data)
+ return -ENODEV;
+
+ if (hba->lanes_per_direction != S32N7_NUM_LANES)
+ return dev_err_probe(dev, -EINVAL,
+ "unsupported lanes-per-direction %u (expected %u)\n",
+ hba->lanes_per_direction, S32N7_NUM_LANES);
+
+ hba->quirks |= UFSHCD_QUIRK_PERFORM_LINK_STARTUP_ONCE;
+ hba->spm_lvl = UFS_PM_LVL_5;
+
+ ufs->reg_scm = devm_platform_ioremap_resource_byname(pdev, "scm");
+ if (IS_ERR(ufs->reg_scm))
+ return dev_err_probe(dev, PTR_ERR(ufs->reg_scm),
+ "ioremap failed for SCM registers.\n");
+
+ ufshcd_set_variant(hba, ufs);
+
+ return 0;
+}
+
+static const struct ufs_hba_variant_ops ufs_hba_s32n79_vops = {
+ .name = "s32n79",
+ .init = ufs_s32n7_init,
+ .link_startup_notify = ufs_s32n7_link_startup_notify,
+ .hce_enable_notify = ufs_s32n7_hce_enable_notify,
+ .pwr_change_notify = ufs_s32n7_pwr_change_notify,
+};
+
+/*
+ * MPHY reset configuration: vendor DME attributes programmed while the PHY is
+ * held in reset. Programs the reference-clock, RX squelch, RX Rhold and
+ * CR-parallel control attributes.
+ */
+static const struct ufshcd_dme_attr_val s32n79_reset_config[] = {
+ /*
+ * Disable reference clock gating support and override the UniPro and
+ * application reference clock enable inputs to 0.
+ */
+ { UIC_ARG_MIB_SEL(CBREFCLKCTRL2, 0), 0x8A, DME_LOCAL },
+ /* RX squelch control. */
+ { UIC_ARG_MIB_SEL(RXSQCONTROL, SELIND_LN0_RX), 0x1, DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RXSQCONTROL, SELIND_LN1_RX), 0x1, DME_LOCAL },
+ /* RX Rhold control option. */
+ { UIC_ARG_MIB_SEL(RXRHOLDCTRLOPT, SELIND_LN0_RX), 0x2, DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RXRHOLDCTRLOPT, SELIND_LN1_RX), 0x2, DME_LOCAL },
+ /* CR parallel selection. */
+ { UIC_ARG_MIB_SEL(CBCRCTRL, 0), 0x1, DME_LOCAL },
+};
+
+/* Calibrations that must run while the MPHY is disabled. */
+static const struct s32n7_phy_reg s32n79_calib_mphy_disabled[] = {
+ /* Bypass RX AFE calibration. */
+ { FAST_FLAGS(0), 0x4 },
+ { FAST_FLAGS(1), 0x4 },
+};
+
+/* Calibrations that must run while the MPHY is enabled. */
+static const struct s32n7_phy_reg s32n79_calib_mphy_enabled[] = {
+ /* Write ATT offset compensation code to internal DAC. */
+ { RX_DAC_CTRL_OVRD(0), 0x1 },
+ { RX_DAC_CTRL_OVRD(1), 0x1 },
+ { RX_DAC_CTRL(0), 0x8E },
+ { RX_DAC_CTRL(1), 0x91 },
+ { RX_DAC_CTRL_SEL(0), 0x1 },
+ { RX_DAC_CTRL_SEL(1), 0x1 },
+ { RX_DAC_CTRL_EN(0), 0x1 },
+ { RX_DAC_CTRL_EN(1), 0x1 },
+ { RX_DAC_CTRL_OVRD(0), 0x0 },
+ { RX_DAC_CTRL_OVRD(1), 0x0 },
+ /* Write CTLE offset compensation code to internal DAC. */
+ { RX_DAC_CTRL_OVRD(0), 0x1 },
+ { RX_DAC_CTRL_OVRD(1), 0x1 },
+ { RX_DAC_CTRL(0), 0x71 },
+ { RX_DAC_CTRL(1), 0x7F },
+ { RX_DAC_CTRL_SEL(0), 0x2 },
+ { RX_DAC_CTRL_SEL(1), 0x2 },
+ { RX_DAC_CTRL_EN(0), 0x1 },
+ { RX_DAC_CTRL_EN(1), 0x1 },
+ { RX_DAC_CTRL_OVRD(0), 0x0 },
+ { RX_DAC_CTRL_OVRD(1), 0x0 },
+ /* FW Calibration configuration. */
+ { FW_CALIB_CCFG(0), 0x100 },
+ { FW_CALIB_CCFG(1), 0x100 },
+};
+
+static const struct s32n7_soc_data s32n79_soc_data = {
+ .vops = &ufs_hba_s32n79_vops,
+ .reset_config = s32n79_reset_config,
+ .reset_config_count = ARRAY_SIZE(s32n79_reset_config),
+ .calib_mphy_disabled = s32n79_calib_mphy_disabled,
+ .calib_mphy_disabled_count = ARRAY_SIZE(s32n79_calib_mphy_disabled),
+ .calib_mphy_enabled = s32n79_calib_mphy_enabled,
+ .calib_mphy_enabled_count = ARRAY_SIZE(s32n79_calib_mphy_enabled),
+};
+
+static const struct of_device_id ufs_s32n7_match[] = {
+ {
+ .compatible = "nxp,s32n79-ufshc",
+ .data = &s32n79_soc_data,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ufs_s32n7_match);
+
+static int ufs_s32n7_probe(struct platform_device *pdev)
+{
+ const struct s32n7_soc_data *soc_data;
+
+ soc_data = device_get_match_data(&pdev->dev);
+ if (!soc_data)
+ return -ENODEV;
+
+ return ufshcd_pltfrm_init(pdev, soc_data->vops);
+}
+
+static const struct dev_pm_ops ufs_s32n7_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume)
+ RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
+ .prepare = pm_sleep_ptr(ufshcd_suspend_prepare),
+ .complete = pm_sleep_ptr(ufshcd_resume_complete),
+};
+
+static struct platform_driver ufs_s32n7_driver = {
+ .probe = ufs_s32n7_probe,
+ .remove = ufshcd_pltfrm_remove,
+ .driver = {
+ .name = "ufs-s32n7",
+ .pm = pm_ptr(&ufs_s32n7_pm_ops),
+ .of_match_table = ufs_s32n7_match,
+ },
+};
+
+module_platform_driver(ufs_s32n7_driver);
+
+MODULE_AUTHOR("Larisa Grigore <larisa.grigore@oss.nxp.com>");
+MODULE_DESCRIPTION("NXP S32N7 UFS Host Controller platform driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/ufs/host/ufshcd-dwc.h b/drivers/ufs/host/ufshcd-dwc.h
index 1abf2b1deac4..d62cd4c085ba 100644
--- a/drivers/ufs/host/ufshcd-dwc.h
+++ b/drivers/ufs/host/ufshcd-dwc.h
@@ -13,6 +13,8 @@
#include <ufs/ufshcd.h>
/* RMMI Attributes */
+#define RXSQCONTROL 0x8009
+#define RXRHOLDCTRLOPT 0x8013
#define CBC10DIRECTCONF2 0x810E
#define CBRATESEL 0x8114
#define CBCREGADDRLSB 0x8116
@@ -27,7 +29,15 @@
#define CBREFREFCLK_GATE_OVR_EN BIT(7)
+/* CBRATESEL: 0 - rate A, 1 - rate B. */
+#define CB_RATE_A 0x0
+#define CB_RATE_B 0x1
+
/* M-PHY registers */
+#define RX_DAC_CTRL(n) (0x10AF + ((n) * 0x100))
+#define RX_DAC_CTRL_OVRD(n) (0x10B0 + ((n) * 0x100))
+#define RX_DAC_CTRL_SEL(n) (0x10B1 + ((n) * 0x100))
+#define RX_DAC_CTRL_EN(n) (0x10B8 + ((n) * 0x100))
#define RX_OVRD_IN_1(n) (0x3006 + ((n) * 0x100))
#define RX_PCS_OUT(n) (0x300F + ((n) * 0x100))
#define FAST_FLAGS(n) (0x401C + ((n) * 0x100))
diff --git a/drivers/ufs/host/ufshci-dwc.h b/drivers/ufs/host/ufshci-dwc.h
index 6c290e272106..c66f33391b65 100644
--- a/drivers/ufs/host/ufshci-dwc.h
+++ b/drivers/ufs/host/ufshci-dwc.h
@@ -12,9 +12,15 @@
/* DWC HC UFSHCI specific Registers */
enum dwc_specific_registers {
+ DWC_UFS_REG_BUSTHRTL = 0xC0,
DWC_UFS_REG_HCLKDIV = 0xFC,
};
+/* Low-Power Power Gating Enable. */
+#define DWC_UFS_BUSTHRTL_LP_PGE BIT(16)
+/* Low Power - AH8 Power Gating Enable. */
+#define DWC_UFS_BUSTHRTL_LP_AH8_PGE BIT(17)
+
/* Clock Divider Values: Hex equivalent of frequency in MHz */
enum clk_div_values {
DWC_UFS_REG_HCLKDIV_DIV_62_5 = 0x3e,
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v10 11/11] MAINTAINERS: Add NXP S32N7 UFS host controller entry
2026-11-03 13:32 [PATCH v10 00/11] Add S32N79RDB UFS support Larisa Grigore
` (9 preceding siblings ...)
2026-11-03 13:32 ` [PATCH v10 10/11] ufs: s32n7: Add NXP S32N79 UFS host controller driver Larisa Grigore
@ 2026-11-03 13:32 ` Larisa Grigore
10 siblings, 0 replies; 12+ messages in thread
From: Larisa Grigore @ 2026-11-03 13:32 UTC (permalink / raw)
To: Larisa Grigore, NXP S32 Linux Team, Alim Akhtar, Bart Van Assche,
Bean Huo, Stanley Jhu, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, James E.J. Bottomley, Martin K. Petersen,
Sai Krishna Potthuri, Ajay Neeli, Manivannan Sadhasivam,
Pedro Sousa, Adrian Hunter, Can Guo, Asutosh Das
Cc: imx, linux-scsi, devicetree, linux-kernel, linux-arm-msm, clizzi,
aruizrui, eballetb, echanude
Add maintainer for S32N7 UFS host controller driver.
Signed-off-by: Larisa Grigore <larisa.grigore@oss.nxp.com>
---
MAINTAINERS | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 93abbb315065..9ad14245acbd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -28801,6 +28801,15 @@ L: linux-scsi@vger.kernel.org
S: Maintained
F: drivers/ufs/host/ufs-renesas.c
+UNIVERSAL FLASH STORAGE HOST CONTROLLER DRIVER S32N7 HOOKS
+M: Larisa Grigore <larisa.grigore@oss.nxp.com>
+R: NXP S32 Linux Team <s32@nxp.com>
+L: imx@lists.linux.dev
+L: linux-scsi@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/ufs/nxp,s32n79-ufshc.yaml
+F: drivers/ufs/host/ufs-nxp-s32n7.c
+
UNIWILL LAPTOP DRIVER
M: Armin Wolf <W_Armin@gmx.de>
L: platform-driver-x86@vger.kernel.org
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-11-03 13:33 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 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 13:32 ` [PATCH v10 04/11] ufs: unipro: Add TX/RX FSM state attributes Larisa Grigore
2026-11-03 13:32 ` [PATCH v10 05/11] ufs: core: Add common Hibern8 TX FSM polling helper Larisa Grigore
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 13:32 ` [PATCH v10 11/11] MAINTAINERS: Add NXP S32N7 UFS host controller entry Larisa Grigore
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®