* [PATCH v2 01/11] scsi: ufs: core: Introduce a new ufshcd vops negotiate_pwr_mode()
2026-03-04 13:53 [PATCH v2 00/11] scsi: ufs: Add TX Equalization support for UFS 5.0 Can Guo
@ 2026-03-04 13:53 ` Can Guo
2026-03-05 12:53 ` Krzysztof Kozlowski
2026-03-05 21:03 ` Bean Huo
2026-03-04 13:53 ` [PATCH v2 02/11] scsi: ufs: core: Pass force_pmc to ufshcd_config_pwr_mode() as a parameter Can Guo
` (10 subsequent siblings)
11 siblings, 2 replies; 39+ messages in thread
From: Can Guo @ 2026-03-04 13:53 UTC (permalink / raw)
To: avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Can Guo, Alim Akhtar, James E.J. Bottomley,
Sai Krishna Potthuri, Ajay Neeli, Peter Griffin,
Krzysztof Kozlowski, Peter Wang, Chaotian Jing, Stanley Jhu,
Manivannan Sadhasivam, Orson Zhai, Baolin Wang, Chunyan Zhang,
Matthias Brugger, AngeloGioacchino Del Regno, Bao D. Nguyen,
Adrian Hunter, Archana Patni, open list,
open list:UNIVERSAL FLASH STORAGE HOST CONTROLLER DRIVER...,
moderated list:ARM/SAMSUNG S3C, S5P AND EXYNOS ARM ARCHITECTURES,
moderated list:UNIVERSAL FLASH STORAGE HOST CONTROLLER DRIVER...,
open list:ARM/QUALCOMM MAILING LIST
Most vendor specific implemenations of vops pwr_change_notify(PRE_CHANGE)
are fulfilling two things at once:
- Vendor specific target power mode negotiation
- Vendor specific power mode change preparation
When TX Equalization is added into consideration, before power mode change
to a target power mode, TX Equalization Training (EQTR) needs be done for
that target power mode. In addition, UFSHCI spec requires to start TX EQTR
from HS-G1 (the most reliable High Speed Gear).
Adding TX EQTR before pwr_change_notify(PRE_CHANGE) is not applicable
because we don't know the negotiated power mode yet.
Adding TX EQTR post pwr_change_notify(PRE_CHANGE) is inappropriate
because pwr_change_notify(PRE_CHANGE) has finished preparation for a power
mode change to negotiated power mode, yet we are changing power mode to
HS-G1 for TX EQTR.
Add a new vops negotiate_pwr_mode() so that vendor specific power mode
negotiation can be fulfilled in its vendor specific implementations.
Later on, TX EQTR can be added post vops negotiate_pwr_mode() and before
vops pwr_change_notify(PRE_CHANGE).
Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
---
drivers/ufs/core/ufshcd-priv.h | 14 +++++++--
drivers/ufs/core/ufshcd.c | 35 ++++++++++++++---------
drivers/ufs/host/ufs-amd-versal2.c | 13 +++++++--
drivers/ufs/host/ufs-exynos.c | 44 +++++++++++++++++-----------
drivers/ufs/host/ufs-hisi.c | 32 ++++++++++++++-------
drivers/ufs/host/ufs-mediatek.c | 46 +++++++++++++++++-------------
drivers/ufs/host/ufs-qcom.c | 34 +++++++++++++++-------
drivers/ufs/host/ufs-sprd.c | 13 +++++++--
drivers/ufs/host/ufshcd-pci.c | 16 ++++++++---
include/ufs/ufshcd.h | 17 ++++++-----
10 files changed, 173 insertions(+), 91 deletions(-)
diff --git a/drivers/ufs/core/ufshcd-priv.h b/drivers/ufs/core/ufshcd-priv.h
index 7d6d19361af9..3b6958d9297a 100644
--- a/drivers/ufs/core/ufshcd-priv.h
+++ b/drivers/ufs/core/ufshcd-priv.h
@@ -167,14 +167,24 @@ static inline int ufshcd_vops_link_startup_notify(struct ufs_hba *hba,
return 0;
}
+static inline int ufshcd_vops_negotiate_pwr_mode(struct ufs_hba *hba,
+ const struct ufs_pa_layer_attr *dev_max_params,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ if (hba->vops && hba->vops->negotiate_pwr_mode)
+ return hba->vops->negotiate_pwr_mode(hba, dev_max_params,
+ dev_req_params);
+
+ return -ENOTSUPP;
+}
+
static inline int ufshcd_vops_pwr_change_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status,
- const struct ufs_pa_layer_attr *dev_max_params,
struct ufs_pa_layer_attr *dev_req_params)
{
if (hba->vops && hba->vops->pwr_change_notify)
return hba->vops->pwr_change_notify(hba, status,
- dev_max_params, dev_req_params);
+ dev_req_params);
return -ENOTSUPP;
}
diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
index 8349fe2090db..c6b9c58fece3 100644
--- a/drivers/ufs/core/ufshcd.c
+++ b/drivers/ufs/core/ufshcd.c
@@ -335,8 +335,6 @@ static void ufshcd_suspend_clkscaling(struct ufs_hba *hba);
static int ufshcd_scale_clks(struct ufs_hba *hba, unsigned long freq,
bool scale_up);
static irqreturn_t ufshcd_intr(int irq, void *__hba);
-static int ufshcd_change_power_mode(struct ufs_hba *hba,
- struct ufs_pa_layer_attr *pwr_mode);
static int ufshcd_setup_hba_vreg(struct ufs_hba *hba, bool on);
static int ufshcd_setup_vreg(struct ufs_hba *hba, bool on);
static inline int ufshcd_config_vreg_hpm(struct ufs_hba *hba,
@@ -4662,8 +4660,8 @@ static int ufshcd_get_max_pwr_mode(struct ufs_hba *hba)
return 0;
}
-static int ufshcd_change_power_mode(struct ufs_hba *hba,
- struct ufs_pa_layer_attr *pwr_mode)
+static int ufshcd_dme_change_power_mode(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode)
{
int ret;
@@ -4747,6 +4745,22 @@ static int ufshcd_change_power_mode(struct ufs_hba *hba,
return ret;
}
+int ufshcd_change_power_mode(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode)
+{
+ int ret;
+
+ ufshcd_vops_pwr_change_notify(hba, PRE_CHANGE, pwr_mode);
+
+ ret = ufshcd_dme_change_power_mode(hba, pwr_mode);
+
+ if (!ret)
+ ufshcd_vops_pwr_change_notify(hba, POST_CHANGE, pwr_mode);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ufshcd_change_power_mode);
+
/**
* ufshcd_config_pwr_mode - configure a new power mode
* @hba: per-adapter instance
@@ -4760,19 +4774,12 @@ int ufshcd_config_pwr_mode(struct ufs_hba *hba,
struct ufs_pa_layer_attr final_params = { 0 };
int ret;
- ret = ufshcd_vops_pwr_change_notify(hba, PRE_CHANGE,
- desired_pwr_mode, &final_params);
-
+ ret = ufshcd_vops_negotiate_pwr_mode(hba, desired_pwr_mode,
+ &final_params);
if (ret)
memcpy(&final_params, desired_pwr_mode, sizeof(final_params));
- ret = ufshcd_change_power_mode(hba, &final_params);
-
- if (!ret)
- ufshcd_vops_pwr_change_notify(hba, POST_CHANGE, NULL,
- &final_params);
-
- return ret;
+ return ufshcd_change_power_mode(hba, &final_params);
}
EXPORT_SYMBOL_GPL(ufshcd_config_pwr_mode);
diff --git a/drivers/ufs/host/ufs-amd-versal2.c b/drivers/ufs/host/ufs-amd-versal2.c
index 40543db621a1..aa1ef4d9d6e8 100644
--- a/drivers/ufs/host/ufs-amd-versal2.c
+++ b/drivers/ufs/host/ufs-amd-versal2.c
@@ -442,8 +442,16 @@ static int ufs_versal2_phy_ratesel(struct ufs_hba *hba, u32 activelanes, u32 rx_
return 0;
}
+static int ufs_versal2_negotiate_pwr_mode(struct ufs_hba *hba,
+ const struct ufs_pa_layer_attr *dev_max_params,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ memcpy(dev_req_params, dev_max_params, sizeof(struct ufs_pa_layer_attr));
+
+ return 0;
+}
+
static int ufs_versal2_pwr_change_notify(struct ufs_hba *hba, enum ufs_notify_change_status status,
- const struct ufs_pa_layer_attr *dev_max_params,
struct ufs_pa_layer_attr *dev_req_params)
{
struct ufs_versal2_host *host = ufshcd_get_variant(hba);
@@ -451,8 +459,6 @@ static int ufs_versal2_pwr_change_notify(struct ufs_hba *hba, enum ufs_notify_ch
int ret = 0;
if (status == PRE_CHANGE) {
- memcpy(dev_req_params, dev_max_params, sizeof(struct ufs_pa_layer_attr));
-
/* If it is not a calibrated part, switch PWRMODE to SLOW_MODE */
if (!host->attcompval0 && !host->attcompval1 && !host->ctlecompval0 &&
!host->ctlecompval1) {
@@ -509,6 +515,7 @@ static struct ufs_hba_variant_ops ufs_versal2_hba_vops = {
.init = ufs_versal2_init,
.link_startup_notify = ufs_versal2_link_startup_notify,
.hce_enable_notify = ufs_versal2_hce_enable_notify,
+ .negotiate_pwr_mode = ufs_versal2_negotiate_pwr_mode,
.pwr_change_notify = ufs_versal2_pwr_change_notify,
};
diff --git a/drivers/ufs/host/ufs-exynos.c b/drivers/ufs/host/ufs-exynos.c
index 76fee3a79c77..ae179339f9c3 100644
--- a/drivers/ufs/host/ufs-exynos.c
+++ b/drivers/ufs/host/ufs-exynos.c
@@ -818,12 +818,10 @@ static u32 exynos_ufs_get_hs_gear(struct ufs_hba *hba)
}
static int exynos_ufs_pre_pwr_mode(struct ufs_hba *hba,
- const struct ufs_pa_layer_attr *dev_max_params,
struct ufs_pa_layer_attr *dev_req_params)
{
struct exynos_ufs *ufs = ufshcd_get_variant(hba);
struct phy *generic_phy = ufs->phy;
- struct ufs_host_params host_params;
int ret;
if (!dev_req_params) {
@@ -832,18 +830,6 @@ static int exynos_ufs_pre_pwr_mode(struct ufs_hba *hba,
goto out;
}
- ufshcd_init_host_params(&host_params);
-
- /* This driver only support symmetric gear setting e.g. hs_tx_gear == hs_rx_gear */
- host_params.hs_tx_gear = exynos_ufs_get_hs_gear(hba);
- host_params.hs_rx_gear = exynos_ufs_get_hs_gear(hba);
-
- ret = ufshcd_negotiate_pwr_params(&host_params, dev_max_params, dev_req_params);
- if (ret) {
- pr_err("%s: failed to determine capabilities\n", __func__);
- goto out;
- }
-
if (ufs->drv_data->pre_pwr_change)
ufs->drv_data->pre_pwr_change(ufs, dev_req_params);
@@ -1677,17 +1663,40 @@ static int exynos_ufs_link_startup_notify(struct ufs_hba *hba,
return ret;
}
+static int exynos_ufs_negotiate_pwr_mode(struct ufs_hba *hba,
+ const struct ufs_pa_layer_attr *dev_max_params,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ struct ufs_host_params host_params;
+ int ret;
+
+ if (!dev_req_params) {
+ pr_err("%s: incoming dev_req_params is NULL\n", __func__);
+ return -EINVAL;
+ }
+
+ ufshcd_init_host_params(&host_params);
+
+ /* This driver only support symmetric gear setting e.g. hs_tx_gear == hs_rx_gear */
+ host_params.hs_tx_gear = exynos_ufs_get_hs_gear(hba);
+ host_params.hs_rx_gear = exynos_ufs_get_hs_gear(hba);
+
+ ret = ufshcd_negotiate_pwr_params(&host_params, dev_max_params, dev_req_params);
+ if (ret)
+ pr_err("%s: failed to determine capabilities\n", __func__);
+
+ return ret;
+}
+
static int exynos_ufs_pwr_change_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status,
- const struct ufs_pa_layer_attr *dev_max_params,
struct ufs_pa_layer_attr *dev_req_params)
{
int ret = 0;
switch (status) {
case PRE_CHANGE:
- ret = exynos_ufs_pre_pwr_mode(hba, dev_max_params,
- dev_req_params);
+ ret = exynos_ufs_pre_pwr_mode(hba, dev_req_params);
break;
case POST_CHANGE:
ret = exynos_ufs_post_pwr_mode(hba, dev_req_params);
@@ -2015,6 +2024,7 @@ static const struct ufs_hba_variant_ops ufs_hba_exynos_ops = {
.exit = exynos_ufs_exit,
.hce_enable_notify = exynos_ufs_hce_enable_notify,
.link_startup_notify = exynos_ufs_link_startup_notify,
+ .negotiate_pwr_mode = exynos_ufs_negotiate_pwr_mode,
.pwr_change_notify = exynos_ufs_pwr_change_notify,
.setup_clocks = exynos_ufs_setup_clocks,
.setup_xfer_req = exynos_ufs_specify_nexus_t_xfer_req,
diff --git a/drivers/ufs/host/ufs-hisi.c b/drivers/ufs/host/ufs-hisi.c
index 6f2e6bf31225..f7301f94502a 100644
--- a/drivers/ufs/host/ufs-hisi.c
+++ b/drivers/ufs/host/ufs-hisi.c
@@ -298,6 +298,26 @@ static void ufs_hisi_set_dev_cap(struct ufs_host_params *host_params)
ufshcd_init_host_params(host_params);
}
+static int ufs_hisi_negotiate_pwr_mode(struct ufs_hba *hba,
+ const struct ufs_pa_layer_attr *dev_max_params,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ struct ufs_host_params host_params;
+ int ret;
+
+ if (!dev_req_params) {
+ dev_err(hba->dev, "%s: incoming dev_req_params is NULL\n", __func__);
+ return -EINVAL;
+ }
+
+ ufs_hisi_set_dev_cap(&host_params);
+ ret = ufshcd_negotiate_pwr_params(&host_params, dev_max_params, dev_req_params);
+ if (ret)
+ dev_err(hba->dev, "%s: failed to determine capabilities\n", __func__);
+
+ return ret;
+}
+
static void ufs_hisi_pwr_change_pre_change(struct ufs_hba *hba)
{
struct ufs_hisi_host *host = ufshcd_get_variant(hba);
@@ -362,10 +382,8 @@ static void ufs_hisi_pwr_change_pre_change(struct ufs_hba *hba)
static int ufs_hisi_pwr_change_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status,
- const struct ufs_pa_layer_attr *dev_max_params,
struct ufs_pa_layer_attr *dev_req_params)
{
- struct ufs_host_params host_params;
int ret = 0;
if (!dev_req_params) {
@@ -377,14 +395,6 @@ static int ufs_hisi_pwr_change_notify(struct ufs_hba *hba,
switch (status) {
case PRE_CHANGE:
- ufs_hisi_set_dev_cap(&host_params);
- ret = ufshcd_negotiate_pwr_params(&host_params, dev_max_params, dev_req_params);
- if (ret) {
- dev_err(hba->dev,
- "%s: failed to determine capabilities\n", __func__);
- goto out;
- }
-
ufs_hisi_pwr_change_pre_change(hba);
break;
case POST_CHANGE:
@@ -543,6 +553,7 @@ static const struct ufs_hba_variant_ops ufs_hba_hi3660_vops = {
.name = "hi3660",
.init = ufs_hi3660_init,
.link_startup_notify = ufs_hisi_link_startup_notify,
+ .negotiate_pwr_mode = ufs_hisi_negotiate_pwr_mode,
.pwr_change_notify = ufs_hisi_pwr_change_notify,
.suspend = ufs_hisi_suspend,
.resume = ufs_hisi_resume,
@@ -552,6 +563,7 @@ static const struct ufs_hba_variant_ops ufs_hba_hi3670_vops = {
.name = "hi3670",
.init = ufs_hi3670_init,
.link_startup_notify = ufs_hisi_link_startup_notify,
+ .negotiate_pwr_mode = ufs_hisi_negotiate_pwr_mode,
.pwr_change_notify = ufs_hisi_pwr_change_notify,
.suspend = ufs_hisi_suspend,
.resume = ufs_hisi_resume,
diff --git a/drivers/ufs/host/ufs-mediatek.c b/drivers/ufs/host/ufs-mediatek.c
index 05892b9ac528..507ff0c1cd26 100644
--- a/drivers/ufs/host/ufs-mediatek.c
+++ b/drivers/ufs/host/ufs-mediatek.c
@@ -1317,6 +1317,29 @@ static int ufs_mtk_init(struct ufs_hba *hba)
return err;
}
+static int ufs_mtk_negotiate_pwr_mode(struct ufs_hba *hba,
+ const struct ufs_pa_layer_attr *dev_max_params,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ struct ufs_host_params host_params;
+ int ret;
+
+ ufshcd_init_host_params(&host_params);
+ host_params.hs_rx_gear = UFS_HS_G5;
+ host_params.hs_tx_gear = UFS_HS_G5;
+
+ if (dev_max_params->pwr_rx == SLOW_MODE ||
+ dev_max_params->pwr_tx == SLOW_MODE)
+ host_params.desired_working_mode = UFS_PWM_MODE;
+
+ ret = ufshcd_negotiate_pwr_params(&host_params, dev_max_params, dev_req_params);
+ if (ret)
+ pr_info("%s: failed to determine capabilities\n",
+ __func__);
+
+ return ret;
+}
+
static bool ufs_mtk_pmc_via_fastauto(struct ufs_hba *hba,
struct ufs_pa_layer_attr *dev_req_params)
{
@@ -1372,26 +1395,10 @@ static void ufs_mtk_adjust_sync_length(struct ufs_hba *hba)
}
static int ufs_mtk_pre_pwr_change(struct ufs_hba *hba,
- const struct ufs_pa_layer_attr *dev_max_params,
struct ufs_pa_layer_attr *dev_req_params)
{
struct ufs_mtk_host *host = ufshcd_get_variant(hba);
- struct ufs_host_params host_params;
- int ret;
-
- ufshcd_init_host_params(&host_params);
- host_params.hs_rx_gear = UFS_HS_G5;
- host_params.hs_tx_gear = UFS_HS_G5;
-
- if (dev_max_params->pwr_rx == SLOW_MODE ||
- dev_max_params->pwr_tx == SLOW_MODE)
- host_params.desired_working_mode = UFS_PWM_MODE;
-
- ret = ufshcd_negotiate_pwr_params(&host_params, dev_max_params, dev_req_params);
- if (ret) {
- pr_info("%s: failed to determine capabilities\n",
- __func__);
- }
+ int ret = 0;
if (ufs_mtk_pmc_via_fastauto(hba, dev_req_params)) {
ufs_mtk_adjust_sync_length(hba);
@@ -1503,7 +1510,6 @@ static int ufs_mtk_auto_hibern8_disable(struct ufs_hba *hba)
static int ufs_mtk_pwr_change_notify(struct ufs_hba *hba,
enum ufs_notify_change_status stage,
- const struct ufs_pa_layer_attr *dev_max_params,
struct ufs_pa_layer_attr *dev_req_params)
{
int ret = 0;
@@ -1515,8 +1521,7 @@ static int ufs_mtk_pwr_change_notify(struct ufs_hba *hba,
reg = ufshcd_readl(hba, REG_AUTO_HIBERNATE_IDLE_TIMER);
ufs_mtk_auto_hibern8_disable(hba);
}
- ret = ufs_mtk_pre_pwr_change(hba, dev_max_params,
- dev_req_params);
+ ret = ufs_mtk_pre_pwr_change(hba, dev_req_params);
break;
case POST_CHANGE:
if (ufshcd_is_auto_hibern8_supported(hba))
@@ -2318,6 +2323,7 @@ static const struct ufs_hba_variant_ops ufs_hba_mtk_vops = {
.setup_clocks = ufs_mtk_setup_clocks,
.hce_enable_notify = ufs_mtk_hce_enable_notify,
.link_startup_notify = ufs_mtk_link_startup_notify,
+ .negotiate_pwr_mode = ufs_mtk_negotiate_pwr_mode,
.pwr_change_notify = ufs_mtk_pwr_change_notify,
.apply_dev_quirks = ufs_mtk_apply_dev_quirks,
.fixup_dev_quirks = ufs_mtk_fixup_dev_quirks,
diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
index 375fd24ba458..5eb12a999eb1 100644
--- a/drivers/ufs/host/ufs-qcom.c
+++ b/drivers/ufs/host/ufs-qcom.c
@@ -966,13 +966,31 @@ static void ufs_qcom_set_tx_hs_equalizer(struct ufs_hba *hba, u32 gear, u32 tx_l
}
}
-static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba,
- enum ufs_notify_change_status status,
- const struct ufs_pa_layer_attr *dev_max_params,
- struct ufs_pa_layer_attr *dev_req_params)
+static int ufs_qcom_negotiate_pwr_mode(struct ufs_hba *hba,
+ const struct ufs_pa_layer_attr *dev_max_params,
+ struct ufs_pa_layer_attr *dev_req_params)
{
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
struct ufs_host_params *host_params = &host->host_params;
+ int ret;
+
+ if (!dev_req_params) {
+ pr_err("%s: incoming dev_req_params is NULL\n", __func__);
+ return -EINVAL;
+ }
+
+ ret = ufshcd_negotiate_pwr_params(host_params, dev_max_params, dev_req_params);
+ if (ret)
+ dev_err(hba->dev, "%s: failed to determine capabilities\n", __func__);
+
+ return ret;
+}
+
+static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
int ret = 0;
if (!dev_req_params) {
@@ -982,13 +1000,6 @@ static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba,
switch (status) {
case PRE_CHANGE:
- ret = ufshcd_negotiate_pwr_params(host_params, dev_max_params, dev_req_params);
- if (ret) {
- dev_err(hba->dev, "%s: failed to determine capabilities\n",
- __func__);
- return ret;
- }
-
/*
* During UFS driver probe, always update the PHY gear to match the negotiated
* gear, so that, if quirk UFSHCD_QUIRK_REINIT_AFTER_MAX_GEAR_SWITCH is enabled,
@@ -2341,6 +2352,7 @@ static const struct ufs_hba_variant_ops ufs_hba_qcom_vops = {
.setup_clocks = ufs_qcom_setup_clocks,
.hce_enable_notify = ufs_qcom_hce_enable_notify,
.link_startup_notify = ufs_qcom_link_startup_notify,
+ .negotiate_pwr_mode = ufs_qcom_negotiate_pwr_mode,
.pwr_change_notify = ufs_qcom_pwr_change_notify,
.apply_dev_quirks = ufs_qcom_apply_dev_quirks,
.fixup_dev_quirks = ufs_qcom_fixup_dev_quirks,
diff --git a/drivers/ufs/host/ufs-sprd.c b/drivers/ufs/host/ufs-sprd.c
index 65bd8fb96b99..fbb290356e1d 100644
--- a/drivers/ufs/host/ufs-sprd.c
+++ b/drivers/ufs/host/ufs-sprd.c
@@ -159,16 +159,22 @@ static int ufs_sprd_common_init(struct ufs_hba *hba)
return ret;
}
+static int sprd_ufs_negotiate_pwr_mode(struct ufs_hba *hba,
+ const struct ufs_pa_layer_attr *dev_max_params,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ memcpy(dev_req_params, dev_max_params, sizeof(struct ufs_pa_layer_attr));
+
+ return 0;
+}
+
static int sprd_ufs_pwr_change_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status,
- const struct ufs_pa_layer_attr *dev_max_params,
struct ufs_pa_layer_attr *dev_req_params)
{
struct ufs_sprd_host *host = ufshcd_get_variant(hba);
if (status == PRE_CHANGE) {
- memcpy(dev_req_params, dev_max_params,
- sizeof(struct ufs_pa_layer_attr));
if (host->unipro_ver >= UFS_UNIPRO_VER_1_8)
ufshcd_dme_configure_adapt(hba, dev_req_params->gear_tx,
PA_INITIAL_ADAPT);
@@ -398,6 +404,7 @@ static struct ufs_sprd_priv n6_ufs = {
.name = "sprd,ums9620-ufs",
.init = ufs_sprd_n6_init,
.hce_enable_notify = sprd_ufs_n6_hce_enable_notify,
+ .negotiate_pwr_mode = sprd_ufs_negotiate_pwr_mode,
.pwr_change_notify = sprd_ufs_pwr_change_notify,
.hibern8_notify = sprd_ufs_n6_h8_notify,
.device_reset = ufs_sprd_n6_device_reset,
diff --git a/drivers/ufs/host/ufshcd-pci.c b/drivers/ufs/host/ufshcd-pci.c
index 5f65dfad1a71..894b7589b14e 100644
--- a/drivers/ufs/host/ufshcd-pci.c
+++ b/drivers/ufs/host/ufshcd-pci.c
@@ -138,6 +138,15 @@ static int ufs_intel_link_startup_notify(struct ufs_hba *hba,
return err;
}
+static int ufs_intel_lkf_negotiate_pwr_mode(struct ufs_hba *hba,
+ const struct ufs_pa_layer_attr *dev_max_params,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ memcpy(dev_req_params, dev_max_params, sizeof(*dev_req_params));
+
+ return 0;
+}
+
static int ufs_intel_set_lanes(struct ufs_hba *hba, u32 lanes)
{
struct ufs_pa_layer_attr pwr_info = hba->pwr_info;
@@ -145,7 +154,7 @@ static int ufs_intel_set_lanes(struct ufs_hba *hba, u32 lanes)
pwr_info.lane_rx = lanes;
pwr_info.lane_tx = lanes;
- ret = ufshcd_config_pwr_mode(hba, &pwr_info);
+ ret = ufshcd_change_power_mode(hba, &pwr_info);
if (ret)
dev_err(hba->dev, "%s: Setting %u lanes, err = %d\n",
__func__, lanes, ret);
@@ -154,17 +163,15 @@ static int ufs_intel_set_lanes(struct ufs_hba *hba, u32 lanes)
static int ufs_intel_lkf_pwr_change_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status,
- const struct ufs_pa_layer_attr *dev_max_params,
struct ufs_pa_layer_attr *dev_req_params)
{
int err = 0;
switch (status) {
case PRE_CHANGE:
- if (ufshcd_is_hs_mode(dev_max_params) &&
+ if (ufshcd_is_hs_mode(dev_req_params) &&
(hba->pwr_info.lane_rx != 2 || hba->pwr_info.lane_tx != 2))
ufs_intel_set_lanes(hba, 2);
- memcpy(dev_req_params, dev_max_params, sizeof(*dev_req_params));
break;
case POST_CHANGE:
if (ufshcd_is_hs_mode(dev_req_params)) {
@@ -507,6 +514,7 @@ static struct ufs_hba_variant_ops ufs_intel_lkf_hba_vops = {
.exit = ufs_intel_common_exit,
.hce_enable_notify = ufs_intel_hce_enable_notify,
.link_startup_notify = ufs_intel_link_startup_notify,
+ .negotiate_pwr_mode = ufs_intel_lkf_negotiate_pwr_mode,
.pwr_change_notify = ufs_intel_lkf_pwr_change_notify,
.apply_dev_quirks = ufs_intel_lkf_apply_dev_quirks,
.resume = ufs_intel_resume,
diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h
index 8563b6648976..51c2555bea73 100644
--- a/include/ufs/ufshcd.h
+++ b/include/ufs/ufshcd.h
@@ -302,11 +302,10 @@ struct ufs_pwr_mode_info {
* variant specific Uni-Pro initialization.
* @link_startup_notify: called before and after Link startup is carried out
* to allow variant specific Uni-Pro initialization.
+ * @negotiate_pwr_mode: called to negotiate power mode.
* @pwr_change_notify: called before and after a power mode change
* is carried out to allow vendor spesific capabilities
- * to be set. PRE_CHANGE can modify final_params based
- * on desired_pwr_mode, but POST_CHANGE must not alter
- * the final_params parameter
+ * to be set.
* @setup_xfer_req: called before any transfer request is issued
* to set some things
* @setup_task_mgmt: called before any task management request is issued
@@ -347,10 +346,12 @@ struct ufs_hba_variant_ops {
enum ufs_notify_change_status);
int (*link_startup_notify)(struct ufs_hba *,
enum ufs_notify_change_status);
- int (*pwr_change_notify)(struct ufs_hba *,
- enum ufs_notify_change_status status,
- const struct ufs_pa_layer_attr *desired_pwr_mode,
- struct ufs_pa_layer_attr *final_params);
+ int (*negotiate_pwr_mode)(struct ufs_hba *hba,
+ const struct ufs_pa_layer_attr *desired_pwr_mode,
+ struct ufs_pa_layer_attr *final_params);
+ int (*pwr_change_notify)(struct ufs_hba *hba,
+ enum ufs_notify_change_status status,
+ struct ufs_pa_layer_attr *final_params);
void (*setup_xfer_req)(struct ufs_hba *hba, int tag,
bool is_scsi_cmd);
void (*setup_task_mgmt)(struct ufs_hba *, int, u8);
@@ -1361,6 +1362,8 @@ extern int ufshcd_dme_set_attr(struct ufs_hba *hba, u32 attr_sel,
u8 attr_set, u32 mib_val, u8 peer);
extern int ufshcd_dme_get_attr(struct ufs_hba *hba, u32 attr_sel,
u32 *mib_val, u8 peer);
+extern int ufshcd_change_power_mode(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode);
extern int ufshcd_config_pwr_mode(struct ufs_hba *hba,
struct ufs_pa_layer_attr *desired_pwr_mode);
extern int ufshcd_uic_change_pwr_mode(struct ufs_hba *hba, u8 mode);
--
2.34.1
^ permalink raw reply related [flat|nested] 39+ messages in thread* Re: [PATCH v2 01/11] scsi: ufs: core: Introduce a new ufshcd vops negotiate_pwr_mode()
2026-03-04 13:53 ` [PATCH v2 01/11] scsi: ufs: core: Introduce a new ufshcd vops negotiate_pwr_mode() Can Guo
@ 2026-03-05 12:53 ` Krzysztof Kozlowski
2026-03-06 12:25 ` Can Guo
2026-03-05 21:03 ` Bean Huo
1 sibling, 1 reply; 39+ messages in thread
From: Krzysztof Kozlowski @ 2026-03-05 12:53 UTC (permalink / raw)
To: Can Guo, avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Alim Akhtar, James E.J. Bottomley,
Sai Krishna Potthuri, Ajay Neeli, Peter Griffin, Peter Wang,
Chaotian Jing, Stanley Jhu, Manivannan Sadhasivam, Orson Zhai,
Baolin Wang, Chunyan Zhang, Matthias Brugger,
AngeloGioacchino Del Regno, Bao D. Nguyen, Adrian Hunter,
Archana Patni, open list,
open list:UNIVERSAL FLASH STORAGE HOST CONTROLLER DRIVER...,
moderated list:ARM/SAMSUNG S3C, S5P AND EXYNOS ARM ARCHITECTURES,
moderated list:UNIVERSAL FLASH STORAGE HOST CONTROLLER DRIVER...,
open list:ARM/QUALCOMM MAILING LIST
On 04/03/2026 14:53, Can Guo wrote:
> {
> int ret;
>
> @@ -4747,6 +4745,22 @@ static int ufshcd_change_power_mode(struct ufs_hba *hba,
> return ret;
> }
>
Missing kerneldoc.
> +int ufshcd_change_power_mode(struct ufs_hba *hba,
> + struct ufs_pa_layer_attr *pwr_mode)
> +{
> + int ret;
> +
> + ufshcd_vops_pwr_change_notify(hba, PRE_CHANGE, pwr_mode);
> +
> + ret = ufshcd_dme_change_power_mode(hba, pwr_mode);
> +
> + if (!ret)
> + ufshcd_vops_pwr_change_notify(hba, POST_CHANGE, pwr_mode);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(ufshcd_change_power_mode);
> +
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 39+ messages in thread* Re: [PATCH v2 01/11] scsi: ufs: core: Introduce a new ufshcd vops negotiate_pwr_mode()
2026-03-05 12:53 ` Krzysztof Kozlowski
@ 2026-03-06 12:25 ` Can Guo
0 siblings, 0 replies; 39+ messages in thread
From: Can Guo @ 2026-03-06 12:25 UTC (permalink / raw)
To: Krzysztof Kozlowski, avri.altman, bvanassche, beanhuo,
martin.petersen
Cc: linux-scsi, Alim Akhtar, James E.J. Bottomley,
Sai Krishna Potthuri, Ajay Neeli, Peter Griffin, Peter Wang,
Chaotian Jing, Stanley Jhu, Manivannan Sadhasivam, Orson Zhai,
Baolin Wang, Chunyan Zhang, Matthias Brugger,
AngeloGioacchino Del Regno, Bao D. Nguyen, Adrian Hunter,
Archana Patni, open list,
open list:UNIVERSAL FLASH STORAGE HOST CONTROLLER DRIVER...,
moderated list:ARM/SAMSUNG S3C, S5P AND EXYNOS ARM ARCHITECTURES,
moderated list:UNIVERSAL FLASH STORAGE HOST CONTROLLER DRIVER...,
open list:ARM/QUALCOMM MAILING LIST
On 3/5/2026 8:53 PM, Krzysztof Kozlowski wrote:
> On 04/03/2026 14:53, Can Guo wrote:
>> {
>> int ret;
>>
>> @@ -4747,6 +4745,22 @@ static int ufshcd_change_power_mode(struct ufs_hba *hba,
>> return ret;
>> }
>>
> Missing kerneldoc.
Will add in next version.
Thanks,
Can Guo.
>
>> +int ufshcd_change_power_mode(struct ufs_hba *hba,
>> + struct ufs_pa_layer_attr *pwr_mode)
>> +{
>> + int ret;
>> +
>> + ufshcd_vops_pwr_change_notify(hba, PRE_CHANGE, pwr_mode);
>> +
>> + ret = ufshcd_dme_change_power_mode(hba, pwr_mode);
>> +
>> + if (!ret)
>> + ufshcd_vops_pwr_change_notify(hba, POST_CHANGE, pwr_mode);
>> +
>> + return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(ufshcd_change_power_mode);
>> +
>
> Best regards,
> Krzysztof
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 01/11] scsi: ufs: core: Introduce a new ufshcd vops negotiate_pwr_mode()
2026-03-04 13:53 ` [PATCH v2 01/11] scsi: ufs: core: Introduce a new ufshcd vops negotiate_pwr_mode() Can Guo
2026-03-05 12:53 ` Krzysztof Kozlowski
@ 2026-03-05 21:03 ` Bean Huo
2026-03-06 12:41 ` Can Guo
1 sibling, 1 reply; 39+ messages in thread
From: Bean Huo @ 2026-03-05 21:03 UTC (permalink / raw)
To: Can Guo, avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Alim Akhtar, James E.J. Bottomley,
Sai Krishna Potthuri, Ajay Neeli, Peter Griffin,
Krzysztof Kozlowski, Peter Wang, Chaotian Jing, Stanley Jhu,
Manivannan Sadhasivam, Orson Zhai, Baolin Wang, Chunyan Zhang,
Matthias Brugger, AngeloGioacchino Del Regno, Bao D. Nguyen,
Adrian Hunter, Archana Patni, open list,
open list:UNIVERSAL FLASH STORAGE HOST CONTROLLER DRIVER...,
moderated list:ARM/SAMSUNG S3C, S5P AND EXYNOS ARM ARCHITECTURES,
moderated list:UNIVERSAL FLASH STORAGE HOST CONTROLLER DRIVER...,
open list:ARM/QUALCOMM MAILING LIST
On Wed, 2026-03-04 at 05:53 -0800, Can Guo wrote:
> @@ -4747,6 +4745,22 @@ static int ufshcd_change_power_mode(struct ufs_hba
> *hba,
> return ret;
> }
>
> +int ufshcd_change_power_mode(struct ufs_hba *hba,
> + struct ufs_pa_layer_attr *pwr_mode)
> +{
> + int ret;
> +
> + ufshcd_vops_pwr_change_notify(hba, PRE_CHANGE, pwr_mode);
ufshcd_change_power_mode() calls pwr_change_notify(PRE_CHANGE) but ignores its
return, this can continue with invalid vendor prep? I saw there is checkup
before, do you think adding check result?
> +
> + ret = ufshcd_dme_change_power_mode(hba, pwr_mode);
> +
> + if (!ret)
> + ufshcd_vops_pwr_change_notify(hba, POST_CHANGE, pwr_mode);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(ufshcd_change_power_mode);
Kind regards,
Bean
^ permalink raw reply [flat|nested] 39+ messages in thread* Re: [PATCH v2 01/11] scsi: ufs: core: Introduce a new ufshcd vops negotiate_pwr_mode()
2026-03-05 21:03 ` Bean Huo
@ 2026-03-06 12:41 ` Can Guo
0 siblings, 0 replies; 39+ messages in thread
From: Can Guo @ 2026-03-06 12:41 UTC (permalink / raw)
To: Bean Huo, avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Alim Akhtar, James E.J. Bottomley,
Sai Krishna Potthuri, Ajay Neeli, Peter Griffin,
Krzysztof Kozlowski, Peter Wang, Chaotian Jing, Stanley Jhu,
Manivannan Sadhasivam, Orson Zhai, Baolin Wang, Chunyan Zhang,
Matthias Brugger, AngeloGioacchino Del Regno, Bao D. Nguyen,
Adrian Hunter, Archana Patni, open list,
open list:UNIVERSAL FLASH STORAGE HOST CONTROLLER DRIVER...,
moderated list:ARM/SAMSUNG S3C, S5P AND EXYNOS ARM ARCHITECTURES,
moderated list:UNIVERSAL FLASH STORAGE HOST CONTROLLER DRIVER...,
open list:ARM/QUALCOMM MAILING LIST
Hi Bean,
On 3/6/2026 5:03 AM, Bean Huo wrote:
> On Wed, 2026-03-04 at 05:53 -0800, Can Guo wrote:
>> @@ -4747,6 +4745,22 @@ static int ufshcd_change_power_mode(struct ufs_hba
>> *hba,
>> return ret;
>> }
>>
>> +int ufshcd_change_power_mode(struct ufs_hba *hba,
>> + struct ufs_pa_layer_attr *pwr_mode)
>> +{
>> + int ret;
>> +
>> + ufshcd_vops_pwr_change_notify(hba, PRE_CHANGE, pwr_mode);
>
> ufshcd_change_power_mode() calls pwr_change_notify(PRE_CHANGE) but ignores its
> return, this can continue with invalid vendor prep? I saw there is checkup
> before, do you think adding check result?
pwr_change_notify(PRE_CHANGE) was used by most vendor-specific
implementations
to negotiate Power Mode negotiation, if pwr_change_notify(PRE_CHANGE) is not
implemented (returns -ENOTSUPP) or Power Mode negotiation returns error,
the error
check was there in order to call the memcpy() to copy the desired Power
Mode as the
final power mode, that is, an error return from
pwr_change_notify(PRE_CHANGE) won't
lead to skipping the ufshcd_change_power_mode().
So, to introduce the new vops negotiate_pwr_mode() and keep the logic
same as before,
in this patch, the error check and its error handling are kept and
coming after the call to
ufshcd_vops_negotiate_pwr_mode():
int ufshcd_config_pwr_mode(struct ufs_hba *hba,
struct ufs_pa_layer_attr *desired_pwr_mode)
{
struct ufs_pa_layer_attr final_params = { 0 };
int ret;
ret = ufshcd_vops_negotiate_pwr_mode(hba, desired_pwr_mode,
&final_params);
if (ret)
memcpy(&final_params, desired_pwr_mode,
sizeof(final_params));
return ufshcd_change_power_mode(hba, &final_params);
}
I hope your question is answered.
Thanks,
Can Guo.
>
>> +
>> + ret = ufshcd_dme_change_power_mode(hba, pwr_mode);
>> +
>> + if (!ret)
>> + ufshcd_vops_pwr_change_notify(hba, POST_CHANGE, pwr_mode);
>> +
>> + return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(ufshcd_change_power_mode);
>
> Kind regards,
> Bean
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 02/11] scsi: ufs: core: Pass force_pmc to ufshcd_config_pwr_mode() as a parameter
2026-03-04 13:53 [PATCH v2 00/11] scsi: ufs: Add TX Equalization support for UFS 5.0 Can Guo
2026-03-04 13:53 ` [PATCH v2 01/11] scsi: ufs: core: Introduce a new ufshcd vops negotiate_pwr_mode() Can Guo
@ 2026-03-04 13:53 ` Can Guo
2026-03-05 13:54 ` Bart Van Assche
2026-03-04 13:53 ` [PATCH v2 03/11] scsi: ufs: core: Add UFS_HS_G6 and UFS_HS_GEAR_MAX to enum ufs_hs_gear_tag Can Guo
` (9 subsequent siblings)
11 siblings, 1 reply; 39+ messages in thread
From: Can Guo @ 2026-03-04 13:53 UTC (permalink / raw)
To: avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Can Guo, Alim Akhtar, James E.J. Bottomley,
Peter Wang, Adrian Hunter, Bao D. Nguyen, Archana Patni,
open list
Currently, callers must manually toggle hba->force_pmc before and after
calling ufshcd_config_pwr_mode() to force a Power Mode change. Refactor
ufshcd_config_pwr_mode() to accept force_pmc as a parameter.
Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
---
drivers/ufs/core/ufshcd.c | 35 ++++++++++++++++++++++-------------
drivers/ufs/host/ufshcd-pci.c | 3 ++-
include/ufs/ufshcd.h | 19 +++++++++++++++----
3 files changed, 39 insertions(+), 18 deletions(-)
diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
index c6b9c58fece3..6809564fb507 100644
--- a/drivers/ufs/core/ufshcd.c
+++ b/drivers/ufs/core/ufshcd.c
@@ -1407,7 +1407,8 @@ static int ufshcd_scale_gear(struct ufs_hba *hba, u32 target_gear, bool scale_up
config_pwr_mode:
/* check if the power mode needs to be changed or not? */
- ret = ufshcd_config_pwr_mode(hba, &new_pwr_info);
+ ret = ufshcd_config_pwr_mode(hba, &new_pwr_info,
+ UFSHCD_PMC_POLICY_DONT_FORCE);
if (ret)
dev_err(hba->dev, "%s: failed err %d, old gear: (tx %d rx %d), new gear: (tx %d rx %d)",
__func__, ret,
@@ -4248,7 +4249,8 @@ int ufshcd_dme_get_attr(struct ufs_hba *hba, u32 attr_sel,
pwr_mode_change = true;
}
if (pwr_mode_change) {
- ret = ufshcd_change_power_mode(hba, &temp_pwr_info);
+ ret = ufshcd_change_power_mode(hba, &temp_pwr_info,
+ UFSHCD_PMC_POLICY_DONT_FORCE);
if (ret)
goto out;
}
@@ -4272,7 +4274,8 @@ int ufshcd_dme_get_attr(struct ufs_hba *hba, u32 attr_sel,
if (peer && (hba->quirks & UFSHCD_QUIRK_DME_PEER_ACCESS_AUTO_MODE)
&& pwr_mode_change)
- ufshcd_change_power_mode(hba, &orig_pwr_info);
+ ufshcd_change_power_mode(hba, &orig_pwr_info,
+ UFSHCD_PMC_POLICY_DONT_FORCE);
out:
return ret;
}
@@ -4661,12 +4664,14 @@ static int ufshcd_get_max_pwr_mode(struct ufs_hba *hba)
}
static int ufshcd_dme_change_power_mode(struct ufs_hba *hba,
- struct ufs_pa_layer_attr *pwr_mode)
+ struct ufs_pa_layer_attr *pwr_mode,
+ enum ufshcd_pmc_policy pmc_policy)
+
{
int ret;
/* if already configured to the requested pwr_mode */
- if (!hba->force_pmc &&
+ if (pmc_policy == UFSHCD_PMC_POLICY_DONT_FORCE &&
pwr_mode->gear_rx == hba->pwr_info.gear_rx &&
pwr_mode->gear_tx == hba->pwr_info.gear_tx &&
pwr_mode->lane_rx == hba->pwr_info.lane_rx &&
@@ -4746,13 +4751,15 @@ static int ufshcd_dme_change_power_mode(struct ufs_hba *hba,
}
int ufshcd_change_power_mode(struct ufs_hba *hba,
- struct ufs_pa_layer_attr *pwr_mode)
+ struct ufs_pa_layer_attr *pwr_mode,
+ enum ufshcd_pmc_policy pmc_policy)
+
{
int ret;
ufshcd_vops_pwr_change_notify(hba, PRE_CHANGE, pwr_mode);
- ret = ufshcd_dme_change_power_mode(hba, pwr_mode);
+ ret = ufshcd_dme_change_power_mode(hba, pwr_mode, pmc_policy);
if (!ret)
ufshcd_vops_pwr_change_notify(hba, POST_CHANGE, pwr_mode);
@@ -4765,11 +4772,13 @@ EXPORT_SYMBOL_GPL(ufshcd_change_power_mode);
* ufshcd_config_pwr_mode - configure a new power mode
* @hba: per-adapter instance
* @desired_pwr_mode: desired power configuration
+ * @pmc_policy: Power Mode change policy
*
* Return: 0 upon success; < 0 upon failure.
*/
int ufshcd_config_pwr_mode(struct ufs_hba *hba,
- struct ufs_pa_layer_attr *desired_pwr_mode)
+ struct ufs_pa_layer_attr *desired_pwr_mode,
+ enum ufshcd_pmc_policy pmc_policy)
{
struct ufs_pa_layer_attr final_params = { 0 };
int ret;
@@ -4779,7 +4788,7 @@ int ufshcd_config_pwr_mode(struct ufs_hba *hba,
if (ret)
memcpy(&final_params, desired_pwr_mode, sizeof(final_params));
- return ufshcd_change_power_mode(hba, &final_params);
+ return ufshcd_change_power_mode(hba, &final_params, pmc_policy);
}
EXPORT_SYMBOL_GPL(ufshcd_config_pwr_mode);
@@ -6836,14 +6845,13 @@ static void ufshcd_err_handler(struct work_struct *work)
* are sent via bsg and/or sysfs.
*/
down_write(&hba->clk_scaling_lock);
- hba->force_pmc = true;
- pmc_err = ufshcd_config_pwr_mode(hba, &(hba->pwr_info));
+ pmc_err = ufshcd_config_pwr_mode(hba, &hba->pwr_info,
+ UFSHCD_PMC_POLICY_FORCE);
if (pmc_err) {
needs_reset = true;
dev_err(hba->dev, "%s: Failed to restore power mode, err = %d\n",
__func__, pmc_err);
}
- hba->force_pmc = false;
ufshcd_print_pwr_info(hba);
up_write(&hba->clk_scaling_lock);
spin_lock_irqsave(hba->host->host_lock, flags);
@@ -9118,7 +9126,8 @@ static int ufshcd_post_device_init(struct ufs_hba *hba)
if (hba->dev_ref_clk_freq != REF_CLK_FREQ_INVAL)
ufshcd_set_dev_ref_clk(hba);
/* Gear up to HS gear. */
- ret = ufshcd_config_pwr_mode(hba, &hba->max_pwr_info.info);
+ ret = ufshcd_config_pwr_mode(hba, &hba->max_pwr_info.info,
+ UFSHCD_PMC_POLICY_DONT_FORCE);
if (ret) {
dev_err(hba->dev, "%s: Failed setting power mode, err = %d\n",
__func__, ret);
diff --git a/drivers/ufs/host/ufshcd-pci.c b/drivers/ufs/host/ufshcd-pci.c
index 894b7589b14e..aa5e593769c3 100644
--- a/drivers/ufs/host/ufshcd-pci.c
+++ b/drivers/ufs/host/ufshcd-pci.c
@@ -154,7 +154,8 @@ static int ufs_intel_set_lanes(struct ufs_hba *hba, u32 lanes)
pwr_info.lane_rx = lanes;
pwr_info.lane_tx = lanes;
- ret = ufshcd_change_power_mode(hba, &pwr_info);
+ ret = ufshcd_change_power_mode(hba, &pwr_info,
+ UFSHCD_PMC_POLICY_DONT_FORCE);
if (ret)
dev_err(hba->dev, "%s: Setting %u lanes, err = %d\n",
__func__, lanes, ret);
diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h
index 51c2555bea73..16facaee3e77 100644
--- a/include/ufs/ufshcd.h
+++ b/include/ufs/ufshcd.h
@@ -529,6 +529,17 @@ enum ufshcd_state {
UFSHCD_STATE_ERROR,
};
+/**
+ * enum ufshcd_pmc_policy - Power Mode change policy
+ * @UFSHCD_PMC_POLICY_DONT_FORCE: Do not force a Power Mode change.
+ * @UFSHCD_PMC_POLICY_FORCE: Force a Power Mode change even if current Power
+ * Mode is same as target Power Mode.
+ */
+enum ufshcd_pmc_policy {
+ UFSHCD_PMC_POLICY_DONT_FORCE,
+ UFSHCD_PMC_POLICY_FORCE,
+};
+
enum ufshcd_quirks {
/* Interrupt aggregation support is broken */
UFSHCD_QUIRK_BROKEN_INTR_AGGR = 1 << 0,
@@ -882,7 +893,6 @@ enum ufshcd_mcq_opr {
* @saved_uic_err: sticky UIC error mask
* @ufs_stats: various error counters
* @force_reset: flag to force eh_work perform a full reset
- * @force_pmc: flag to force a power mode change
* @silence_err_logs: flag to silence error logs
* @dev_cmd: ufs device management command information
* @last_dme_cmd_tstamp: time stamp of the last completed DME command
@@ -1036,7 +1046,6 @@ struct ufs_hba {
u32 saved_uic_err;
struct ufs_stats ufs_stats;
bool force_reset;
- bool force_pmc;
bool silence_err_logs;
/* Device management request data */
@@ -1363,9 +1372,11 @@ extern int ufshcd_dme_set_attr(struct ufs_hba *hba, u32 attr_sel,
extern int ufshcd_dme_get_attr(struct ufs_hba *hba, u32 attr_sel,
u32 *mib_val, u8 peer);
extern int ufshcd_change_power_mode(struct ufs_hba *hba,
- struct ufs_pa_layer_attr *pwr_mode);
+ struct ufs_pa_layer_attr *pwr_mode,
+ enum ufshcd_pmc_policy pmc_policy);
extern int ufshcd_config_pwr_mode(struct ufs_hba *hba,
- struct ufs_pa_layer_attr *desired_pwr_mode);
+ struct ufs_pa_layer_attr *desired_pwr_mode,
+ enum ufshcd_pmc_policy pmc_policy);
extern int ufshcd_uic_change_pwr_mode(struct ufs_hba *hba, u8 mode);
/* UIC command interfaces for DME primitives */
--
2.34.1
^ permalink raw reply related [flat|nested] 39+ messages in thread* Re: [PATCH v2 02/11] scsi: ufs: core: Pass force_pmc to ufshcd_config_pwr_mode() as a parameter
2026-03-04 13:53 ` [PATCH v2 02/11] scsi: ufs: core: Pass force_pmc to ufshcd_config_pwr_mode() as a parameter Can Guo
@ 2026-03-05 13:54 ` Bart Van Assche
0 siblings, 0 replies; 39+ messages in thread
From: Bart Van Assche @ 2026-03-05 13:54 UTC (permalink / raw)
To: Can Guo, avri.altman, beanhuo, martin.petersen
Cc: linux-scsi, Alim Akhtar, James E.J. Bottomley, Peter Wang,
Adrian Hunter, Bao D. Nguyen, Archana Patni, open list
On 3/4/26 7:53 AM, Can Guo wrote:
> Currently, callers must manually toggle hba->force_pmc before and after
> calling ufshcd_config_pwr_mode() to force a Power Mode change. Refactor
> ufshcd_config_pwr_mode() to accept force_pmc as a parameter.
Reviewed-by: Bart Van Assche <bvanassche@acm.org>
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 03/11] scsi: ufs: core: Add UFS_HS_G6 and UFS_HS_GEAR_MAX to enum ufs_hs_gear_tag
2026-03-04 13:53 [PATCH v2 00/11] scsi: ufs: Add TX Equalization support for UFS 5.0 Can Guo
2026-03-04 13:53 ` [PATCH v2 01/11] scsi: ufs: core: Introduce a new ufshcd vops negotiate_pwr_mode() Can Guo
2026-03-04 13:53 ` [PATCH v2 02/11] scsi: ufs: core: Pass force_pmc to ufshcd_config_pwr_mode() as a parameter Can Guo
@ 2026-03-04 13:53 ` Can Guo
2026-03-05 13:53 ` Bart Van Assche
2026-03-04 13:53 ` [PATCH v2 04/11] scsi: ufs: core: Add support for TX Equalization Can Guo
` (8 subsequent siblings)
11 siblings, 1 reply; 39+ messages in thread
From: Can Guo @ 2026-03-04 13:53 UTC (permalink / raw)
To: avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Can Guo, Alim Akhtar, James E.J. Bottomley, open list
Add UFS_HS_G6 to enum ufs_hs_gear_tag. In addition, add UFS_HS_GEAR_MAX to
enum ufs_hs_gear_tag to facilitate iteration over valid High Speed Gears.
Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
---
include/ufs/unipro.h | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/include/ufs/unipro.h b/include/ufs/unipro.h
index 59de737490ca..eccb45d3b86c 100644
--- a/include/ufs/unipro.h
+++ b/include/ufs/unipro.h
@@ -233,7 +233,9 @@ enum ufs_hs_gear_tag {
UFS_HS_G2, /* HS Gear 2 */
UFS_HS_G3, /* HS Gear 3 */
UFS_HS_G4, /* HS Gear 4 */
- UFS_HS_G5 /* HS Gear 5 */
+ UFS_HS_G5, /* HS Gear 5 */
+ UFS_HS_G6, /* HS Gear 6 */
+ UFS_HS_GEAR_MAX,
};
enum ufs_lanes {
--
2.34.1
^ permalink raw reply related [flat|nested] 39+ messages in thread* [PATCH v2 04/11] scsi: ufs: core: Add support for TX Equalization
2026-03-04 13:53 [PATCH v2 00/11] scsi: ufs: Add TX Equalization support for UFS 5.0 Can Guo
` (2 preceding siblings ...)
2026-03-04 13:53 ` [PATCH v2 03/11] scsi: ufs: core: Add UFS_HS_G6 and UFS_HS_GEAR_MAX to enum ufs_hs_gear_tag Can Guo
@ 2026-03-04 13:53 ` Can Guo
2026-03-04 20:46 ` kernel test robot
` (2 more replies)
2026-03-04 13:53 ` [PATCH v2 05/11] scsi: ufs: core: Add debugfs entries for TX Equalization params Can Guo
` (7 subsequent siblings)
11 siblings, 3 replies; 39+ messages in thread
From: Can Guo @ 2026-03-04 13:53 UTC (permalink / raw)
To: avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Can Guo, Alim Akhtar, James E.J. Bottomley,
Peter Wang, Bao D. Nguyen, Adrian Hunter, open list
MIPI Unipro3.0 introduced PA_TxEQGnSetting and PA_PreCodeEn attributes for
TX Equalization and Pre-Coding. It is Host Software's responsibility to
configure these attributes for both host and device before initiating
Power Mode Change to High-Speed Gears.
MIPI Unipro3.0 also introduced TX Equalization Training (EQTR) to identify
optimal TX Equalization settings for use by both Host's and Device's
UniPro. TX EQTR shall be initiated from the most reliable High-Speed Gear
(HS-G1) targeting High-Speed Gears (HS-G4 to HS-G6).
Implement TX Equalization configuration and TX EQTR procedure as defined
in UFSHCI v5.0 specification. The TX EQTR procedure determines the optimal
TX Equalization settings by iterating through all possible PreShoot and
DeEmphasis combinations and selecting the best combinations for both Host
and Device based on Figure of Merit (FOM) evaluation.
Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
---
drivers/ufs/core/Makefile | 2 +-
drivers/ufs/core/ufs-txeq.c | 1210 ++++++++++++++++++++++++++++++++
drivers/ufs/core/ufshcd-priv.h | 38 +
drivers/ufs/core/ufshcd.c | 52 +-
include/ufs/ufshcd.h | 108 +++
include/ufs/unipro.h | 120 +++-
6 files changed, 1521 insertions(+), 9 deletions(-)
create mode 100644 drivers/ufs/core/ufs-txeq.c
diff --git a/drivers/ufs/core/Makefile b/drivers/ufs/core/Makefile
index 51e1867e524e..ce7d16d2cf35 100644
--- a/drivers/ufs/core/Makefile
+++ b/drivers/ufs/core/Makefile
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_SCSI_UFSHCD) += ufshcd-core.o
-ufshcd-core-y += ufshcd.o ufs-sysfs.o ufs-mcq.o
+ufshcd-core-y += ufshcd.o ufs-sysfs.o ufs-mcq.o ufs-txeq.o
ufshcd-core-$(CONFIG_RPMB) += ufs-rpmb.o
ufshcd-core-$(CONFIG_DEBUG_FS) += ufs-debugfs.o
ufshcd-core-$(CONFIG_SCSI_UFS_BSG) += ufs_bsg.o
diff --git a/drivers/ufs/core/ufs-txeq.c b/drivers/ufs/core/ufs-txeq.c
new file mode 100644
index 000000000000..2f637077548c
--- /dev/null
+++ b/drivers/ufs/core/ufs-txeq.c
@@ -0,0 +1,1210 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Qualcomm Technologies, Inc.
+ *
+ * Author:
+ * Can Guo <can.guo@oss.qualcomm.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <ufs/ufshcd.h>
+#include <ufs/unipro.h>
+#include "ufshcd-priv.h"
+
+static bool use_adaptive_txeq;
+module_param(use_adaptive_txeq, bool, 0644);
+MODULE_PARM_DESC(use_adaptive_txeq, "Find and apply optimal TX Equalization settings before power mode change (default: false)");
+
+static int txeq_gear_set(const char *val, const struct kernel_param *kp)
+{
+ return param_set_uint_minmax(val, kp, UFS_HS_G1, UFS_HS_G6);
+}
+
+static const struct kernel_param_ops txeq_gear_ops = {
+ .set = txeq_gear_set,
+ .get = param_get_uint,
+};
+
+static unsigned int adaptive_txeq_gear = UFS_HS_G6;
+module_param_cb(adaptive_txeq_gear, &txeq_gear_ops, &adaptive_txeq_gear, 0644);
+MODULE_PARM_DESC(adaptive_txeq_gear, "For the HS-Gear[n] and above, adaptive txeq shall be used");
+
+static bool use_txeq_presets = true;
+module_param(use_txeq_presets, bool, 0644);
+MODULE_PARM_DESC(use_txeq_presets, "Use only the 8 TX Equalization Presets (pre-defined Pre-Shoot & De-Emphasis combinations) for TX EQTR (default: true)");
+
+static bool txeq_presets_selected[UFS_TX_EQ_PRESET_MAX] = {[0 ... (UFS_TX_EQ_PRESET_MAX - 1)] = 1};
+module_param_array(txeq_presets_selected, bool, NULL, 0644);
+MODULE_PARM_DESC(txeq_presets_selected, "Use only the selected Presets out of the 8 TX Equalization Presets for TX EQTR");
+
+/**
+ * ufs_tx_eq_preset - Table of minimum required list of presets.
+ *
+ * A HS-G6 capable M-TX shall support the presets defined in M-PHY v6.0 spec.
+ * Preset Pre-Shoot(dB) De-Emphasis(dB)
+ * P0 0.0 0.0
+ * P1 0.0 0.8
+ * P2 0.0 1.6
+ * P3 0.8 0.0
+ * P4 1.6 0.0
+ * P5 0.8 0.8
+ * P6 0.8 1.6
+ * P7 1.6 0.8
+ */
+static const struct __ufs_tx_eq_preset {
+ unsigned int preshoot;
+ unsigned int deemphasis;
+} ufs_tx_eq_preset[UFS_TX_EQ_PRESET_MAX] = {
+ [UFS_TX_EQ_PRESET_P0] = {UFS_TX_HS_PRESHOOT_DB_0P0, UFS_TX_HS_DEEMPHASIS_DB_0P0},
+ [UFS_TX_EQ_PRESET_P1] = {UFS_TX_HS_PRESHOOT_DB_0P0, UFS_TX_HS_DEEMPHASIS_DB_0P8},
+ [UFS_TX_EQ_PRESET_P2] = {UFS_TX_HS_PRESHOOT_DB_0P0, UFS_TX_HS_DEEMPHASIS_DB_1P6},
+ [UFS_TX_EQ_PRESET_P3] = {UFS_TX_HS_PRESHOOT_DB_0P8, UFS_TX_HS_DEEMPHASIS_DB_0P0},
+ [UFS_TX_EQ_PRESET_P4] = {UFS_TX_HS_PRESHOOT_DB_1P6, UFS_TX_HS_DEEMPHASIS_DB_0P0},
+ [UFS_TX_EQ_PRESET_P5] = {UFS_TX_HS_PRESHOOT_DB_0P8, UFS_TX_HS_DEEMPHASIS_DB_0P8},
+ [UFS_TX_EQ_PRESET_P6] = {UFS_TX_HS_PRESHOOT_DB_0P8, UFS_TX_HS_DEEMPHASIS_DB_1P6},
+ [UFS_TX_EQ_PRESET_P7] = {UFS_TX_HS_PRESHOOT_DB_1P6, UFS_TX_HS_DEEMPHASIS_DB_0P8},
+};
+
+/**
+ * pa_peer_rx_adapt_initial - Table of UniPro PA_PeerRxHSGnAdaptInitial
+ * attribute IDs for High Speed (HS) Gears.
+ *
+ * This table maps HS Gears to their respective UniPro PA_PeerRxHSGnAdaptInitial
+ * attribute IDs. Entries for Gears 1-3 are 0 (unsupported).
+ */
+static const u32 pa_peer_rx_adapt_initial[UFS_HS_GEAR_MAX] = {
+ [UFS_HS_G4] = PA_PEERRXHSG4ADAPTINITIAL,
+ [UFS_HS_G5] = PA_PEERRXHSG5ADAPTINITIAL,
+ [UFS_HS_G6] = PA_PEERRXHSG6ADAPTINITIALL0L3
+};
+
+/**
+ * rx_adapt_initial_cap - Table of M-PHY RX_HS_Gn_ADAPT_INITIAL_Capability
+ * attribute IDs for High Speed (HS) Gears.
+ *
+ * This table maps HS Gears to their respective M-PHY
+ * RX_HS_Gn_ADAPT_INITIAL_Capability attribute IDs. Entries for Gears 1-3 are 0
+ * (unsupported).
+ */
+static const u32 rx_adapt_initial_cap[UFS_HS_GEAR_MAX] = {
+ [UFS_HS_G4] = RX_HS_G4_ADAPT_INITIAL_CAP,
+ [UFS_HS_G5] = RX_HS_G5_ADAPT_INITIAL_CAP,
+ [UFS_HS_G6] = RX_HS_G6_ADAPT_INITIAL_CAP
+};
+
+/**
+ * pa_tx_eq_setting - Table of UniPro PA_TxEQGnSetting attribute IDs for High
+ * Speed (HS) Gears.
+ *
+ * This table maps HS Gears to their respective UniPro PA_TxEQGnSetting
+ * attribute IDs.
+ */
+static const u32 pa_tx_eq_setting[UFS_HS_GEAR_MAX] = {
+ 0,
+ PA_TXEQG1SETTING,
+ PA_TXEQG2SETTING,
+ PA_TXEQG3SETTING,
+ PA_TXEQG4SETTING,
+ PA_TXEQG5SETTING,
+ PA_TXEQG6SETTING
+};
+
+/**
+ * ufshcd_configure_precoding - Configure Pre-Coding for all active lanes
+ * @hba: per adapter instance
+ * @params: TX EQ parameters data structure
+ *
+ * Bit[7] in RX_FOM indicates that the receiver needs to enable Pre-Coding when
+ * set. Pre-Coding must be enabled on both the transmitter and receiver to
+ * ensure proper operation.
+ *
+ * Returns 0 on success, negative error code otherwise
+ */
+static int ufshcd_configure_precoding(struct ufs_hba *hba,
+ struct ufshcd_tx_eq_params *params)
+{
+ u32 local_precode_en = 0;
+ u32 peer_precode_en = 0;
+ int lane, ret = 0;
+
+ /* Enable Pre-Coding for Host's TX & Device's RX pair */
+ for (lane = 0; lane < params->tx_lanes; lane++) {
+ if (params->host[lane].precode_en) {
+ local_precode_en |= PRECODEEN_TX_BIT(lane);
+ peer_precode_en |= PRECODEEN_RX_BIT(lane);
+ }
+ }
+
+ /* Enable Pre-Coding for Device's TX & Host's RX pair */
+ for (lane = 0; lane < params->rx_lanes; lane++) {
+ if (params->device[lane].precode_en) {
+ peer_precode_en |= PRECODEEN_TX_BIT(lane);
+ local_precode_en |= PRECODEEN_RX_BIT(lane);
+ }
+ }
+
+ if (!local_precode_en && !peer_precode_en) {
+ dev_dbg(hba->dev, "Pre-Coding is not required for either side\n");
+ return ret;
+ }
+
+ /* Set local PA_PreCodeEn */
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PRECODEEN), local_precode_en);
+ if (ret) {
+ dev_err(hba->dev, "Failed to set local PA_PreCodeEn: %d\n",
+ ret);
+ return ret;
+ }
+
+ /* Set peer PA_PreCodeEn */
+ ret = ufshcd_dme_peer_set(hba, UIC_ARG_MIB(PA_PRECODEEN), peer_precode_en);
+ if (ret) {
+ dev_err(hba->dev, "Failed to set peer PA_PreCodeEn: %d\n",
+ ret);
+ return ret;
+ }
+
+ dev_dbg(hba->dev, "Pre-Coding configured - Local PA_PreCodeEn: 0x%02x, Peer PA_PreCodeEn: 0x%02x\n",
+ local_precode_en, peer_precode_en);
+
+ return ret;
+}
+
+void ufshcd_print_tx_eq_params(struct ufs_hba *hba)
+{
+ struct ufshcd_tx_eq_params *params;
+ u32 gear = hba->pwr_info.gear_tx;
+ int lane;
+
+ if (!ufshcd_is_tx_eq_supported(hba))
+ return;
+
+ if (gear < UFS_HS_G1 || gear >= UFS_HS_GEAR_MAX)
+ return;
+
+ params = &hba->tx_eq_params[gear - 1];
+ if (!params->is_valid || !params->is_applied)
+ return;
+
+ for (lane = 0; lane < params->tx_lanes; lane++)
+ dev_dbg(hba->dev, "Host TX Equalization params: Lane %u - PreShoot %u, DeEmphasis %u, FOM value %u, PreCodeEn %d\n",
+ lane, params->host[lane].preshoot,
+ params->host[lane].deemphasis,
+ params->host[lane].fom_val,
+ params->host[lane].precode_en);
+
+ for (lane = 0; lane < params->rx_lanes; lane++)
+ dev_dbg(hba->dev, "Device TX Equalization params: Lane %u - PreShoot %u, DeEmphasis %u, FOM value %u, PreCodeEn %d\n",
+ lane, params->device[lane].preshoot,
+ params->device[lane].deemphasis,
+ params->device[lane].fom_val,
+ params->device[lane].precode_en);
+}
+
+static inline u32
+ufshcd_compose_tx_eq_setting(struct ufshcd_tx_eq_settings *settings,
+ int num_lanes)
+{
+ u32 setting = 0;
+ int lane;
+
+ for (lane = 0; lane < num_lanes; lane++, settings++) {
+ setting |= TX_HS_PRESHOOT_BITS(lane, settings->preshoot);
+ setting |= TX_HS_DEEMPHASIS_BITS(lane, settings->deemphasis);
+ }
+
+ return setting;
+}
+
+/**
+ * ufshcd_apply_tx_eq_settings - Apply TX Equalization settings for target gear
+ * @hba: per adapter instance
+ * @params: TX EQ parameters data structure
+ * @gear: target gear
+ *
+ * Returns 0 on success, negative error code otherwise
+ */
+static int ufshcd_apply_tx_eq_settings(struct ufs_hba *hba,
+ struct ufshcd_tx_eq_params *params,
+ u32 gear)
+{
+ u32 setting;
+ int ret;
+
+ /* Compose settings for Host's TX Lanes */
+ setting = ufshcd_compose_tx_eq_setting(params->host, params->tx_lanes);
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(pa_tx_eq_setting[gear]), setting);
+ if (ret)
+ return ret;
+
+ /* Compose settings for Device's TX Lanes */
+ setting = ufshcd_compose_tx_eq_setting(params->device, params->rx_lanes);
+ ret = ufshcd_dme_peer_set(hba, UIC_ARG_MIB(pa_tx_eq_setting[gear]),
+ setting);
+ if (ret)
+ return ret;
+
+ /* Configure Pre-Coding */
+ if (gear >= UFS_HS_G6) {
+ ret = ufshcd_configure_precoding(hba, params);
+ if (ret) {
+ dev_err(hba->dev, "Failed to configure pre-coding: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * ufshcd_evaluate_fom - Update TX EQ params based on FOM results
+ * @hba: per adapter instance
+ * @params: TX EQ parameters data structure
+ * @h_iter: host TX EQTR iterator data structure
+ * @d_iter: device TX EQTR iterator data structure
+ *
+ * Evaluate FOM results, update host and device TX EQ params if FOM results are
+ * improved, and record TX EQTR results.
+ */
+static void ufshcd_evaluate_fom(struct ufs_hba *hba,
+ struct ufshcd_tx_eq_params *params,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter)
+{
+ u32 preshoot, deemphasis, fom_value;
+ bool precode_en;
+ int lane;
+
+ for (lane = 0; h_iter->is_new && lane < h_iter->num_lanes; lane++) {
+ preshoot = h_iter->preshoot;
+ deemphasis = h_iter->deemphasis;
+ fom_value = h_iter->fom[lane] & RX_FOM_VALUE_MASK;
+ precode_en = h_iter->fom[lane] & RX_FOM_PRECODING_EN_MASK;
+
+ /* Record host TX EQTR result */
+ params->host_eqtr_record[lane][preshoot][deemphasis] = h_iter->fom[lane];
+
+ /* Check if FOM is improved for host's TX Lanes */
+ if (fom_value > params->host[lane].fom_val) {
+ params->host[lane].preshoot = preshoot;
+ params->host[lane].deemphasis = deemphasis;
+ params->host[lane].fom_val = fom_value;
+ params->host[lane].precode_en = precode_en;
+ }
+
+ dev_dbg(hba->dev, "Host TX EQTR: Lane %u - PreShoot %u, DeEmphasis %u, FOM value %u, PreCodeEn %d\n",
+ lane, preshoot, deemphasis, fom_value, precode_en);
+ }
+
+ for (lane = 0; d_iter->is_new && lane < d_iter->num_lanes; lane++) {
+ preshoot = d_iter->preshoot;
+ deemphasis = d_iter->deemphasis;
+ fom_value = d_iter->fom[lane] & RX_FOM_VALUE_MASK;
+ precode_en = d_iter->fom[lane] & RX_FOM_PRECODING_EN_MASK;
+
+ /* Record device TX EQTR result */
+ params->device_eqtr_record[lane][preshoot][deemphasis] = d_iter->fom[lane];
+
+ /* Check if FOM is improved for Device's TX Lanes */
+ if (fom_value > params->device[lane].fom_val) {
+ params->device[lane].preshoot = preshoot;
+ params->device[lane].deemphasis = deemphasis;
+ params->device[lane].fom_val = fom_value;
+ params->device[lane].precode_en = precode_en;
+ }
+
+ dev_dbg(hba->dev, "Device TX EQTR: Lane %u - PreShoot %u, DeEmphasis %u, FOM value %u, PreCodeEn %d\n",
+ lane, preshoot, deemphasis, fom_value, precode_en);
+ }
+}
+
+/**
+ * ufshcd_get_rx_fom - Get Figure of Merit (FOM) for both sides
+ * @hba: per adapter instance
+ * @pwr_mode: target power mode containing gear and rate information
+ * @h_iter: host TX EQTR iterator data structure
+ * @d_iter: device TX EQTR iterator data structure
+ *
+ * Returns 0 on success, negative error code otherwise
+ */
+static int ufshcd_get_rx_fom(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter)
+{
+ int lane, ret;
+
+ /* Get FOM of host's TX lanes from device's RX_FOM. */
+ for (lane = 0; lane < h_iter->num_lanes; lane++) {
+ ret = ufshcd_dme_peer_get(hba, UIC_ARG_MIB_SEL(RX_FOM,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ &h_iter->fom[lane]);
+ if (ret)
+ return ret;
+ }
+
+ /* Get FOM of device's TX lanes from host's RX_FOM. */
+ for (lane = 0; lane < d_iter->num_lanes; lane++) {
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(RX_FOM,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ &d_iter->fom[lane]);
+ if (ret)
+ return ret;
+ }
+
+ ret = ufshcd_vops_get_rx_fom(hba, pwr_mode, h_iter, d_iter);
+ if (ret)
+ dev_err(hba->dev, "Failed to get FOM via vops: %d\n", ret);
+
+ return ret;
+}
+
+/**
+ * tx_eqtr_iter_set - Set TX EQTR iterator with supported settings
+ * @iter: TX EQTR iterator data structure
+ * @preshoot: PreShoot value
+ * @deemphasis: DeEmphasis value
+ *
+ * This function validates whether the requested PreShoot and DeEmphasis
+ * combination is supported or not.
+ */
+static inline void tx_eqtr_iter_set(struct tx_eqtr_iter *iter,
+ unsigned int preshoot,
+ unsigned int deemphasis)
+{
+ if (!test_bit(preshoot, &iter->preshoot_bitmap) ||
+ !test_bit(deemphasis, &iter->deemphasis_bitmap)) {
+ iter->is_new = false;
+ return;
+ }
+
+ if (use_txeq_presets) {
+ bool is_preset = false;
+
+ for (int i = 0; i < UFS_TX_EQ_PRESET_MAX; i++) {
+ if (!txeq_presets_selected[i])
+ continue;
+
+ if (preshoot == ufs_tx_eq_preset[i].preshoot &&
+ deemphasis == ufs_tx_eq_preset[i].deemphasis) {
+ is_preset = true;
+ break;
+ }
+ }
+
+ if (!is_preset) {
+ iter->is_new = false;
+ return;
+ }
+ }
+
+ iter->preshoot = preshoot;
+ iter->deemphasis = deemphasis;
+ iter->is_new = true;
+}
+
+/**
+ * tx_eqtr_iter_update() - Update TX Equalization training iterators
+ * @preshoot: PreShoot value
+ * @deemphasis: DeEmphasis value
+ * @h_iter: Host TX EQTR iterator data structure
+ * @d_iter: Device TX EQTR iterator data structure
+ *
+ * Updates host and device TX Equalization training iterators with the
+ * provided PreShoot and DeEmphasis.
+ *
+ * Return: true if host and/or device TX Equalization training iterator has
+ * been updated to the provided PreShoot and DeEmphasis, false otherwise.
+ */
+static inline bool tx_eqtr_iter_update(unsigned int preshoot,
+ unsigned int deemphasis,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter)
+{
+ tx_eqtr_iter_set(h_iter, preshoot, deemphasis);
+ tx_eqtr_iter_set(d_iter, preshoot, deemphasis);
+
+ return h_iter->is_new || d_iter->is_new;
+}
+
+/**
+ * ufshcd_tx_eqtr_data_init - Initialize TX EQTR iteration data and TX EQ params
+ * @hba: per adapter instance
+ * @params: TX EQ parameters data structure
+ * @h_iter: host TX EQTR iterator data structure
+ * @d_iter: device TX EQTR iterator data structure
+ *
+ * This function initializes the TX EQTR iterator structures for both host and
+ * device by reading their TX equalization capabilities and setting up the
+ * iteration state. The capabilities are cached in the hba structure to avoid
+ * redundant DME operations in subsequent calls. The iterator structures are
+ * used by tx_eqtr_iter_load() to systematically iterate through all possible TX
+ * Equalization setting combinations during the TX EQTR procedure. This function
+ * also clears the host and device parameter arrays in the TX EQ params data
+ * structure, so that the TX EQTR procedure does not compare FOM values to stale
+ * FOM values.
+ *
+ * Returns 0 on success, negative error code otherwise
+ */
+static int ufshcd_tx_eqtr_data_init(struct ufs_hba *hba,
+ struct ufshcd_tx_eq_params *params,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter)
+{
+ u32 cap;
+ int ret;
+
+ if (!hba->host_preshoot_cap) {
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(TX_HS_PRESHOOT_SETTING_CAP), &cap);
+ if (ret)
+ return ret;
+
+ hba->host_preshoot_cap = cap & TX_EQTR_CAP_MASK;
+ }
+
+ if (!hba->host_deemphasis_cap) {
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(TX_HS_DEEMPHASIS_SETTING_CAP), &cap);
+ if (ret)
+ return ret;
+
+ hba->host_deemphasis_cap = cap & TX_EQTR_CAP_MASK;
+ }
+
+ if (!hba->device_preshoot_cap) {
+ ret = ufshcd_dme_peer_get(hba, UIC_ARG_MIB(TX_HS_PRESHOOT_SETTING_CAP), &cap);
+ if (ret)
+ return ret;
+
+ hba->device_preshoot_cap = cap & TX_EQTR_CAP_MASK;
+ }
+
+ if (!hba->device_deemphasis_cap) {
+ ret = ufshcd_dme_peer_get(hba, UIC_ARG_MIB(TX_HS_DEEMPHASIS_SETTING_CAP), &cap);
+ if (ret)
+ return ret;
+
+ hba->device_deemphasis_cap = cap & TX_EQTR_CAP_MASK;
+ }
+
+ memset(params->host, 0, sizeof(params->host));
+ memset(params->device, 0, sizeof(params->device));
+ memset(params->host_eqtr_record, 0xFF, sizeof(params->host_eqtr_record));
+ memset(params->device_eqtr_record, 0xFF, sizeof(params->device_eqtr_record));
+
+ memset(h_iter, 0, sizeof(struct tx_eqtr_iter));
+ memset(d_iter, 0, sizeof(struct tx_eqtr_iter));
+
+ h_iter->num_lanes = params->tx_lanes;
+ d_iter->num_lanes = params->rx_lanes;
+
+ /*
+ * Support PreShoot & DeEmphasis of value 0 is mandatory, hence they are
+ * not reflected in PreShoot/DeEmphasis capabilities. Left shift the
+ * capability bitmap by 1 and set bit[0] to reflect value 0 is
+ * supported, such that test_bit() can be used later for convenience.
+ */
+ h_iter->preshoot_bitmap = (hba->host_preshoot_cap << 0x1) | 0x1;
+ h_iter->deemphasis_bitmap = (hba->host_deemphasis_cap << 0x1) | 0x1;
+ d_iter->preshoot_bitmap = (hba->device_preshoot_cap << 0x1) | 0x1;
+ d_iter->deemphasis_bitmap = (hba->device_deemphasis_cap << 0x1) | 0x1;
+
+ return ret;
+}
+
+/**
+ * adapt_cap_to_t_adapt - Calculate TAdapt from adapt capability
+ * @adapt_cap: Adapt capability
+ *
+ * For NRZ:
+ * IF (ADAPT_range = FINE)
+ * TADAPT = 650 x (ADAPT_length + 1)
+ * ELSE (IF ADAPT_range = COARSE)
+ * TADAPT = 650 x 2^ADAPT_length
+ *
+ * Returns calculated TAdapt value in term of Unit Intervals (UI)
+ */
+static inline u64 adapt_cap_to_t_adapt(u32 adapt_cap)
+{
+ u64 tadapt;
+ u8 adapt_range = adapt_cap & ADAPT_RANGE_MASK;
+ u8 adapt_length = adapt_cap & ADAPT_LENGTH_MASK;
+
+ if (!adapt_range)
+ tadapt = TADAPT_FACTOR * (adapt_length + 1);
+ else
+ tadapt = TADAPT_FACTOR * (1 << adapt_length);
+
+ return tadapt;
+}
+
+/**
+ * adapt_cap_to_t_adapt_l0l3 - Calculate TAdapt_L0_L3 from adapt capability
+ * @adapt_cap: Adapt capability
+ *
+ * For PAM-4:
+ * IF (ADAPT_range = FINE)
+ * TADAPT_L0_L3 = 2^9 x ADAPT_length
+ * ELSE IF (ADAPT_range = COARSE)
+ * TADAPT_L0_L3 = 2^9 x (2^ADAPT_length)
+ *
+ * Returns calculated TAdapt value in term of Unit Intervals (UI)
+ */
+static inline u64 adapt_cap_to_t_adapt_l0l3(u32 adapt_cap)
+{
+ u64 tadapt;
+ u8 adapt_range = adapt_cap & ADAPT_RANGE_MASK;
+ u8 adapt_length = adapt_cap & ADAPT_LENGTH_MASK;
+
+ if (!adapt_range)
+ tadapt = TADAPT_L0L3_FACTOR * adapt_length;
+ else
+ tadapt = TADAPT_L0L3_FACTOR * (1 << adapt_length);
+
+ return tadapt;
+}
+
+/**
+ * adapt_cap_to_t_adapt_l0l1l2l3 - Calculate TAdapt_L0_L1_L2_L3 from adapt capability
+ * @adapt_cap: Adapt capability
+ *
+ * For PAM-4:
+ * IF (ADAPT_range_L0_L1_L2_L3 = FINE)
+ * TADAPT_L0_L1_L2_L3 = 2^15 x (ADAPT_length_L0_L1_L2_L3 + 1)
+ * ELSE IF (ADAPT_range_L0_L1_L2_L3 = COARSE)
+ * TADAPT_L0_L1_L2_L3 = 2^15 x 2^ADAPT_length_L0_L1_L2_L3
+ *
+ * Returns calculated TAdapt value in term of Unit Intervals (UI)
+ */
+static inline u64 adapt_cap_to_t_adapt_l0l1l2l3(u32 adapt_cap)
+{
+ u64 tadapt;
+ u8 adapt_range = adapt_cap & ADAPT_RANGE_MASK;
+ u8 adapt_length = adapt_cap & ADAPT_LENGTH_MASK;
+
+ if (!adapt_range)
+ tadapt = TADAPT_L0L1L2L3_FACTOR * (adapt_length + 1);
+ else
+ tadapt = TADAPT_L0L1L2L3_FACTOR * (1 << adapt_length);
+
+ return tadapt;
+}
+
+/**
+ * ufshcd_setup_tx_eqtr_adapt_length - Setup TX adapt length for EQTR
+ * @hba: per adapter instance
+ * @gear: target gear for EQTR
+ *
+ * This function determines and configures the proper TX adapt length (TAdapt)
+ * for the TX EQTR procedure based on the target gear and RX adapt capabilities
+ * of both host and device.
+ *
+ * Guidelines from MIPI UniPro v3.0 spec - select the minimum Adapt Length for
+ * the Equalization Training procedure based on the following conditions:
+ *
+ * If the target High-Speed Gear n is HS-G4 or HS-G5:
+ * PA_TxAdaptLength_EQTR[7:0] >= Max (10us, RX_HS_Gn_ADAPT_INITIAL_Capability,
+ * PA_PeerRxHsGnAdaptInitial)
+ * PA_TxAdaptLength_EQTR[7:0] shall be shorter than PACP_REQUEST_TIMER (10ms)
+ * PA_TxAdaptLength_EQTR[15:8] is not relevant for HS-G4 and HS-G5. This field
+ * is set to 255 (reserved value).
+ *
+ * If the target High-Speed Gear n is HS-G6:
+ * PA_TxAdapthLength_EQTR >= 10us
+ * PA_TxAdapthLength_EQTR[7:0] >= Max (RX_HS_G6_ADAPT_INITIAL_Capability,
+ * PA_PeerRxHsG6AdaptInitialL0L3)
+ * PA_TxAdapthLength_EQTR[15:8] >= Max (RX_HS_G6_ADAPT_INITIAL_L0_L1_L2_L3_Capability,
+ * PA_PeerRxHsG6AdaptInitialL0L1L2L3)
+ * PA_TxAdaptLength_EQTR shall be shorter than PACP_REQUEST_TIMER value of 10ms.
+ *
+ * Since adapt capabilities encode both range (fine/coarse) and length values,
+ * direct comparison is not possible. This function converts adapt capabilities
+ * to actual time durations in Unit Intervals (UI) using the Adapt time
+ * calculation formular in M-PHY v6.0 spec (Table 8), then selects the maximum
+ * to ensure both host and device use adequate TX adapt length.
+ *
+ * Returns 0 on success, negative error code otherwise
+ */
+static int ufshcd_setup_tx_eqtr_adapt_length(struct ufs_hba *hba, u32 gear)
+{
+ struct ufshcd_tx_eq_params *params = &hba->tx_eq_params[gear - 1];
+ u32 adapt_eqtr;
+ int ret;
+
+ if (params->saved_adapt_eqtr)
+ goto set_adapt_eqtr;
+
+ if (gear == UFS_HS_G4 || gear == UFS_HS_G5) {
+ u64 t_adapt, t_adapt_local, t_adapt_peer;
+ u32 adapt_cap_local, adapt_cap_peer, adapt_length;
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(rx_adapt_initial_cap[gear],
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)),
+ &adapt_cap_local);
+ if (ret)
+ return ret;
+
+ if (adapt_cap_local > ADAPT_LENGTH_MAX) {
+ dev_err(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_CAP (0x%x) exceeds max\n",
+ gear, adapt_cap_local);
+ return -EINVAL;
+ }
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(pa_peer_rx_adapt_initial[gear]),
+ &adapt_cap_peer);
+ if (ret)
+ return ret;
+
+ if (adapt_cap_peer > ADAPT_LENGTH_MAX) {
+ dev_err(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_CAP (0x%x) exceeds max\n",
+ gear, adapt_cap_peer);
+ return -EINVAL;
+ }
+
+ t_adapt_local = adapt_cap_to_t_adapt(adapt_cap_local);
+ t_adapt_peer = adapt_cap_to_t_adapt(adapt_cap_peer);
+
+ dev_dbg(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_CAP = 0x%x\n",
+ gear, adapt_cap_local);
+ dev_dbg(hba->dev, "peer RX_HS_G%u_ADAPT_INITIAL_CAP = 0x%x\n",
+ gear, adapt_cap_peer);
+ dev_dbg(hba->dev, "t_adapt_local = %llu UI, t_adapt_peer = %llu UI\n",
+ t_adapt_local, t_adapt_peer);
+
+ t_adapt = max(t_adapt_local, t_adapt_peer);
+ adapt_length = (t_adapt == t_adapt_local) ?
+ adapt_cap_local : adapt_cap_peer;
+
+ dev_dbg(hba->dev, "TAdapt %llu UI selected for TX EQTR\n",
+ t_adapt);
+
+ if (gear == UFS_HS_G4 && t_adapt < TX_EQTR_HS_G4_MIN_T_ADAPT) {
+ dev_dbg(hba->dev, "TAdapt %llu UI is too short for TX EQTR for HS-G%u, use default Adapt 0x%x\n",
+ t_adapt, gear, TX_EQTR_HS_G4_ADAPT_DEFAULT);
+ adapt_length = TX_EQTR_HS_G4_ADAPT_DEFAULT;
+ } else if (gear == UFS_HS_G5 && t_adapt < TX_EQTR_HS_G5_MIN_T_ADAPT) {
+ dev_dbg(hba->dev, "TAdapt %llu UI is too short for TX EQTR for HS-G%u, use default Adapt 0x%x\n",
+ t_adapt, gear, TX_EQTR_HS_G5_ADAPT_DEFAULT);
+ adapt_length = TX_EQTR_HS_G5_ADAPT_DEFAULT;
+ }
+
+ adapt_eqtr = (adapt_length << TX_EQTR_ADAPT_LENGTH_SHIFT) |
+ (TX_EQTR_ADAPT_RESERVED << TX_EQTR_ADAPT_LENGTH_L0L1L2L3_SHIFT);
+ } else if (gear == UFS_HS_G6) {
+ u64 t_adapt, t_adapt_l0l3, t_adapt_l0l3_local, t_adapt_l0l3_peer;
+ u64 t_adapt_l0l1l2l3, t_adapt_l0l1l2l3_local, t_adapt_l0l1l2l3_peer;
+ u32 adapt_l0l3_cap_local, adapt_l0l3_cap_peer, adapt_length_l0l3;
+ u32 adapt_l0l1l2l3_cap_local, adapt_l0l1l2l3_cap_peer, adapt_length_l0l1l2l3;
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(rx_adapt_initial_cap[gear],
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)),
+ &adapt_l0l3_cap_local);
+ if (ret)
+ return ret;
+
+ if (adapt_l0l3_cap_local > ADAPT_L0L3_LENGTH_MAX) {
+ dev_err(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_CAP (0x%x) exceeds max\n",
+ gear, adapt_l0l3_cap_local);
+ return -EINVAL;
+ }
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(pa_peer_rx_adapt_initial[gear]),
+ &adapt_l0l3_cap_peer);
+ if (ret)
+ return ret;
+
+ if (adapt_l0l3_cap_peer > ADAPT_L0L3_LENGTH_MAX) {
+ dev_err(hba->dev, "peer RX_HS_G%u_ADAPT_INITIAL_CAP (0x%x) exceeds max\n",
+ gear, adapt_l0l3_cap_peer);
+ return -EINVAL;
+ }
+
+ t_adapt_l0l3_local = adapt_cap_to_t_adapt_l0l3(adapt_l0l3_cap_local);
+ t_adapt_l0l3_peer = adapt_cap_to_t_adapt_l0l3(adapt_l0l3_cap_peer);
+
+ dev_dbg(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_CAP = 0x%x\n",
+ gear, adapt_l0l3_cap_local);
+ dev_dbg(hba->dev, "peer RX_HS_G%u_ADAPT_INITIAL_CAP = 0x%x\n",
+ gear, adapt_l0l3_cap_peer);
+ dev_dbg(hba->dev, "t_adapt_l0l3_local = %llu UI, t_adapt_l0l3_peer = %llu UI\n",
+ t_adapt_l0l3_local, t_adapt_l0l3_peer);
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(RX_HS_G6_ADAPT_INITIAL_L0L1L2L3_CAP,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)),
+ &adapt_l0l1l2l3_cap_local);
+ if (ret)
+ return ret;
+
+ if (adapt_l0l1l2l3_cap_local > ADAPT_L0L1L2L3_LENGTH_MAX) {
+ dev_err(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_L0L1L2L3_CAP (0x%x) exceeds max\n",
+ gear, adapt_l0l1l2l3_cap_local);
+ return -EINVAL;
+ }
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTINITIALL0L1L2L3),
+ &adapt_l0l1l2l3_cap_peer);
+ if (ret)
+ return ret;
+
+ if (adapt_l0l1l2l3_cap_peer > ADAPT_L0L1L2L3_LENGTH_MAX) {
+ dev_err(hba->dev, "peer RX_HS_G%u_ADAPT_INITIAL_L0L1L2L3_CAP (0x%x) exceeds max\n",
+ gear, adapt_l0l1l2l3_cap_peer);
+ return -EINVAL;
+ }
+
+ t_adapt_l0l1l2l3_local = adapt_cap_to_t_adapt_l0l1l2l3(adapt_l0l1l2l3_cap_local);
+ t_adapt_l0l1l2l3_peer = adapt_cap_to_t_adapt_l0l1l2l3(adapt_l0l1l2l3_cap_peer);
+
+ dev_dbg(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_L0L1L2L3_CAP = 0x%x\n",
+ gear, adapt_l0l1l2l3_cap_local);
+ dev_dbg(hba->dev, "peer RX_HS_G%u_ADAPT_INITIAL_L0L1L2L3_CAP = 0x%x\n",
+ gear, adapt_l0l1l2l3_cap_peer);
+ dev_dbg(hba->dev, "t_adapt_l0l1l2l3_local = %llu UI, t_adapt_l0l1l2l3_peer = %llu UI\n",
+ t_adapt_l0l1l2l3_local, t_adapt_l0l1l2l3_peer);
+
+ t_adapt_l0l3 = max(t_adapt_l0l3_local, t_adapt_l0l3_peer);
+ adapt_length_l0l3 = (t_adapt_l0l3 == t_adapt_l0l3_local) ?
+ adapt_l0l3_cap_local : adapt_l0l3_cap_peer;
+
+ t_adapt_l0l1l2l3 = max(t_adapt_l0l1l2l3_local, t_adapt_l0l1l2l3_peer);
+ adapt_length_l0l1l2l3 = (t_adapt_l0l1l2l3 == t_adapt_l0l1l2l3_local) ?
+ adapt_l0l1l2l3_cap_local : adapt_l0l1l2l3_cap_peer;
+
+ t_adapt = t_adapt_l0l3 + t_adapt_l0l1l2l3;
+
+ dev_dbg(hba->dev, "TAdapt %llu PAM-4 UI selected for TX EQTR\n",
+ t_adapt);
+
+ if (t_adapt < TX_EQTR_HS_G6_MIN_T_ADAPT) {
+ dev_dbg(hba->dev, "TAdapt %llu UI is too short for TX EQTR for HS-G%u, use default Adapt 0x%x\n",
+ t_adapt, gear, TX_EQTR_HS_G6_ADAPT_DEFAULT);
+ adapt_length_l0l3 = TX_EQTR_HS_G6_ADAPT_DEFAULT;
+ }
+
+ adapt_eqtr = (adapt_length_l0l3 << TX_EQTR_ADAPT_LENGTH_SHIFT) |
+ (adapt_length_l0l1l2l3 << TX_EQTR_ADAPT_LENGTH_L0L1L2L3_SHIFT);
+ } else {
+ return -EINVAL;
+ }
+
+ params->saved_adapt_eqtr = adapt_eqtr;
+
+set_adapt_eqtr:
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXADAPTLENGTH_EQTR),
+ params->saved_adapt_eqtr);
+ if (ret)
+ dev_err(hba->dev, "Failed to set adapt length for TX EQTR: %d\n",
+ ret);
+ else
+ dev_dbg(hba->dev, "PA_TXADAPTLENGTH_EQTR configured to 0x%08x\n",
+ params->saved_adapt_eqtr);
+
+ return ret;
+}
+
+/**
+ * ufshcd_compose_tx_eqtr_setting - Compose TX EQTR setting
+ * @iter: TX EQTR iterator data structure
+ *
+ * Returns composed TX EQTR setting, same setting is used for all active lanes
+ */
+static inline u32 ufshcd_compose_tx_eqtr_setting(struct tx_eqtr_iter *iter)
+{
+ u32 setting = 0;
+ int lane;
+
+ for (lane = 0; lane < iter->num_lanes; lane++) {
+ setting |= TX_HS_PRESHOOT_BITS(lane, iter->preshoot);
+ setting |= TX_HS_DEEMPHASIS_BITS(lane, iter->deemphasis);
+ }
+
+ return setting;
+}
+
+/**
+ * ufshcd_apply_tx_eqtr_settings - Apply TX EQTR setting
+ * @hba: per adapter instance
+ * @pwr_mode: target power mode containing gear and rate information
+ * @h_iter: host TX EQTR iterator data structure
+ * @d_iter: device TX EQTR iterator data structure
+ *
+ * Returns 0 on success, negative error code otherwise
+ */
+static int ufshcd_apply_tx_eqtr_settings(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter)
+{
+ u32 setting;
+ int ret;
+
+ setting = ufshcd_compose_tx_eqtr_setting(h_iter);
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXEQTRSETTING), setting);
+ if (ret)
+ return ret;
+
+ setting = ufshcd_compose_tx_eqtr_setting(d_iter);
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PEERTXEQTRSETTING), setting);
+ if (ret)
+ return ret;
+
+ ret = ufshcd_vops_apply_tx_eqtr_settings(hba, pwr_mode, h_iter, d_iter);
+
+ return ret;
+}
+
+/**
+ * ufshcd_tx_eqtr_result_examine() - Examine TX Equalization training results
+ * @old_params: Pointer to the previously known TX Equalization parameters
+ * @new_params: Pointer to the newly trained TX Equalization parameters
+ *
+ * Checks the Figure of Merit (FOM) for each lane in the newly trained TX
+ * Equalization parameters. If a lane in the newly trained TX Equalization
+ * parameters has an FOM of 0, it restores the TX Equalization settings from
+ * the old_params for that specific lane.
+ */
+static inline void
+ufshcd_tx_eqtr_result_examine(struct ufshcd_tx_eq_params *old_params,
+ struct ufshcd_tx_eq_params *new_params)
+{
+ int lane;
+
+ if (!old_params->is_valid)
+ return;
+
+ for (lane = 0; lane < new_params->tx_lanes; lane++)
+ if (new_params->host[lane].fom_val == 0)
+ new_params->host[lane] = old_params->host[lane];
+
+ for (lane = 0; lane < new_params->rx_lanes; lane++)
+ if (new_params->device[lane].fom_val == 0)
+ new_params->device[lane] = old_params->device[lane];
+}
+
+/**
+ * __ufshcd_tx_eqtr - TX Equalization Training (EQTR) procedure
+ * @hba: per adapter instance
+ * @params: TX EQ parameters data structure
+ * @pwr_mode: target power mode containing gear and rate information
+ *
+ * This function implements the complete TX EQTR procedure as defined in UFSHCI
+ * v5.0 specification. It iterates through all possible combinations of PreShoot
+ * and DeEmphasis settings to find the optimal TX Equalization settings for all
+ * active lanes.
+ *
+ * Returns 0 on success, negative error code otherwise
+ */
+static int __ufshcd_tx_eqtr(struct ufs_hba *hba,
+ struct ufshcd_tx_eq_params *params,
+ struct ufs_pa_layer_attr *pwr_mode)
+{
+ struct ufshcd_tx_eq_params *new_params __free(kfree) =
+ kzalloc(sizeof(*new_params), GFP_KERNEL);
+ struct tx_eqtr_iter h_iter, d_iter;
+ unsigned int preshoot, deemphasis;
+ u32 gear = pwr_mode->gear_tx;
+ ktime_t start;
+ int ret;
+
+ if (!new_params)
+ return -ENOMEM;
+
+ dev_info(hba->dev, "Starting TX EQTR procedure for HS-G%u, Rate-%s, active RX lanes: %u, active TX Lanes: %u\n",
+ gear, UFS_HS_RATE_STRING(pwr_mode->hs_rate),
+ params->rx_lanes, params->tx_lanes);
+
+ start = ktime_get();
+
+ /* Step 1 - Determine the TX Adapt Length for EQTR */
+ ret = ufshcd_setup_tx_eqtr_adapt_length(hba, gear);
+ if (ret) {
+ dev_err(hba->dev, "Failed to setup adapt length: %d\n", ret);
+ return ret;
+ }
+
+ /* Step 2 - Determine TX Equalization setting capabilities */
+ memcpy(new_params, params, sizeof(struct ufshcd_tx_eq_params));
+ ret = ufshcd_tx_eqtr_data_init(hba, new_params, &h_iter, &d_iter);
+ if (ret) {
+ dev_err(hba->dev, "Failed to init TX EQTR data: %d\n", ret);
+ return ret;
+ }
+
+ /* TX EQTR main loop */
+ for (preshoot = 0; preshoot < TX_HS_NUM_PRESHOOT; preshoot++) {
+ for (deemphasis = 0; deemphasis < TX_HS_NUM_DEEMPHASIS; deemphasis++) {
+ if (!tx_eqtr_iter_update(preshoot, deemphasis, &h_iter, &d_iter))
+ continue;
+
+ /* Step 3 - Apply TX EQTR settings */
+ ret = ufshcd_apply_tx_eqtr_settings(hba, pwr_mode, &h_iter, &d_iter);
+ if (ret) {
+ dev_err(hba->dev, "Failed to apply TX EQTR settings: %d\n",
+ ret);
+ return ret;
+ }
+
+ /* Step 4 - Perform UIC TX EQTR */
+ ret = ufshcd_uic_tx_eqtr(hba, gear);
+ if (ret) {
+ dev_err(hba->dev, "Failed to start TX EQTR procedure for target gear %u: %d\n",
+ gear, ret);
+ return ret;
+ }
+
+ /* Step 5 - Get FOM */
+ ret = ufshcd_get_rx_fom(hba, pwr_mode, &h_iter, &d_iter);
+ if (ret) {
+ dev_err(hba->dev, "Failed to get RX_FOM: %d\n",
+ ret);
+ return ret;
+ }
+
+ ufshcd_evaluate_fom(hba, new_params, &h_iter, &d_iter);
+ }
+ }
+
+ dev_info(hba->dev, "TX EQTR procedure completed successfully! Time elapsed: %llu ms\n",
+ ktime_to_ms(ktime_sub(ktime_get(), start)));
+
+ ufshcd_tx_eqtr_result_examine(params, new_params);
+
+ memcpy(params, new_params, sizeof(struct ufshcd_tx_eq_params));
+ params->last_eqtr_ts = ktime_get();
+ params->num_eqtr_records++;
+
+ return ret;
+}
+
+/**
+ * ufshcd_tx_eqtr_prepare - Prepare UFS link for TX EQTR procedure
+ * @hba: per adapter instance
+ * @pwr_mode: target power mode containing gear and rate
+ *
+ * This function prepares the UFS link for TX Equalization Training (EQTR) by
+ * establishing the proper initial conditions required by the EQTR procedure.
+ * It ensures that EQTR starts from the most reliable Power Mode (HS-G1) with
+ * all connected lanes activated and sets host TX HS Adapt Type to INITIAL.
+ *
+ * Returns 0 on successful preparation, negative error code on failure
+ */
+static int ufshcd_tx_eqtr_prepare(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode)
+{
+ struct ufs_pa_layer_attr pwr_mode_hs_g1 = {
+ /* TX EQTR shall be initiated from the most reliable HS-G1 */
+ .gear_rx = UFS_HS_G1,
+ .gear_tx = UFS_HS_G1,
+ .lane_rx = pwr_mode->lane_rx,
+ .lane_tx = pwr_mode->lane_tx,
+ .pwr_rx = FAST_MODE,
+ .pwr_tx = FAST_MODE,
+ /* Use the target power mode's HS rate */
+ .hs_rate = pwr_mode->hs_rate,
+ };
+ u32 rate = pwr_mode->hs_rate;
+ int ret;
+
+ /* Change power mode to HS-G1, activate all connected lanes. */
+ ret = ufshcd_change_power_mode(hba, &pwr_mode_hs_g1,
+ UFSHCD_PMC_POLICY_DONT_FORCE);
+ if (ret) {
+ dev_err(hba->dev, "TX EQTR: Failed to change power mode to HS-G1, Rate-%s: %d\n",
+ UFS_HS_RATE_STRING(rate), ret);
+ return ret;
+ }
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXHSADAPTTYPE),
+ PA_INITIAL_ADAPT);
+ if (ret)
+ dev_err(hba->dev, "TX EQTR: Failed to set Host Adapt type to INITIAL: %d\n",
+ ret);
+
+ return ret;
+}
+
+static void ufshcd_tx_eqtr_unprepare(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode)
+{
+ int err;
+
+ if (pwr_mode->pwr_rx == SLOWAUTO_MODE || pwr_mode->hs_rate == 0)
+ return;
+
+ err = ufshcd_change_power_mode(hba, pwr_mode,
+ UFSHCD_PMC_POLICY_DONT_FORCE);
+ if (err)
+ dev_err(hba->dev, "%s: Failed to restore Power Mode: %d\n",
+ __func__, err);
+}
+
+/**
+ * ufshcd_tx_eqtr - Perform TX EQTR procedures with vops callbacks
+ * @hba: per adapter instance
+ * @params: TX EQ parameters data structure to populate
+ * @pwr_mode: target power mode containing gear and rate information
+ *
+ * This is the main entry point for performing TX Equalization Training (EQTR)
+ * procedure as defined in UFSCHI v5.0 specification. It serves as a wrapper
+ * around __ufshcd_tx_eqtr() to provide vops support through the variant
+ * operations framework.
+ *
+ * Returns 0 on success, negative error code on failure
+ */
+static int ufshcd_tx_eqtr(struct ufs_hba *hba,
+ struct ufshcd_tx_eq_params *params,
+ struct ufs_pa_layer_attr *pwr_mode)
+{
+ struct ufs_pa_layer_attr old_pwr_info;
+ u32 gear = pwr_mode->gear_tx;
+ int ret;
+
+ if (gear < UFS_HS_G4 || gear > UFS_HS_G6) {
+ dev_err(hba->dev, "TX EQTR is not implemented for HS-G%u\n",
+ gear);
+ return -EINVAL;
+ }
+
+ params->rx_lanes = pwr_mode->lane_rx;
+ params->tx_lanes = pwr_mode->lane_tx;
+
+ memcpy(&old_pwr_info, &hba->pwr_info, sizeof(struct ufs_pa_layer_attr));
+
+ ret = ufshcd_tx_eqtr_prepare(hba, pwr_mode);
+ if (ret) {
+ dev_err(hba->dev, "Failed to prepare TX EQTR: %d\n", ret);
+ goto out;
+ }
+
+ ret = ufshcd_vops_tx_eqtr_notify(hba, PRE_CHANGE, pwr_mode);
+ if (ret)
+ goto out;
+
+ ret = __ufshcd_tx_eqtr(hba, params, pwr_mode);
+ if (ret)
+ goto out;
+
+ ret = ufshcd_vops_tx_eqtr_notify(hba, POST_CHANGE, pwr_mode);
+ if (ret)
+ goto out;
+
+out:
+ if (ret)
+ ufshcd_tx_eqtr_unprepare(hba, &old_pwr_info);
+
+ return ret;
+}
+
+/**
+ * ufshcd_config_tx_eq_settings - Configure TX Equalization settings
+ * @hba: per adapter instance
+ * @pwr_mode: target power mode containing gear and rate information
+ *
+ * This function finds and sets the TX Equalization settings for the given
+ * target power mode.
+ *
+ * Returns 0 on success, error code otherwise
+ */
+int ufshcd_config_tx_eq_settings(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode)
+{
+ struct ufshcd_tx_eq_params *params;
+ u32 gear, rate;
+
+ if (!ufshcd_is_tx_eq_supported(hba) || !use_adaptive_txeq)
+ return 0;
+
+ if (!pwr_mode) {
+ dev_err(hba->dev, "%s: Target power mode is NULL\n", __func__);
+ return -EINVAL;
+ }
+
+ gear = pwr_mode->gear_tx;
+ rate = pwr_mode->hs_rate;
+ params = &hba->tx_eq_params[gear - 1];
+
+ if (gear < UFS_HS_G1 || gear >= UFS_HS_GEAR_MAX) {
+ dev_err(hba->dev, "Invalid HS-Gear (%u) for TX Equalization\n",
+ gear);
+ return -EINVAL;
+ } else if (gear < adaptive_txeq_gear) {
+ return 0;
+ }
+
+ if (rate != PA_HS_MODE_A && rate != PA_HS_MODE_B) {
+ dev_err(hba->dev, "Invalid HS-Rate (%u) for TX Equalization\n",
+ rate);
+ return -EINVAL;
+ }
+
+ /* TX EQTR is supported for HS-G4 and higher Gears */
+ if (gear < UFS_HS_G4)
+ goto apply_tx_eq_settings;
+
+ if (!params->is_valid) {
+ int ret;
+
+ ret = ufshcd_tx_eqtr(hba, params, pwr_mode);
+ if (ret) {
+ dev_err(hba->dev, "Failed to train TX Equalization for HS-G%u, Rate-%s: %d\n",
+ gear, UFS_HS_RATE_STRING(rate), ret);
+ return ret;
+ }
+
+ /* Mark TX Equalization settings as valid */
+ params->is_valid = true;
+ params->is_applied = false;
+ }
+
+apply_tx_eq_settings:
+ if (params->is_valid && !params->is_applied) {
+ int ret;
+
+ ret = ufshcd_apply_tx_eq_settings(hba, params, gear);
+ if (ret) {
+ dev_err(hba->dev, "Failed to apply TX Equalization settings for HS-G%u, Rate-%s: %d\n",
+ gear, UFS_HS_RATE_STRING(rate), ret);
+ return ret;
+ }
+
+ params->is_applied = true;
+ }
+
+ return 0;
+}
+
+void ufshcd_apply_valid_tx_eq_settings(struct ufs_hba *hba)
+{
+ struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info;
+ struct ufshcd_tx_eq_params *params;
+ int gear, ret;
+
+ if (!ufshcd_is_tx_eq_supported(hba))
+ return;
+
+ if (!hba->max_pwr_info.is_valid) {
+ dev_err(hba->dev, "Max power info invalid, cannot apply TX Equalization settings\n");
+ return;
+ }
+
+ for (gear = UFS_HS_G1; gear < UFS_HS_GEAR_MAX; gear++) {
+ params = &hba->tx_eq_params[gear - 1];
+
+ if (params->is_valid) {
+ params->tx_lanes = pwr_info->lane_tx;
+ params->rx_lanes = pwr_info->lane_rx;
+
+ ret = ufshcd_apply_tx_eq_settings(hba, params, gear);
+ if (ret) {
+ params->is_applied = false;
+ dev_err(hba->dev, "Failed to apply TX Equalization settings for HS-G%u: %d\n",
+ gear, ret);
+ } else {
+ params->is_applied = true;
+ }
+ }
+ }
+}
diff --git a/drivers/ufs/core/ufshcd-priv.h b/drivers/ufs/core/ufshcd-priv.h
index 3b6958d9297a..20ec8d8ac0a4 100644
--- a/drivers/ufs/core/ufshcd-priv.h
+++ b/drivers/ufs/core/ufshcd-priv.h
@@ -103,6 +103,12 @@ int ufshcd_exec_raw_upiu_cmd(struct ufs_hba *hba,
int ufshcd_wb_toggle(struct ufs_hba *hba, bool enable);
int ufshcd_read_device_lvl_exception_id(struct ufs_hba *hba, u64 *exception_id);
+int ufshcd_uic_tx_eqtr(struct ufs_hba *hba, int gear);
+void ufshcd_apply_valid_tx_eq_settings(struct ufs_hba *hba);
+int ufshcd_config_tx_eq_settings(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode);
+void ufshcd_print_tx_eq_params(struct ufs_hba *hba);
+
/* Wrapper functions for safely calling variant operations */
static inline const char *ufshcd_get_var_name(struct ufs_hba *hba)
{
@@ -297,6 +303,38 @@ static inline u32 ufshcd_vops_freq_to_gear_speed(struct ufs_hba *hba, unsigned l
return 0;
}
+static inline int ufshcd_vops_get_rx_fom(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter)
+{
+ if (hba->vops && hba->vops->get_rx_fom)
+ return hba->vops->get_rx_fom(hba, pwr_mode, h_iter, d_iter);
+
+ return 0;
+}
+
+static inline int ufshcd_vops_apply_tx_eqtr_settings(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter)
+{
+ if (hba->vops && hba->vops->apply_tx_eqtr_settings)
+ return hba->vops->apply_tx_eqtr_settings(hba, pwr_mode, h_iter, d_iter);
+
+ return 0;
+}
+
+static inline int ufshcd_vops_tx_eqtr_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status,
+ struct ufs_pa_layer_attr *pwr_mode)
+{
+ if (hba->vops && hba->vops->tx_eqtr_notify)
+ return hba->vops->tx_eqtr_notify(hba, status, pwr_mode);
+
+ return 0;
+}
+
extern const struct ufs_pm_lvl_states ufs_pm_lvl_states[];
/**
diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
index 6809564fb507..f30ff912ce1a 100644
--- a/drivers/ufs/core/ufshcd.c
+++ b/drivers/ufs/core/ufshcd.c
@@ -4342,16 +4342,18 @@ static int ufshcd_uic_pwr_ctrl(struct ufs_hba *hba, struct uic_command *cmd)
ret = __ufshcd_send_uic_cmd(hba, cmd);
if (ret) {
dev_err(hba->dev,
- "pwr ctrl cmd 0x%x with mode 0x%x uic error %d\n",
- cmd->command, cmd->argument3, ret);
+ "pwr ctrl cmd 0x%x with (MIBattribute 0x%x, mode 0x%x) uic error %d\n",
+ cmd->command, UIC_GET_ATTR_ID(cmd->argument1),
+ cmd->argument3, ret);
goto out;
}
if (!wait_for_completion_timeout(hba->uic_async_done,
msecs_to_jiffies(uic_cmd_timeout))) {
dev_err(hba->dev,
- "pwr ctrl cmd 0x%x with mode 0x%x completion timeout\n",
- cmd->command, cmd->argument3);
+ "pwr ctrl cmd 0x%x with (MIBattribute 0x%x, mode 0x%x) completion timeout\n",
+ cmd->command, UIC_GET_ATTR_ID(cmd->argument1),
+ cmd->argument3);
if (!cmd->cmd_active) {
dev_err(hba->dev, "%s: Power Mode Change operation has been completed, go check UPMCRS\n",
@@ -4367,14 +4369,16 @@ static int ufshcd_uic_pwr_ctrl(struct ufs_hba *hba, struct uic_command *cmd)
status = ufshcd_get_upmcrs(hba);
if (status != PWR_LOCAL) {
dev_err(hba->dev,
- "pwr ctrl cmd 0x%x failed, host upmcrs:0x%x\n",
- cmd->command, status);
+ "pwr ctrl cmd 0x%x with (MIBattribute 0x%x, mode 0x%x) failed, host upmcrs:0x%x\n",
+ cmd->command, UIC_GET_ATTR_ID(cmd->argument1),
+ cmd->argument3, status);
ret = (status != PWR_OK) ? status : -1;
}
out:
if (ret) {
ufshcd_print_host_state(hba);
ufshcd_print_pwr_info(hba);
+ ufshcd_print_tx_eq_params(hba);
ufshcd_print_evt_hist(hba);
}
@@ -4400,6 +4404,29 @@ static int ufshcd_uic_pwr_ctrl(struct ufs_hba *hba, struct uic_command *cmd)
return ret;
}
+/**
+ * ufshcd_uic_tx_eqtr - Perform UIC TX Equalization Training
+ * @hba: per adapter instance
+ * @gear: target gear for EQTR
+ *
+ * Returns 0 on success, negative error code otherwise
+ */
+int ufshcd_uic_tx_eqtr(struct ufs_hba *hba, int gear)
+{
+ struct uic_command uic_cmd = {
+ .command = UIC_CMD_DME_SET,
+ .argument1 = UIC_ARG_MIB(PA_EQTR_GEAR),
+ .argument3 = gear,
+ };
+ int ret;
+
+ ufshcd_hold(hba);
+ ret = ufshcd_uic_pwr_ctrl(hba, &uic_cmd);
+ ufshcd_release(hba);
+
+ return ret;
+}
+
/**
* ufshcd_send_bsg_uic_cmd - Send UIC commands requested via BSG layer and retrieve the result
* @hba: per adapter instance
@@ -4788,6 +4815,14 @@ int ufshcd_config_pwr_mode(struct ufs_hba *hba,
if (ret)
memcpy(&final_params, desired_pwr_mode, sizeof(final_params));
+ ret = ufshcd_config_tx_eq_settings(hba, &final_params);
+ if (ret) {
+ dev_err(hba->dev, "Failed to configure TX Equalization settings for HS-G%u, Rate-%s: %d\n",
+ final_params.gear_tx,
+ UFS_HS_RATE_STRING(final_params.hs_rate), ret);
+ return ret;
+ }
+
return ufshcd_change_power_mode(hba, &final_params, pmc_policy);
}
EXPORT_SYMBOL_GPL(ufshcd_config_pwr_mode);
@@ -6787,6 +6822,7 @@ static void ufshcd_err_handler(struct work_struct *work)
spin_unlock_irqrestore(hba->host->host_lock, flags);
ufshcd_print_host_state(hba);
ufshcd_print_pwr_info(hba);
+ ufshcd_print_tx_eq_params(hba);
ufshcd_print_evt_hist(hba);
ufshcd_print_tmrs(hba, hba->outstanding_tasks);
ufshcd_print_trs_all(hba, pr_prdt);
@@ -7050,6 +7086,7 @@ static irqreturn_t ufshcd_check_errors(struct ufs_hba *hba, u32 intr_status)
ufshcd_dump_regs(hba, 0, UFSHCI_REG_SPACE_SIZE,
"host_regs: ");
ufshcd_print_pwr_info(hba);
+ ufshcd_print_tx_eq_params(hba);
}
ufshcd_schedule_eh_work(hba);
retval |= IRQ_HANDLED;
@@ -7831,6 +7868,7 @@ static int ufshcd_abort(struct scsi_cmnd *cmd)
ufshcd_print_evt_hist(hba);
ufshcd_print_host_state(hba);
ufshcd_print_pwr_info(hba);
+ ufshcd_print_tx_eq_params(hba);
ufshcd_print_tr(hba, cmd, true);
} else {
ufshcd_print_tr(hba, cmd, false);
@@ -8808,6 +8846,8 @@ static void ufshcd_tune_unipro_params(struct ufs_hba *hba)
if (hba->dev_quirks & UFS_DEVICE_QUIRK_PA_HIBER8TIME)
ufshcd_quirk_override_pa_h8time(hba);
+
+ ufshcd_apply_valid_tx_eq_settings(hba);
}
static void ufshcd_clear_dbg_ufs_stats(struct ufs_hba *hba)
diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h
index 16facaee3e77..6130ee9aeb03 100644
--- a/include/ufs/ufshcd.h
+++ b/include/ufs/ufshcd.h
@@ -287,6 +287,73 @@ struct ufs_pwr_mode_info {
struct ufs_pa_layer_attr info;
};
+/**
+ * struct tx_eqtr_iter - TX EQTR setting iterator
+ * @preshoot_bitmap: PreShoot bitmap
+ * @deemphasis_bitmap: DeEmphasis bitmap
+ * @num_lanes: number of active lanes
+ * @preshoot: PreShoot value
+ * @deemphasis: DeEmphasis value
+ * @fom: Figure-of-Merit read out from RX_FOM
+ * @is_new: Flag to indicate whether re-newed since previous iteration
+ */
+struct tx_eqtr_iter {
+ unsigned long preshoot_bitmap;
+ unsigned long deemphasis_bitmap;
+ u32 num_lanes;
+ u32 preshoot;
+ u32 deemphasis;
+ u32 fom[PA_MAXDATALANES];
+ bool is_new;
+};
+
+/**
+ * struct ufshcd_tx_eq_settings - TX Equalization settings
+ * @preshoot: PreShoot value
+ * @deemphasis: DeEmphasis value
+ * @fom: Figure-of-Merit read out from RX_FOM
+ * @precode_en: Flag to indicate whether need to enable pre-coding
+ */
+struct ufshcd_tx_eq_settings {
+ u32 preshoot;
+ u32 deemphasis;
+ u32 fom_val;
+ bool precode_en;
+};
+
+/**
+ * struct ufshcd_tx_eq_params - TX Equalization parameters structure
+ * @tx_lanes: Number of active TX lanes
+ * @rx_lanes: Number of active RX lanes
+ * @host: TX EQ settings for host TX lanes
+ * @device: TX EQ settings for device TX lanes
+ * @host_eqtr_record: last host TX EQTR record
+ * @device_eqtr_record: last device TX EQTR record
+ * @last_eqtr_ts: last TX EQTR timestamp
+ * @num_eqtr_records: number of TX EQTR happened
+ * @saved_adapt_eqtr: saved adaptation length setting for TX EQTR
+ * @is_valid: True if parameters contain valid/optimal settings
+ * @is_applied: True if settings have been applied to UniPro of both sides
+ */
+struct ufshcd_tx_eq_params {
+ u32 tx_lanes;
+ u32 rx_lanes;
+
+ struct ufshcd_tx_eq_settings host[PA_MAXDATALANES];
+ struct ufshcd_tx_eq_settings device[PA_MAXDATALANES];
+
+ u32 host_eqtr_record[PA_MAXDATALANES][TX_HS_NUM_PRESHOOT][TX_HS_NUM_DEEMPHASIS];
+ u32 device_eqtr_record[PA_MAXDATALANES][TX_HS_NUM_PRESHOOT][TX_HS_NUM_DEEMPHASIS];
+
+ ktime_t last_eqtr_ts;
+ int num_eqtr_records;
+
+ u32 saved_adapt_eqtr;
+
+ bool is_valid;
+ bool is_applied;
+};
+
/**
* struct ufs_hba_variant_ops - variant specific callbacks
* @name: variant name
@@ -330,6 +397,11 @@ struct ufs_pwr_mode_info {
* @config_esi: called to config Event Specific Interrupt
* @config_scsi_dev: called to configure SCSI device parameters
* @freq_to_gear_speed: called to map clock frequency to the max supported gear speed
+ * @apply_tx_eqtr_settings: called to apply settings for TX Equalization
+ * Training settings.
+ * @get_rx_fom: called to get Figure of Merit (FOM) value.
+ * @tx_eqtr_notify: called before and after TX Equalization Training procedure
+ * to allow platform vendor specific configs to take place.
*/
struct ufs_hba_variant_ops {
const char *name;
@@ -381,6 +453,17 @@ struct ufs_hba_variant_ops {
int (*config_esi)(struct ufs_hba *hba);
void (*config_scsi_dev)(struct scsi_device *sdev);
u32 (*freq_to_gear_speed)(struct ufs_hba *hba, unsigned long freq);
+ int (*get_rx_fom)(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter);
+ int (*apply_tx_eqtr_settings)(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter);
+ int (*tx_eqtr_notify)(struct ufs_hba *hba,
+ enum ufs_notify_change_status status,
+ struct ufs_pa_layer_attr *pwr_mode);
};
/* clock gating state */
@@ -779,6 +862,13 @@ enum ufshcd_caps {
* WriteBooster when scaling the clock down.
*/
UFSHCD_CAP_WB_WITH_CLK_SCALING = 1 << 12,
+
+ /*
+ * This capability allows the host controller driver to apply TX
+ * Equalization settings discovered from UFS attributes, variant
+ * specific operations and TX Equaliztion Training procedure.
+ */
+ UFSHCD_CAP_TX_EQUALIZATION = 1 << 13,
};
struct ufs_hba_variant_params {
@@ -955,6 +1045,11 @@ enum ufshcd_mcq_opr {
* @dev_lvl_exception_count: count of device level exceptions since last reset
* @dev_lvl_exception_id: vendor specific information about the device level exception event.
* @rpmbs: list of OP-TEE RPMB devices (one per RPMB region)
+ * @host_preshoot_cap: host TX PreShoot capability
+ * @host_deemphasis_cap: host TX DeEmphasis capability
+ * @device_preshoot_cap: device TX PreShoot capability
+ * @device_deemphasis_cap: device TX DeEmphasis capability
+ * @tx_eq_params: TX Equalization settings
*/
struct ufs_hba {
void __iomem *mmio_base;
@@ -1128,6 +1223,12 @@ struct ufs_hba {
u64 dev_lvl_exception_id;
u32 vcc_off_delay_us;
struct list_head rpmbs;
+
+ u32 host_preshoot_cap;
+ u32 host_deemphasis_cap;
+ u32 device_preshoot_cap;
+ u32 device_deemphasis_cap;
+ struct ufshcd_tx_eq_params tx_eq_params[UFS_HS_GEAR_MAX - 1];
};
/**
@@ -1272,6 +1373,13 @@ static inline bool ufshcd_enable_wb_if_scaling_up(struct ufs_hba *hba)
return hba->caps & UFSHCD_CAP_WB_WITH_CLK_SCALING;
}
+static inline bool ufshcd_is_tx_eq_supported(struct ufs_hba *hba)
+{
+ return hba->caps & UFSHCD_CAP_TX_EQUALIZATION &&
+ hba->ufs_version >= ufshci_version(5, 0) &&
+ hba->dev_info.wspecversion >= 0x500;
+}
+
#define ufsmcq_writel(hba, val, reg) \
writel((val), (hba)->mcq_base + (reg))
#define ufsmcq_readl(hba, reg) \
diff --git a/include/ufs/unipro.h b/include/ufs/unipro.h
index eccb45d3b86c..c6f215bb5ed3 100644
--- a/include/ufs/unipro.h
+++ b/include/ufs/unipro.h
@@ -10,6 +10,8 @@
* M-TX Configuration Attributes
*/
#define TX_HIBERN8TIME_CAPABILITY 0x000F
+#define TX_HS_DEEMPHASIS_SETTING_CAP 0x0012
+#define TX_HS_PRESHOOT_SETTING_CAP 0x0015
#define TX_MODE 0x0021
#define TX_HSRATE_SERIES 0x0022
#define TX_HSGEAR 0x0023
@@ -38,6 +40,9 @@
/*
* M-RX Configuration Attributes
*/
+#define RX_HS_G5_ADAPT_INITIAL_CAP 0x0074
+#define RX_HS_G6_ADAPT_INITIAL_CAP 0x007B
+#define RX_HS_G6_ADAPT_INITIAL_L0L1L2L3_CAP 0x007D
#define RX_HS_G1_SYNC_LENGTH_CAP 0x008B
#define RX_HS_G1_PREP_LENGTH_CAP 0x008C
#define RX_MIN_ACTIVATETIME_CAPABILITY 0x008F
@@ -50,6 +55,7 @@
#define RX_HIBERN8TIME_CAP 0x0092
#define RX_ADV_HIBERN8TIME_CAP 0x0099
#define RX_ADV_MIN_ACTIVATETIME_CAP 0x009A
+#define RX_HS_G4_ADAPT_INITIAL_CAP 0x009F
#define RX_MODE 0x00A1
#define RX_HSRATE_SERIES 0x00A2
#define RX_HSGEAR 0x00A3
@@ -64,6 +70,7 @@
#define CFGRXCDR8 0x00BA
#define CFGRXOVR8 0x00BD
#define CFGRXOVR6 0x00BF
+#define RX_FOM 0x00C2
#define RXDIRECTCTRL2 0x00C7
#define CFGRXOVR4 0x00E9
#define RX_REFCLKFREQ 0x00EB
@@ -73,7 +80,6 @@
#define ENARXDIRECTCFG3 0x00F3
#define ENARXDIRECTCFG2 0x00F4
-
#define is_mphy_tx_attr(attr) (attr < RX_MODE)
#define RX_ADV_FINE_GRAN_STEP(x) ((((x) & 0x3) << 1) | 0x1)
#define SYNC_LEN_FINE(x) ((x) & 0x3F)
@@ -99,6 +105,18 @@
#define UNIPRO_CB_OFFSET(x) (0x8000 | x)
+/* Adapt capability masks */
+#define ADAPT_LENGTH_MASK 0x7F
+#define ADAPT_RANGE_MASK 0x80
+
+/* Adapt definitions */
+#define ADAPT_LENGTH_MAX 0x91
+#define ADAPT_L0L3_LENGTH_MAX 0x90
+#define ADAPT_L0L1L2L3_LENGTH_MAX 0x8C
+#define TADAPT_FACTOR 650
+#define TADAPT_L0L3_FACTOR (1 << 9)
+#define TADAPT_L0L1L2L3_FACTOR (1 << 15)
+
/*
* PHY Adapter attributes
*/
@@ -164,10 +182,26 @@
#define PA_PACPERRORCOUNT 0x15C1
#define PA_PHYTESTCONTROL 0x15C2
#define PA_TXHSG4SYNCLENGTH 0x15D0
+#define PA_PEERRXHSG4ADAPTINITIAL 0x15D3
#define PA_TXHSADAPTTYPE 0x15D4
#define PA_TXHSG5SYNCLENGTH 0x15D6
+#define PA_PEERRXHSG5ADAPTINITIAL 0x15D9
+#define PA_PEERRXHSG6ADAPTINITIALL0L3 0x15DF
+#define PA_PEERRXHSG6ADAPTREFRESHL0L1L2L3 0x15DE
+#define PA_PEERRXHSG6ADAPTINITIALL0L1L2L3 0x15E0
+#define PA_TXEQG1SETTING 0x15E1
+#define PA_TXEQG2SETTING 0x15E2
+#define PA_TXEQG3SETTING 0x15E3
+#define PA_TXEQG4SETTING 0x15E4
+#define PA_TXEQG5SETTING 0x15E5
+#define PA_TXEQG6SETTING 0x15E6
+#define PA_TXEQTRSETTING 0x15E7
+#define PA_PEERTXEQTRSETTING 0x15E8
+#define PA_PRECODEEN 0x15E9
+#define PA_EQTR_GEAR 0x15EA
+#define PA_TXADAPTLENGTH_EQTR 0x15EB
-/* Adpat type for PA_TXHSADAPTTYPE attribute */
+/* Adapt type for PA_TXHSADAPTTYPE attribute */
#define PA_REFRESH_ADAPT 0x00
#define PA_INITIAL_ADAPT 0x01
#define PA_NO_ADAPT 0x03
@@ -187,6 +221,83 @@
/* PHY Adapter Protocol Constants */
#define PA_MAXDATALANES 4
+/*
+ * TX EQTR's minimum TAdapt should not be less than 10us.
+ * This value is rounded up into the nearest Unit Intervals (UI)
+ */
+#define TX_EQTR_HS_G4_MIN_T_ADAPT 166400
+#define TX_EQTR_HS_G5_MIN_T_ADAPT 332800
+#define TX_EQTR_HS_G6_MIN_T_ADAPT 262144
+
+#define TX_EQTR_HS_G4_ADAPT_DEFAULT 0x88
+#define TX_EQTR_HS_G5_ADAPT_DEFAULT 0x89
+#define TX_EQTR_HS_G6_ADAPT_DEFAULT 0x89
+
+#define TX_EQTR_CAP_MASK 0x7F
+
+#define TX_EQTR_ADAPT_LENGTH_SHIFT 0
+#define TX_EQTR_ADAPT_LENGTH_L0L1L2L3_SHIFT 8
+#define TX_EQTR_ADAPT_RESERVED 0xFF
+
+#define TX_HS_NUM_PRESHOOT 8
+#define TX_HS_NUM_DEEMPHASIS 8
+#define TX_HS_PRESHOOT_SHIFT 4
+#define TX_HS_DEEMPHASIS_SHIFT 4
+#define TX_HS_PRESHOOT_OFFSET 0
+#define TX_HS_DEEMPHASIS_OFFSET 16
+
+#define TX_HS_PRESHOOT_LANE_SHIFT(lane) \
+ (TX_HS_PRESHOOT_OFFSET + (lane) * TX_HS_PRESHOOT_SHIFT)
+#define TX_HS_DEEMPHASIS_LANE_SHIFT(lane) \
+ (TX_HS_DEEMPHASIS_OFFSET + (lane) * TX_HS_DEEMPHASIS_SHIFT)
+
+#define TX_HS_PRESHOOT_BITS(lane, val) \
+ ((val) << TX_HS_PRESHOOT_LANE_SHIFT(lane))
+#define TX_HS_DEEMPHASIS_BITS(lane, val) \
+ ((val) << TX_HS_DEEMPHASIS_LANE_SHIFT(lane))
+
+#define RX_FOM_VALUE_MASK 0x7F
+#define RX_FOM_PRECODING_EN_MASK 0x80
+
+#define PRECODEEN_TX_OFFSET 0
+#define PRECODEEN_RX_OFFSET 4
+#define PRECODEEN_TX_BIT(lane) (1 << (PRECODEEN_TX_OFFSET + (lane)))
+#define PRECODEEN_RX_BIT(lane) (1 << (PRECODEEN_RX_OFFSET + (lane)))
+
+enum ufs_tx_eq_preset {
+ UFS_TX_EQ_PRESET_P0,
+ UFS_TX_EQ_PRESET_P1,
+ UFS_TX_EQ_PRESET_P2,
+ UFS_TX_EQ_PRESET_P3,
+ UFS_TX_EQ_PRESET_P4,
+ UFS_TX_EQ_PRESET_P5,
+ UFS_TX_EQ_PRESET_P6,
+ UFS_TX_EQ_PRESET_P7,
+ UFS_TX_EQ_PRESET_MAX,
+};
+
+enum ufs_tx_hs_preshoot {
+ UFS_TX_HS_PRESHOOT_DB_0P0,
+ UFS_TX_HS_PRESHOOT_DB_0P4,
+ UFS_TX_HS_PRESHOOT_DB_0P8,
+ UFS_TX_HS_PRESHOOT_DB_1P2,
+ UFS_TX_HS_PRESHOOT_DB_1P6,
+ UFS_TX_HS_PRESHOOT_DB_2P5,
+ UFS_TX_HS_PRESHOOT_DB_3P5,
+ UFS_TX_HS_PRESHOOT_DB_4P7,
+};
+
+enum ufs_tx_hs_deemphasis {
+ UFS_TX_HS_DEEMPHASIS_DB_0P0,
+ UFS_TX_HS_DEEMPHASIS_DB_0P8,
+ UFS_TX_HS_DEEMPHASIS_DB_1P6,
+ UFS_TX_HS_DEEMPHASIS_DB_2P5,
+ UFS_TX_HS_DEEMPHASIS_DB_3P5,
+ UFS_TX_HS_DEEMPHASIS_DB_4P7,
+ UFS_TX_HS_DEEMPHASIS_DB_6P0,
+ UFS_TX_HS_DEEMPHASIS_DB_7P6,
+};
+
#define DL_FC0ProtectionTimeOutVal_Default 8191
#define DL_TC0ReplayTimeOutVal_Default 65535
#define DL_AFC0ReqTimeOutVal_Default 32767
@@ -216,6 +327,11 @@ enum ufs_hs_gear_rate {
PA_HS_MODE_B = 2,
};
+#define UFS_HS_RATE_STRING(rate) \
+ ((rate) == PA_HS_MODE_A ? "A" : \
+ (rate) == PA_HS_MODE_B ? "B" : \
+ "Unknown")
+
enum ufs_pwm_gear_tag {
UFS_PWM_DONT_CHANGE, /* Don't change Gear */
UFS_PWM_G1, /* PWM Gear 1 (default for reset) */
--
2.34.1
^ permalink raw reply related [flat|nested] 39+ messages in thread* Re: [PATCH v2 04/11] scsi: ufs: core: Add support for TX Equalization
2026-03-04 13:53 ` [PATCH v2 04/11] scsi: ufs: core: Add support for TX Equalization Can Guo
@ 2026-03-04 20:46 ` kernel test robot
2026-03-04 23:18 ` kernel test robot
2026-03-05 21:50 ` Bean Huo
2 siblings, 0 replies; 39+ messages in thread
From: kernel test robot @ 2026-03-04 20:46 UTC (permalink / raw)
To: Can Guo, avri.altman, bvanassche, beanhuo, martin.petersen
Cc: llvm, oe-kbuild-all, linux-scsi, Can Guo, Alim Akhtar,
James E.J. Bottomley, Peter Wang, Bao D. Nguyen, Adrian Hunter,
linux-kernel
Hi Can,
kernel test robot noticed the following build warnings:
[auto build test WARNING on mkp-scsi/for-next]
[also build test WARNING on jejb-scsi/for-next krzk/for-next linus/master v7.0-rc2 next-20260304]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Can-Guo/scsi-ufs-core-Introduce-a-new-ufshcd-vops-negotiate_pwr_mode/20260304-220245
base: https://git.kernel.org/pub/scm/linux/kernel/git/mkp/scsi.git for-next
patch link: https://lore.kernel.org/r/20260304135313.413688-5-can.guo%40oss.qualcomm.com
patch subject: [PATCH v2 04/11] scsi: ufs: core: Add support for TX Equalization
config: x86_64-kexec (https://download.01.org/0day-ci/archive/20260304/202603042130.iNqLS3Zo-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260304/202603042130.iNqLS3Zo-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603042130.iNqLS3Zo-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> Warning: drivers/ufs/core/ufs-txeq.c:56 cannot understand function prototype: 'const struct __ufs_tx_eq_preset'
>> Warning: drivers/ufs/core/ufs-txeq.c:77 cannot understand function prototype: 'const u32 pa_peer_rx_adapt_initial[UFS_HS_GEAR_MAX] ='
>> Warning: drivers/ufs/core/ufs-txeq.c:91 cannot understand function prototype: 'const u32 rx_adapt_initial_cap[UFS_HS_GEAR_MAX] ='
>> Warning: drivers/ufs/core/ufs-txeq.c:104 cannot understand function prototype: 'const u32 pa_tx_eq_setting[UFS_HS_GEAR_MAX] ='
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 04/11] scsi: ufs: core: Add support for TX Equalization
2026-03-04 13:53 ` [PATCH v2 04/11] scsi: ufs: core: Add support for TX Equalization Can Guo
2026-03-04 20:46 ` kernel test robot
@ 2026-03-04 23:18 ` kernel test robot
2026-03-05 21:50 ` Bean Huo
2 siblings, 0 replies; 39+ messages in thread
From: kernel test robot @ 2026-03-04 23:18 UTC (permalink / raw)
To: Can Guo, avri.altman, bvanassche, beanhuo, martin.petersen
Cc: oe-kbuild-all, linux-scsi, Can Guo, Alim Akhtar,
James E.J. Bottomley, Peter Wang, Bao D. Nguyen, Adrian Hunter,
linux-kernel
Hi Can,
kernel test robot noticed the following build warnings:
[auto build test WARNING on mkp-scsi/for-next]
[also build test WARNING on jejb-scsi/for-next krzk/for-next linus/master v7.0-rc2 next-20260303]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Can-Guo/scsi-ufs-core-Introduce-a-new-ufshcd-vops-negotiate_pwr_mode/20260304-220245
base: https://git.kernel.org/pub/scm/linux/kernel/git/mkp/scsi.git for-next
patch link: https://lore.kernel.org/r/20260304135313.413688-5-can.guo%40oss.qualcomm.com
patch subject: [PATCH v2 04/11] scsi: ufs: core: Add support for TX Equalization
config: i386-randconfig-r071-20260305 (https://download.01.org/0day-ci/archive/20260305/202603050737.IgToFD5G-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
smatch: v0.5.0-9004-gb810ac53
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260305/202603050737.IgToFD5G-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603050737.IgToFD5G-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> Warning: drivers/ufs/core/ufs-txeq.c:56 cannot understand function prototype: 'const struct __ufs_tx_eq_preset'
>> Warning: drivers/ufs/core/ufs-txeq.c:77 cannot understand function prototype: 'const u32 pa_peer_rx_adapt_initial[UFS_HS_GEAR_MAX] ='
>> Warning: drivers/ufs/core/ufs-txeq.c:91 cannot understand function prototype: 'const u32 rx_adapt_initial_cap[UFS_HS_GEAR_MAX] ='
>> Warning: drivers/ufs/core/ufs-txeq.c:104 cannot understand function prototype: 'const u32 pa_tx_eq_setting[UFS_HS_GEAR_MAX] ='
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 04/11] scsi: ufs: core: Add support for TX Equalization
2026-03-04 13:53 ` [PATCH v2 04/11] scsi: ufs: core: Add support for TX Equalization Can Guo
2026-03-04 20:46 ` kernel test robot
2026-03-04 23:18 ` kernel test robot
@ 2026-03-05 21:50 ` Bean Huo
2026-03-06 12:59 ` Can Guo
2 siblings, 1 reply; 39+ messages in thread
From: Bean Huo @ 2026-03-05 21:50 UTC (permalink / raw)
To: Can Guo, avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Alim Akhtar, James E.J. Bottomley, Peter Wang,
Bao D. Nguyen, Adrian Hunter, open list
On Wed, 2026-03-04 at 05:53 -0800, Can Guo wrote:
> + */
> +static int ufshcd_tx_eqtr_data_init(struct ufs_hba *hba,
> + struct ufshcd_tx_eq_params *params,
> + struct tx_eqtr_iter *h_iter,
> + struct tx_eqtr_iter *d_iter)
> +{
> + u32 cap;
> + int ret;
> +
> + if (!hba->host_preshoot_cap) {
> + ret = ufshcd_dme_get(hba,
> UIC_ARG_MIB(TX_HS_PRESHOOT_SETTING_CAP), &cap);
> + if (ret)
> + return ret;
> +
> + hba->host_preshoot_cap = cap & TX_EQTR_CAP_MASK;
> + }
> +
> + if (!hba->host_deemphasis_cap) {
> + ret = ufshcd_dme_get(hba,
> UIC_ARG_MIB(TX_HS_DEEMPHASIS_SETTING_CAP), &cap);
> + if (ret)
> + return ret;
> +
> + hba->host_deemphasis_cap = cap & TX_EQTR_CAP_MASK;
> + }
> +
> + if (!hba->device_preshoot_cap) {
> + ret = ufshcd_dme_peer_get(hba,
> UIC_ARG_MIB(TX_HS_PRESHOOT_SETTING_CAP), &cap);
> + if (ret)
> + return ret;
> +
> + hba->device_preshoot_cap = cap & TX_EQTR_CAP_MASK;
> + }
> +
> + if (!hba->device_deemphasis_cap) {
> + ret = ufshcd_dme_peer_get(hba,
> UIC_ARG_MIB(TX_HS_DEEMPHASIS_SETTING_CAP), &cap);
> + if (ret)
> + return ret;
> +
> + hba->device_deemphasis_cap = cap & TX_EQTR_CAP_MASK;
> + }
> +
> + memset(params->host, 0, sizeof(params->host));
> + memset(params->device, 0, sizeof(params->device));
> + memset(params->host_eqtr_record, 0xFF, sizeof(params-
> >host_eqtr_record));
> + memset(params->device_eqtr_record, 0xFF, sizeof(params-
> >device_eqtr_record));
> +
> + memset(h_iter, 0, sizeof(struct tx_eqtr_iter));
> + memset(d_iter, 0, sizeof(struct tx_eqtr_iter));
> +
> + h_iter->num_lanes = params->tx_lanes;
> + d_iter->num_lanes = params->rx_lanes;
> +
> + /*
> + * Support PreShoot & DeEmphasis of value 0 is mandatory, hence they
> are
> + * not reflected in PreShoot/DeEmphasis capabilities. Left shift the
> + * capability bitmap by 1 and set bit[0] to reflect value 0 is
> + * supported, such that test_bit() can be used later for convenience.
> + */
> + h_iter->preshoot_bitmap = (hba->host_preshoot_cap << 0x1) | 0x1;
> + h_iter->deemphasis_bitmap = (hba->host_deemphasis_cap << 0x1) | 0x1;
> + d_iter->preshoot_bitmap = (hba->device_preshoot_cap << 0x1) | 0x1;
> + d_iter->deemphasis_bitmap = (hba->device_deemphasis_cap << 0x1) | 0x1;
> +
> + return ret;
ret is returned without guaranteed initialization when caps are already cached.
> +}
^ permalink raw reply [flat|nested] 39+ messages in thread* Re: [PATCH v2 04/11] scsi: ufs: core: Add support for TX Equalization
2026-03-05 21:50 ` Bean Huo
@ 2026-03-06 12:59 ` Can Guo
0 siblings, 0 replies; 39+ messages in thread
From: Can Guo @ 2026-03-06 12:59 UTC (permalink / raw)
To: Bean Huo, avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Alim Akhtar, James E.J. Bottomley, Peter Wang,
Bao D. Nguyen, Adrian Hunter, open list
On 3/6/2026 5:50 AM, Bean Huo wrote:
> On Wed, 2026-03-04 at 05:53 -0800, Can Guo wrote:
>> + */
>> +static int ufshcd_tx_eqtr_data_init(struct ufs_hba *hba,
>> + struct ufshcd_tx_eq_params *params,
>> + struct tx_eqtr_iter *h_iter,
>> + struct tx_eqtr_iter *d_iter)
>> +{
>> + u32 cap;
>> + int ret;
>> +
>> + if (!hba->host_preshoot_cap) {
>> + ret = ufshcd_dme_get(hba,
>> UIC_ARG_MIB(TX_HS_PRESHOOT_SETTING_CAP), &cap);
>> + if (ret)
>> + return ret;
>> +
>> + hba->host_preshoot_cap = cap & TX_EQTR_CAP_MASK;
>> + }
>> +
>> + if (!hba->host_deemphasis_cap) {
>> + ret = ufshcd_dme_get(hba,
>> UIC_ARG_MIB(TX_HS_DEEMPHASIS_SETTING_CAP), &cap);
>> + if (ret)
>> + return ret;
>> +
>> + hba->host_deemphasis_cap = cap & TX_EQTR_CAP_MASK;
>> + }
>> +
>> + if (!hba->device_preshoot_cap) {
>> + ret = ufshcd_dme_peer_get(hba,
>> UIC_ARG_MIB(TX_HS_PRESHOOT_SETTING_CAP), &cap);
>> + if (ret)
>> + return ret;
>> +
>> + hba->device_preshoot_cap = cap & TX_EQTR_CAP_MASK;
>> + }
>> +
>> + if (!hba->device_deemphasis_cap) {
>> + ret = ufshcd_dme_peer_get(hba,
>> UIC_ARG_MIB(TX_HS_DEEMPHASIS_SETTING_CAP), &cap);
>> + if (ret)
>> + return ret;
>> +
>> + hba->device_deemphasis_cap = cap & TX_EQTR_CAP_MASK;
>> + }
>> +
>> + memset(params->host, 0, sizeof(params->host));
>> + memset(params->device, 0, sizeof(params->device));
>> + memset(params->host_eqtr_record, 0xFF, sizeof(params-
>>> host_eqtr_record));
>> + memset(params->device_eqtr_record, 0xFF, sizeof(params-
>>> device_eqtr_record));
>> +
>> + memset(h_iter, 0, sizeof(struct tx_eqtr_iter));
>> + memset(d_iter, 0, sizeof(struct tx_eqtr_iter));
>> +
>> + h_iter->num_lanes = params->tx_lanes;
>> + d_iter->num_lanes = params->rx_lanes;
>> +
>> + /*
>> + * Support PreShoot & DeEmphasis of value 0 is mandatory, hence they
>> are
>> + * not reflected in PreShoot/DeEmphasis capabilities. Left shift the
>> + * capability bitmap by 1 and set bit[0] to reflect value 0 is
>> + * supported, such that test_bit() can be used later for convenience.
>> + */
>> + h_iter->preshoot_bitmap = (hba->host_preshoot_cap << 0x1) | 0x1;
>> + h_iter->deemphasis_bitmap = (hba->host_deemphasis_cap << 0x1) | 0x1;
>> + d_iter->preshoot_bitmap = (hba->device_preshoot_cap << 0x1) | 0x1;
>> + d_iter->deemphasis_bitmap = (hba->device_deemphasis_cap << 0x1) | 0x1;
>> +
>> + return ret;
>
> ret is returned without guaranteed initialization when caps are already cached.
Good catch.
Thanks,
Can Guo.
>
>> +}
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 05/11] scsi: ufs: core: Add debugfs entries for TX Equalization params
2026-03-04 13:53 [PATCH v2 00/11] scsi: ufs: Add TX Equalization support for UFS 5.0 Can Guo
` (3 preceding siblings ...)
2026-03-04 13:53 ` [PATCH v2 04/11] scsi: ufs: core: Add support for TX Equalization Can Guo
@ 2026-03-04 13:53 ` Can Guo
2026-03-05 14:00 ` Bart Van Assche
2026-03-04 13:53 ` [PATCH v2 06/11] scsi: ufs: core: Add support to retrain TX Equalization via debugfs Can Guo
` (6 subsequent siblings)
11 siblings, 1 reply; 39+ messages in thread
From: Can Guo @ 2026-03-04 13:53 UTC (permalink / raw)
To: avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Can Guo, Alim Akhtar, James E.J. Bottomley, open list
Add debugfs support for UFS TX Equalization and UFS TX Equalization
Training (EQTR) to facilitate runtime inspection of link quality. These
entries allow developers to monitor and optimize TX Equalization
parameters and EQTR records during live operation.
The debugfs entries are organized on a per-gear basis under the HBA's
debugfs root. Since TX EQTR is only defined for High Speed Gear 4 (HS-G4)
and above, EQTR-related entries are explicitly excluded for HS-G1
through HS-G3 to avoid exposing unsupported attributes.
The ufshcd's debugfs folder structure will look like below:
/sys/kernel/debug/ufshcd/*ufs*/
|--tx_eq_hs_gear1/
| |--device_tx_eq_params
| |--host_tx_eq_params
|--tx_eq_hs_gear2/
|--tx_eq_hs_gear3/
|--tx_eq_hs_gear4/
|--tx_eq_hs_gear5/
|--tx_eq_hs_gear6/
|--device_tx_eq_params
|--device_tx_eqtr_record
|--host_tx_eq_params
|--host_tx_eqtr_record
Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
---
drivers/ufs/core/ufs-debugfs.c | 211 +++++++++++++++++++++++++++++++++
1 file changed, 211 insertions(+)
diff --git a/drivers/ufs/core/ufs-debugfs.c b/drivers/ufs/core/ufs-debugfs.c
index e3baed6c70bd..b618d9b11dc9 100644
--- a/drivers/ufs/core/ufs-debugfs.c
+++ b/drivers/ufs/core/ufs-debugfs.c
@@ -209,6 +209,186 @@ static const struct ufs_debugfs_attr ufs_attrs[] = {
{ }
};
+static int ufs_tx_eq_params_show(struct seq_file *s, void *data)
+{
+ struct ufs_hba *hba = hba_from_file(s->file);
+ struct ufshcd_tx_eq_settings *settings;
+ struct ufshcd_tx_eq_params *params;
+ const char *file_name = s->file->f_path.dentry->d_name.name;
+ u32 gear = (u32)(uintptr_t)s->file->f_inode->i_private;
+ u32 rate = hba->pwr_info.hs_rate;
+ u32 num_lanes;
+ int lane;
+
+ if (!ufshcd_is_tx_eq_supported(hba))
+ return -EOPNOTSUPP;
+
+ if (gear < UFS_HS_G1 || gear >= UFS_HS_GEAR_MAX) {
+ seq_printf(s, "Invalid gear selected: %u\n", gear);
+ return 0;
+ }
+
+ params = &hba->tx_eq_params[gear - 1];
+ if (!params->is_valid) {
+ seq_printf(s, "TX EQ params are invalid for HS-G%u, Rate-%s\n",
+ gear, UFS_HS_RATE_STRING(rate));
+ return 0;
+ }
+
+ if (strcmp(file_name, "host_tx_eq_params") == 0) {
+ settings = params->host;
+ num_lanes = params->tx_lanes;
+ seq_printf(s, "Host TX EQ PreShoot Cap: 0x%02x, DeEmphasis Cap: 0x%02x\n",
+ hba->host_preshoot_cap, hba->host_deemphasis_cap);
+ } else if (strcmp(file_name, "device_tx_eq_params") == 0) {
+ settings = params->device;
+ num_lanes = params->rx_lanes;
+ seq_printf(s, "Device TX EQ PreShoot Cap: 0x%02x, DeEmphasis Cap: 0x%02x\n",
+ hba->device_preshoot_cap, hba->device_deemphasis_cap);
+ } else {
+ return -ENOENT;
+ }
+
+ seq_printf(s, "TX EQ setting for HS-G%u, Rate-%s:\n", gear,
+ UFS_HS_RATE_STRING(rate));
+ for (lane = 0; lane < num_lanes; lane++)
+ seq_printf(s, "TX Lane %d - PreShoot: %d, DeEmphasis: %d, Pre-Coding %senabled\n",
+ lane, settings[lane].preshoot,
+ settings[lane].deemphasis,
+ settings[lane].precode_en ? "" : "not ");
+
+ return 0;
+}
+
+static int ufs_tx_eq_params_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ufs_tx_eq_params_show, inode->i_private);
+}
+
+static const struct file_operations ufs_tx_eq_params_fops = {
+ .owner = THIS_MODULE,
+ .open = ufs_tx_eq_params_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const struct ufs_debugfs_attr ufs_tx_eq_attrs[] = {
+ { "host_tx_eq_params", 0400, &ufs_tx_eq_params_fops },
+ { "device_tx_eq_params", 0400, &ufs_tx_eq_params_fops },
+ { }
+};
+
+static int ufs_tx_eqtr_record_show(struct seq_file *s, void *data)
+{
+ struct ufs_hba *hba = hba_from_file(s->file);
+ struct ufshcd_tx_eq_params *params;
+ unsigned long preshoot_bitmap, deemphasis_bitmap;
+ unsigned int preshoot, deemphasis;
+ const char *file_name = s->file->f_path.dentry->d_name.name;
+ u32 (*record)[TX_HS_NUM_PRESHOOT][TX_HS_NUM_DEEMPHASIS];
+ u32 gear = (u32)(uintptr_t)s->file->f_inode->i_private;
+ u32 rate = hba->pwr_info.hs_rate;
+ u32 num_lanes;
+ int lane;
+ char name[32];
+
+ if (!ufshcd_is_tx_eq_supported(hba))
+ return -EOPNOTSUPP;
+
+ if (gear < UFS_HS_G1 || gear >= UFS_HS_GEAR_MAX) {
+ seq_printf(s, "Invalid gear selected: %u\n", gear);
+ return 0;
+ }
+
+ params = &hba->tx_eq_params[gear - 1];
+ if (!params->is_valid) {
+ seq_printf(s, "TX EQ params are invalid for HS-G%u, Rate-%s\n",
+ gear, UFS_HS_RATE_STRING(rate));
+ return 0;
+ }
+
+ if (!params->num_eqtr_records) {
+ seq_printf(s, "No TX EQTR records found for HS-G%u, Rate-%s.\n",
+ gear, UFS_HS_RATE_STRING(rate));
+ return 0;
+ }
+
+ if (strcmp(file_name, "host_tx_eqtr_record") == 0) {
+ record = params->host_eqtr_record;
+ preshoot_bitmap = (hba->host_preshoot_cap << 0x1) | 0x1;
+ deemphasis_bitmap = (hba->host_deemphasis_cap << 0x1) | 0x1;
+ num_lanes = params->tx_lanes;
+ snprintf(name, 32, "%s", "Host");
+ } else if (strcmp(file_name, "device_tx_eqtr_record") == 0) {
+ record = params->device_eqtr_record;
+ preshoot_bitmap = (hba->device_preshoot_cap << 0x1) | 0x1;
+ deemphasis_bitmap = (hba->device_deemphasis_cap << 0x1) | 0x1;
+ num_lanes = params->rx_lanes;
+ snprintf(name, 32, "%s", "Device");
+ } else {
+ return -ENOENT;
+ }
+
+ seq_printf(s, "%s TX EQTR record summary -\n", name);
+ seq_printf(s, "Target Power Mode: HS-G%u, Rate-%s\n", gear,
+ UFS_HS_RATE_STRING(rate));
+ seq_printf(s, "Number of records: %d\n", params->num_eqtr_records);
+ seq_printf(s, "Last record timestamp: %llu us\n",
+ ktime_to_us(params->last_eqtr_ts));
+
+ for (lane = 0; lane < num_lanes; lane++) {
+ seq_printf(s, "\nTX Lane %d FOM - %s\n", lane, "PreShoot\\DeEmphasis");
+ seq_puts(s, "\\");
+ /* Print DeEmphasis header as X-axis. */
+ for (deemphasis = 0; deemphasis < TX_HS_NUM_DEEMPHASIS; deemphasis++)
+ seq_printf(s, "%8d%s", deemphasis, " ");
+ seq_puts(s, "\n");
+ /* Print matrix rows with PreShoot as Y-axis. */
+ for (preshoot = 0; preshoot < TX_HS_NUM_PRESHOOT; preshoot++) {
+ seq_printf(s, "%d", preshoot);
+ for (deemphasis = 0; deemphasis < TX_HS_NUM_DEEMPHASIS; deemphasis++) {
+ if (test_bit(preshoot, &preshoot_bitmap) &&
+ test_bit(deemphasis, &deemphasis_bitmap)) {
+ u32 fom = record[lane][preshoot][deemphasis];
+ u32 fom_val = fom & RX_FOM_VALUE_MASK;
+ bool precode_en = !!(fom & RX_FOM_PRECODING_EN_MASK);
+
+ if (fom == 0xFFFFFFFF)
+ seq_printf(s, "%8s%s", "-", " ");
+ else
+ seq_printf(s, "%8u%s", fom_val,
+ precode_en ? "*" : " ");
+ } else {
+ seq_printf(s, "%8s%s", "x", " ");
+ }
+ }
+ seq_puts(s, "\n");
+ }
+ }
+
+ return 0;
+}
+
+static int ufs_tx_eqtr_record_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ufs_tx_eqtr_record_show, inode->i_private);
+}
+
+static const struct file_operations ufs_tx_eqtr_record_fops = {
+ .owner = THIS_MODULE,
+ .open = ufs_tx_eqtr_record_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const struct ufs_debugfs_attr ufs_tx_eqtr_attrs[] = {
+ { "host_tx_eqtr_record", 0400, &ufs_tx_eqtr_record_fops },
+ { "device_tx_eqtr_record", 0400, &ufs_tx_eqtr_record_fops },
+ { }
+};
+
void ufs_debugfs_hba_init(struct ufs_hba *hba)
{
const struct ufs_debugfs_attr *attr;
@@ -230,6 +410,37 @@ void ufs_debugfs_hba_init(struct ufs_hba *hba)
hba, &ee_usr_mask_fops);
debugfs_create_u32("exception_event_rate_limit_ms", 0600, hba->debugfs_root,
&hba->debugfs_ee_rate_limit_ms);
+
+ if (!(hba->caps & UFSHCD_CAP_TX_EQUALIZATION))
+ return;
+
+ for (u32 gear = UFS_HS_G1; gear < UFS_HS_GEAR_MAX; gear++) {
+ struct dentry *txeq_dir;
+ char name[32];
+
+ snprintf(name, 32, "tx_eq_hs_gear%d", gear);
+ txeq_dir = debugfs_create_dir(name, hba->debugfs_root);
+ if (IS_ERR_OR_NULL(txeq_dir))
+ return;
+
+ d_inode(txeq_dir)->i_private = hba;
+
+ /* Create files for TX Equalization parameters */
+ for (attr = ufs_tx_eq_attrs; attr->name; attr++)
+ debugfs_create_file(attr->name, attr->mode, txeq_dir,
+ (void *)(uintptr_t)gear,
+ attr->fops);
+
+ /* TX EQTR is supported for HS-G4 and higher Gears */
+ if (gear < UFS_HS_G4)
+ continue;
+
+ /* Create files for TX EQTR related attributes */
+ for (attr = ufs_tx_eqtr_attrs; attr->name; attr++)
+ debugfs_create_file(attr->name, attr->mode, txeq_dir,
+ (void *)(uintptr_t)gear,
+ attr->fops);
+ }
}
void ufs_debugfs_hba_exit(struct ufs_hba *hba)
--
2.34.1
^ permalink raw reply related [flat|nested] 39+ messages in thread* Re: [PATCH v2 05/11] scsi: ufs: core: Add debugfs entries for TX Equalization params
2026-03-04 13:53 ` [PATCH v2 05/11] scsi: ufs: core: Add debugfs entries for TX Equalization params Can Guo
@ 2026-03-05 14:00 ` Bart Van Assche
0 siblings, 0 replies; 39+ messages in thread
From: Bart Van Assche @ 2026-03-05 14:00 UTC (permalink / raw)
To: Can Guo, avri.altman, beanhuo, martin.petersen
Cc: linux-scsi, Alim Akhtar, James E.J. Bottomley, open list
On 3/4/26 7:53 AM, Can Guo wrote:
> +static int ufs_tx_eq_params_show(struct seq_file *s, void *data)
> +{
> + struct ufs_hba *hba = hba_from_file(s->file);
> + struct ufshcd_tx_eq_settings *settings;
> + struct ufshcd_tx_eq_params *params;
> + const char *file_name = s->file->f_path.dentry->d_name.name;
> + u32 gear = (u32)(uintptr_t)s->file->f_inode->i_private;
> + u32 rate = hba->pwr_info.hs_rate;
> + u32 num_lanes;
> + int lane;
Please order declarations from longest to shortest.
> +static int ufs_tx_eqtr_record_show(struct seq_file *s, void *data)
> +{
> + struct ufs_hba *hba = hba_from_file(s->file);
> + struct ufshcd_tx_eq_params *params;
> + unsigned long preshoot_bitmap, deemphasis_bitmap;
> + unsigned int preshoot, deemphasis;
> + const char *file_name = s->file->f_path.dentry->d_name.name;
> + u32 (*record)[TX_HS_NUM_PRESHOOT][TX_HS_NUM_DEEMPHASIS];
> + u32 gear = (u32)(uintptr_t)s->file->f_inode->i_private;
> + u32 rate = hba->pwr_info.hs_rate;
> + u32 num_lanes;
> + int lane;
> + char name[32];
Also here, please order declarations from longest to shortest.
> + snprintf(name, 32, "tx_eq_hs_gear%d", gear);
Please change "32" into "sizeof(name)" as is done elsewhere in the
Linux kernel.
Thanks,
Bart.
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 06/11] scsi: ufs: core: Add support to retrain TX Equalization via debugfs
2026-03-04 13:53 [PATCH v2 00/11] scsi: ufs: Add TX Equalization support for UFS 5.0 Can Guo
` (4 preceding siblings ...)
2026-03-04 13:53 ` [PATCH v2 05/11] scsi: ufs: core: Add debugfs entries for TX Equalization params Can Guo
@ 2026-03-04 13:53 ` Can Guo
2026-03-05 14:11 ` Bart Van Assche
2026-03-05 21:25 ` Bean Huo
2026-03-04 13:53 ` [PATCH v2 07/11] scsi: ufs: ufs-qcom: Fixup PAM-4 TX L0_L1_L2_L3 adaptation pattern length Can Guo
` (5 subsequent siblings)
11 siblings, 2 replies; 39+ messages in thread
From: Can Guo @ 2026-03-04 13:53 UTC (permalink / raw)
To: avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Can Guo, Alim Akhtar, James E.J. Bottomley,
Peter Wang, Bao D. Nguyen, Adrian Hunter, open list
Drastic environmental changes, such as significant temperature shifts, can
impact UFS link signal integrity. In such cases, retraining the Transmit
(TX) Equalization is necessary to find optimal settings that compensate
for these thermal effects.
Add a debugfs entry, 'retrain_tx_eq', to allow userspace to manually
trigger the TX Equalization training (EQTR) process. These entries are
created on a per-gear basis for High Speed Gear 4 (HS-G4) and above, as
EQTR is not supported for lower gears.
The 'retrain_tx_eq' entry is a write-only trigger that initiates the
retraining sequence when the value '1' is written to it.
The ufshcd's debugfs folder structure will look like below:
/sys/kernel/debug/ufshcd/*ufs*/
|--tx_eq_hs_gear1/
| |--device_tx_eq_params
| |--host_tx_eq_params
|--tx_eq_hs_gear2/
|--tx_eq_hs_gear3/
|--tx_eq_hs_gear4/
|--tx_eq_hs_gear5/
|--tx_eq_hs_gear6/
|--device_tx_eq_params
|--device_tx_eqtr_record
|--host_tx_eq_params
|--host_tx_eqtr_record
|--retrain_tx_eq
Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
---
drivers/ufs/core/ufs-debugfs.c | 44 +++++++++++++++++++++++
drivers/ufs/core/ufs-txeq.c | 64 ++++++++++++++++++++++++++++++++--
drivers/ufs/core/ufshcd-priv.h | 7 +++-
drivers/ufs/core/ufshcd.c | 32 ++++++++++++++---
4 files changed, 139 insertions(+), 8 deletions(-)
diff --git a/drivers/ufs/core/ufs-debugfs.c b/drivers/ufs/core/ufs-debugfs.c
index b618d9b11dc9..45397a6e9ea0 100644
--- a/drivers/ufs/core/ufs-debugfs.c
+++ b/drivers/ufs/core/ufs-debugfs.c
@@ -383,9 +383,53 @@ static const struct file_operations ufs_tx_eqtr_record_fops = {
.release = single_release,
};
+static ssize_t ufs_retrain_tx_eq_write(struct file *file,
+ const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct ufs_hba *hba = hba_from_file(file);
+ struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info;
+ u32 gear = (u32)(uintptr_t)file->f_inode->i_private;
+ int val, ret;
+
+ ret = kstrtoint_from_user(buf, count, 0, &val);
+ if (ret)
+ return ret;
+
+ if (val != 1)
+ return -EINVAL;
+
+ if (hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL)
+ return -EBUSY;
+
+ if (!hba->ufs_device_wlun)
+ return -ENODEV;
+
+ if (!hba->max_pwr_info.is_valid || gear > pwr_info->gear_tx)
+ return -EINVAL;
+
+ ret = ufs_debugfs_get_user_access(hba);
+ if (ret)
+ return ret;
+ ufshcd_hold(hba);
+ ret = ufshcd_retrain_tx_eq(hba, gear);
+ ufshcd_release(hba);
+ ufs_debugfs_put_user_access(hba);
+
+ return ret ? ret : count;
+}
+
+static const struct file_operations ufs_retrain_tx_eq_fops = {
+ .owner = THIS_MODULE,
+ .open = simple_open,
+ .write = ufs_retrain_tx_eq_write,
+ .llseek = noop_llseek,
+};
+
static const struct ufs_debugfs_attr ufs_tx_eqtr_attrs[] = {
{ "host_tx_eqtr_record", 0400, &ufs_tx_eqtr_record_fops },
{ "device_tx_eqtr_record", 0400, &ufs_tx_eqtr_record_fops },
+ { "retrain_tx_eq", 0400, &ufs_retrain_tx_eq_fops },
{ }
};
diff --git a/drivers/ufs/core/ufs-txeq.c b/drivers/ufs/core/ufs-txeq.c
index 2f637077548c..2cd2d5156607 100644
--- a/drivers/ufs/core/ufs-txeq.c
+++ b/drivers/ufs/core/ufs-txeq.c
@@ -1102,6 +1102,7 @@ static int ufshcd_tx_eqtr(struct ufs_hba *hba,
* ufshcd_config_tx_eq_settings - Configure TX Equalization settings
* @hba: per adapter instance
* @pwr_mode: target power mode containing gear and rate information
+ * @force_tx_eqtr: do a TX EQTR regardless
*
* This function finds and sets the TX Equalization settings for the given
* target power mode.
@@ -1109,7 +1110,8 @@ static int ufshcd_tx_eqtr(struct ufs_hba *hba,
* Returns 0 on success, error code otherwise
*/
int ufshcd_config_tx_eq_settings(struct ufs_hba *hba,
- struct ufs_pa_layer_attr *pwr_mode)
+ struct ufs_pa_layer_attr *pwr_mode,
+ bool force_tx_eqtr)
{
struct ufshcd_tx_eq_params *params;
u32 gear, rate;
@@ -1144,7 +1146,7 @@ int ufshcd_config_tx_eq_settings(struct ufs_hba *hba,
if (gear < UFS_HS_G4)
goto apply_tx_eq_settings;
- if (!params->is_valid) {
+ if (!params->is_valid || force_tx_eqtr) {
int ret;
ret = ufshcd_tx_eqtr(hba, params, pwr_mode);
@@ -1208,3 +1210,61 @@ void ufshcd_apply_valid_tx_eq_settings(struct ufs_hba *hba)
}
}
}
+
+int ufshcd_retrain_tx_eq(struct ufs_hba *hba, u32 gear)
+{
+ struct ufs_pa_layer_attr new_pwr_info, final_params = { 0 };
+ int ret;
+
+ if (!ufshcd_is_tx_eq_supported(hba) || !use_adaptive_txeq)
+ return -EOPNOTSUPP;
+
+ if (gear < adaptive_txeq_gear)
+ return -ETOOSMALL;
+
+ ret = ufshcd_pause_command_processing(hba, 1 * USEC_PER_SEC);
+ if (ret)
+ return ret;
+
+ ufshcd_hold(hba);
+
+ /* scale up clocks to max frequency before TX EQTR */
+ if (ufshcd_is_clkscaling_supported(hba))
+ ufshcd_scale_clks(hba, ULONG_MAX, true);
+
+ new_pwr_info = hba->pwr_info;
+ new_pwr_info.gear_tx = gear;
+ new_pwr_info.gear_rx = gear;
+
+ ret = ufshcd_vops_negotiate_pwr_mode(hba, &new_pwr_info, &final_params);
+ if (ret)
+ memcpy(&final_params, &new_pwr_info, sizeof(final_params));
+
+ if (final_params.gear_tx != gear) {
+ dev_err(hba->dev, "%s: Negotiated Gear (%u) does not match target Gear (%u)\n",
+ __func__, final_params.gear_tx, gear);
+ goto out;
+ }
+
+ ret = ufshcd_config_tx_eq_settings(hba, &final_params, true);
+ if (ret) {
+ dev_err(hba->dev, "%s: Failed to configure TX Equalization settings for HS-G%u, Rate-%s: %d\n",
+ __func__, final_params.gear_tx,
+ UFS_HS_RATE_STRING(final_params.hs_rate), ret);
+ goto out;
+ }
+
+ /* Change Power Mode to apply the new TX EQ settings */
+ ret = ufshcd_change_power_mode(hba, &final_params,
+ UFSHCD_PMC_POLICY_FORCE);
+ if (ret)
+ dev_err(hba->dev, "%s: Failed to change Power Mode to HS-G%u, Rate-%s: %d\n",
+ __func__, final_params.gear_tx,
+ UFS_HS_RATE_STRING(final_params.hs_rate), ret);
+
+out:
+ ufshcd_resume_command_processing(hba);
+ ufshcd_release(hba);
+
+ return ret;
+}
diff --git a/drivers/ufs/core/ufshcd-priv.h b/drivers/ufs/core/ufshcd-priv.h
index 20ec8d8ac0a4..c63c53f61d2c 100644
--- a/drivers/ufs/core/ufshcd-priv.h
+++ b/drivers/ufs/core/ufshcd-priv.h
@@ -78,6 +78,9 @@ int ufshcd_mcq_sq_cleanup(struct ufs_hba *hba, int task_tag);
int ufshcd_mcq_abort(struct scsi_cmnd *cmd);
int ufshcd_try_to_abort_task(struct ufs_hba *hba, int tag);
void ufshcd_release_scsi_cmd(struct ufs_hba *hba, struct scsi_cmnd *cmd);
+int ufshcd_scale_clks(struct ufs_hba *hba, unsigned long freq, bool scale_up);
+int ufshcd_pause_command_processing(struct ufs_hba *hba, u64 timeout_us);
+void ufshcd_resume_command_processing(struct ufs_hba *hba);
/**
* enum ufs_descr_fmt - UFS string descriptor format
@@ -106,8 +109,10 @@ int ufshcd_read_device_lvl_exception_id(struct ufs_hba *hba, u64 *exception_id);
int ufshcd_uic_tx_eqtr(struct ufs_hba *hba, int gear);
void ufshcd_apply_valid_tx_eq_settings(struct ufs_hba *hba);
int ufshcd_config_tx_eq_settings(struct ufs_hba *hba,
- struct ufs_pa_layer_attr *pwr_mode);
+ struct ufs_pa_layer_attr *pwr_mode,
+ bool force_tx_eqtr);
void ufshcd_print_tx_eq_params(struct ufs_hba *hba);
+int ufshcd_retrain_tx_eq(struct ufs_hba *hba, u32 target_gear);
/* Wrapper functions for safely calling variant operations */
static inline const char *ufshcd_get_var_name(struct ufs_hba *hba)
diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
index f30ff912ce1a..71c737bef205 100644
--- a/drivers/ufs/core/ufshcd.c
+++ b/drivers/ufs/core/ufshcd.c
@@ -332,8 +332,6 @@ static inline void ufshcd_add_delay_before_dme_cmd(struct ufs_hba *hba);
static int ufshcd_host_reset_and_restore(struct ufs_hba *hba);
static void ufshcd_resume_clkscaling(struct ufs_hba *hba);
static void ufshcd_suspend_clkscaling(struct ufs_hba *hba);
-static int ufshcd_scale_clks(struct ufs_hba *hba, unsigned long freq,
- bool scale_up);
static irqreturn_t ufshcd_intr(int irq, void *__hba);
static int ufshcd_setup_hba_vreg(struct ufs_hba *hba, bool on);
static int ufshcd_setup_vreg(struct ufs_hba *hba, bool on);
@@ -1208,8 +1206,7 @@ static int ufshcd_opp_set_rate(struct ufs_hba *hba, unsigned long freq)
*
* Return: 0 if successful; < 0 upon failure.
*/
-static int ufshcd_scale_clks(struct ufs_hba *hba, unsigned long freq,
- bool scale_up)
+int ufshcd_scale_clks(struct ufs_hba *hba, unsigned long freq, bool scale_up)
{
int ret = 0;
ktime_t start = ktime_get();
@@ -1362,6 +1359,31 @@ static int ufshcd_wait_for_pending_cmds(struct ufs_hba *hba,
return ret;
}
+int ufshcd_pause_command_processing(struct ufs_hba *hba, u64 timeout_us)
+{
+ int ret = 0;
+
+ mutex_lock(&hba->host->scan_mutex);
+ blk_mq_quiesce_tagset(&hba->host->tag_set);
+ down_write(&hba->clk_scaling_lock);
+
+ if (ufshcd_wait_for_pending_cmds(hba, 1 * USEC_PER_SEC)) {
+ ret = -EBUSY;
+ up_write(&hba->clk_scaling_lock);
+ blk_mq_unquiesce_tagset(&hba->host->tag_set);
+ mutex_unlock(&hba->host->scan_mutex);
+ }
+
+ return ret;
+}
+
+void ufshcd_resume_command_processing(struct ufs_hba *hba)
+{
+ up_write(&hba->clk_scaling_lock);
+ blk_mq_unquiesce_tagset(&hba->host->tag_set);
+ mutex_unlock(&hba->host->scan_mutex);
+}
+
/**
* ufshcd_scale_gear - scale up/down UFS gear
* @hba: per adapter instance
@@ -4815,7 +4837,7 @@ int ufshcd_config_pwr_mode(struct ufs_hba *hba,
if (ret)
memcpy(&final_params, desired_pwr_mode, sizeof(final_params));
- ret = ufshcd_config_tx_eq_settings(hba, &final_params);
+ ret = ufshcd_config_tx_eq_settings(hba, &final_params, false);
if (ret) {
dev_err(hba->dev, "Failed to configure TX Equalization settings for HS-G%u, Rate-%s: %d\n",
final_params.gear_tx,
--
2.34.1
^ permalink raw reply related [flat|nested] 39+ messages in thread* Re: [PATCH v2 06/11] scsi: ufs: core: Add support to retrain TX Equalization via debugfs
2026-03-04 13:53 ` [PATCH v2 06/11] scsi: ufs: core: Add support to retrain TX Equalization via debugfs Can Guo
@ 2026-03-05 14:11 ` Bart Van Assche
2026-03-07 13:02 ` Can Guo
2026-03-05 21:25 ` Bean Huo
1 sibling, 1 reply; 39+ messages in thread
From: Bart Van Assche @ 2026-03-05 14:11 UTC (permalink / raw)
To: Can Guo, avri.altman, beanhuo, martin.petersen
Cc: linux-scsi, Alim Akhtar, James E.J. Bottomley, Peter Wang,
Bao D. Nguyen, Adrian Hunter, open list
On 3/4/26 7:53 AM, Can Guo wrote:
> + ret = kstrtoint_from_user(buf, count, 0, &val);
> + if (ret)
> + return ret;
> +
> + if (val != 1)
> + return -EINVAL;
Why does "1" have to be written into the "retrain_tx_eq" attribute to
trigger retraining? Nobody will know that "1" has to be written into
this attribute without reading the code. I propose to accept strings
for this attribute, e.g. "retrain" to trigger retraining. I expect that
this will make shell scripts that write into this attribute easier to
read.
> +int ufshcd_retrain_tx_eq(struct ufs_hba *hba, u32 gear)
> +{
> + struct ufs_pa_layer_attr new_pwr_info, final_params = { 0 };
> + int ret;
The recommended style for zero-initializing data structures is "{}"
instead of "{ 0 }". The initializer "{}" doesn't trigger any compiler
warnings if the first member of a data structure is a pointer. A
compiler warning will be triggered when using "{ 0 }" and the first
member of a data structure is a pointer.
> + ret = ufshcd_pause_command_processing(hba, 1 * USEC_PER_SEC);
> + if (ret)
> + return ret;
> +
> + ufshcd_hold(hba);
The ufshcd_hold() call probably should come before the
ufshcd_pause_command_processing() call to reduce latency.
> +int ufshcd_pause_command_processing(struct ufs_hba *hba, u64 timeout_us)
> +{
> + int ret = 0;
> +
> + mutex_lock(&hba->host->scan_mutex);
> + blk_mq_quiesce_tagset(&hba->host->tag_set);
> + down_write(&hba->clk_scaling_lock);
> +
> + if (ufshcd_wait_for_pending_cmds(hba, 1 * USEC_PER_SEC)) {
> + ret = -EBUSY;
> + up_write(&hba->clk_scaling_lock);
> + blk_mq_unquiesce_tagset(&hba->host->tag_set);
> + mutex_unlock(&hba->host->scan_mutex);
> + }
> +
> + return ret;
> +}
> +
> +void ufshcd_resume_command_processing(struct ufs_hba *hba)
> +{
> + up_write(&hba->clk_scaling_lock);
> + blk_mq_unquiesce_tagset(&hba->host->tag_set);
> + mutex_unlock(&hba->host->scan_mutex);
> +}
Because of the "one change per patch" rule, introduction of these two
helper functions should go into a separate patch.
Thanks,
Bart.
^ permalink raw reply [flat|nested] 39+ messages in thread* Re: [PATCH v2 06/11] scsi: ufs: core: Add support to retrain TX Equalization via debugfs
2026-03-05 14:11 ` Bart Van Assche
@ 2026-03-07 13:02 ` Can Guo
0 siblings, 0 replies; 39+ messages in thread
From: Can Guo @ 2026-03-07 13:02 UTC (permalink / raw)
To: Bart Van Assche, avri.altman, beanhuo, martin.petersen
Cc: linux-scsi, Alim Akhtar, James E.J. Bottomley, Peter Wang,
Bao D. Nguyen, Adrian Hunter, open list
On 3/5/2026 10:11 PM, Bart Van Assche wrote:
> On 3/4/26 7:53 AM, Can Guo wrote:
>> + ret = kstrtoint_from_user(buf, count, 0, &val);
>> + if (ret)
>> + return ret;
>> +
>> + if (val != 1)
>> + return -EINVAL;
>
> Why does "1" have to be written into the "retrain_tx_eq" attribute to
> trigger retraining? Nobody will know that "1" has to be written into
> this attribute without reading the code. I propose to accept strings
> for this attribute, e.g. "retrain" to trigger retraining. I expect that
> this will make shell scripts that write into this attribute easier to
> read.
Thanks for the suggestion, point taken. Will use string "retrain" as
input for the trigger.
>
>> +int ufshcd_retrain_tx_eq(struct ufs_hba *hba, u32 gear)
>> +{
>> + struct ufs_pa_layer_attr new_pwr_info, final_params = { 0 };
>> + int ret;
>
> The recommended style for zero-initializing data structures is "{}"
> instead of "{ 0 }". The initializer "{}" doesn't trigger any compiler
> warnings if the first member of a data structure is a pointer. A
> compiler warning will be triggered when using "{ 0 }" and the first
> member of a data structure is a pointer.
Thanks for letting me know.
>
>> + ret = ufshcd_pause_command_processing(hba, 1 * USEC_PER_SEC);
>> + if (ret)
>> + return ret;
>> +
>> + ufshcd_hold(hba);
>
> The ufshcd_hold() call probably should come before the
> ufshcd_pause_command_processing() call to reduce latency.
OK.
>
>> +int ufshcd_pause_command_processing(struct ufs_hba *hba, u64
>> timeout_us)
>> +{
>> + int ret = 0;
>> +
>> + mutex_lock(&hba->host->scan_mutex);
>> + blk_mq_quiesce_tagset(&hba->host->tag_set);
>> + down_write(&hba->clk_scaling_lock);
>> +
>> + if (ufshcd_wait_for_pending_cmds(hba, 1 * USEC_PER_SEC)) {
>> + ret = -EBUSY;
>> + up_write(&hba->clk_scaling_lock);
>> + blk_mq_unquiesce_tagset(&hba->host->tag_set);
>> + mutex_unlock(&hba->host->scan_mutex);
>> + }
>> +
>> + return ret;
>> +}
>> +
>> +void ufshcd_resume_command_processing(struct ufs_hba *hba)
>> +{
>> + up_write(&hba->clk_scaling_lock);
>> + blk_mq_unquiesce_tagset(&hba->host->tag_set);
>> + mutex_unlock(&hba->host->scan_mutex);
>> +}
>
> Because of the "one change per patch" rule, introduction of these two
> helper functions should go into a separate patch.
Sure.
Thanks,
Can Guo.
>
> Thanks,
>
> Bart.
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 06/11] scsi: ufs: core: Add support to retrain TX Equalization via debugfs
2026-03-04 13:53 ` [PATCH v2 06/11] scsi: ufs: core: Add support to retrain TX Equalization via debugfs Can Guo
2026-03-05 14:11 ` Bart Van Assche
@ 2026-03-05 21:25 ` Bean Huo
2026-03-06 13:26 ` Can Guo
1 sibling, 1 reply; 39+ messages in thread
From: Bean Huo @ 2026-03-05 21:25 UTC (permalink / raw)
To: Can Guo, avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Alim Akhtar, James E.J. Bottomley, Peter Wang,
Bao D. Nguyen, Adrian Hunter, open list
On Wed, 2026-03-04 at 05:53 -0800, Can Guo wrote:
>
> +int ufshcd_pause_command_processing(struct ufs_hba *hba, u64 timeout_us)
timeout_us is not used function always waits 1 * USEC_PER_SEC.
> +{
> + int ret = 0;
> +
> + mutex_lock(&hba->host->scan_mutex);
> + blk_mq_quiesce_tagset(&hba->host->tag_set);
> + down_write(&hba->clk_scaling_lock);
> +
> + if (ufshcd_wait_for_pending_cmds(hba, 1 * USEC_PER_SEC)) {
> + ret = -EBUSY;
> + up_write(&hba->clk_scaling_lock);
> + blk_mq_unquiesce_tagset(&hba->host->tag_set);
> + mutex_unlock(&hba->host->scan_mutex);
> + }
> +
> + return ret;
> +}
> +
^ permalink raw reply [flat|nested] 39+ messages in thread* Re: [PATCH v2 06/11] scsi: ufs: core: Add support to retrain TX Equalization via debugfs
2026-03-05 21:25 ` Bean Huo
@ 2026-03-06 13:26 ` Can Guo
0 siblings, 0 replies; 39+ messages in thread
From: Can Guo @ 2026-03-06 13:26 UTC (permalink / raw)
To: Bean Huo, avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Alim Akhtar, James E.J. Bottomley, Peter Wang,
Bao D. Nguyen, Adrian Hunter, open list
Hi Bean,
On 3/6/2026 5:25 AM, Bean Huo wrote:
> On Wed, 2026-03-04 at 05:53 -0800, Can Guo wrote:
>>
>> +int ufshcd_pause_command_processing(struct ufs_hba *hba, u64 timeout_us)
>
> timeout_us is not used function always waits 1 * USEC_PER_SEC.
Oh... I missed it... I will use timeout_us in next version.
Thanks,
Can Guo.
>
>
>> +{
>> + int ret = 0;
>> +
>> + mutex_lock(&hba->host->scan_mutex);
>> + blk_mq_quiesce_tagset(&hba->host->tag_set);
>> + down_write(&hba->clk_scaling_lock);
>> +
>> + if (ufshcd_wait_for_pending_cmds(hba, 1 * USEC_PER_SEC)) {
>> + ret = -EBUSY;
>> + up_write(&hba->clk_scaling_lock);
>> + blk_mq_unquiesce_tagset(&hba->host->tag_set);
>> + mutex_unlock(&hba->host->scan_mutex);
>> + }
>> +
>> + return ret;
>> +}
>> +
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 07/11] scsi: ufs: ufs-qcom: Fixup PAM-4 TX L0_L1_L2_L3 adaptation pattern length
2026-03-04 13:53 [PATCH v2 00/11] scsi: ufs: Add TX Equalization support for UFS 5.0 Can Guo
` (5 preceding siblings ...)
2026-03-04 13:53 ` [PATCH v2 06/11] scsi: ufs: core: Add support to retrain TX Equalization via debugfs Can Guo
@ 2026-03-04 13:53 ` Can Guo
2026-03-04 15:10 ` Manivannan Sadhasivam
2026-03-04 13:53 ` [PATCH v2 08/11] scsi: ufs: ufs-qcom: Implement vops tx_eqtr_notify() Can Guo
` (4 subsequent siblings)
11 siblings, 1 reply; 39+ messages in thread
From: Can Guo @ 2026-03-04 13:53 UTC (permalink / raw)
To: avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Can Guo, Manivannan Sadhasivam, James E.J. Bottomley,
open list:ARM/QUALCOMM MAILING LIST, open list
If HS-G6 Power Mode change handshake is successful and outbound data Lanes
are expected to transmit ADAPT, M-TX Lanes shall be configured as
if (Adapt Type == REFRESH)
TX_HS_ADAPT_LENGTH_L0_L1_L2_L3 = PA_PeerRxHsG6AdaptRefreshL0L1L2L3.
else if (Adapt Type == INITIAL)
TX_HS_ADAPT_LENGTH_L0_L1_L2_L3 = PA_PeerRxHsG6AdaptInitialL0L1L2L3.
On some platforms, the ADAPT_L0_L1_L2_L3 duration on Host TX Lanes is only
a half of theoretical ADAPT_L0_L1_L2_L3 duration TADAPT_L0_L1_L2_L3 (in
PAM-4 UI) calculated from TX_HS_ADAPT_LENGTH_L0_L1_L2_L3.
For such platforms, the workaround is to double the ADAPT_L0_L1_L2_L3
duration by uplifting TX_HS_ADAPT_LENGTH_L0_L1_L2_L3. UniPro initializes
TX_HS_ADAPT_LENGTH_L0_L1_L2_L3 during HS-G6 Power Mode change handshake,
it would be too late for SW to update TX_HS_ADAPT_LENGTH_L0_L1_L2_L3 post
HS-G6 Power Mode change. Update PA_PeerRxHsG6AdaptRefreshL0L1L2L3 and
PA_PeerRxHsG6AdaptInitialL0L1L2L3 post Link Startup and before HS-G6
Power Mode change, so that the UniPro would use the updated value during
HS-G6 Power Mode change handshake.
Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
---
drivers/ufs/host/ufs-qcom.c | 175 ++++++++++++++++++++++++++++++++++++
1 file changed, 175 insertions(+)
diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
index 5eb12a999eb1..3a9279066192 100644
--- a/drivers/ufs/host/ufs-qcom.c
+++ b/drivers/ufs/host/ufs-qcom.c
@@ -1079,10 +1079,185 @@ static void ufs_qcom_override_pa_tx_hsg1_sync_len(struct ufs_hba *hba)
dev_err(hba->dev, "Failed (%d) set PA_TX_HSG1_SYNC_LENGTH\n", err);
}
+/**
+ * ufs_qcom_double_t_adapt_l0l1l2l3 - Create a new adapt that doubles the
+ * adaptation duration TADAPT_L0_L1_L2_L3 derived from the old adapt.
+ *
+ * @old_adapt: Original ADAPT_L0_L1_L2_L3 capability
+ *
+ * ADAPT_length_L0_L1_L2_L3 formula from M-PHY spec:
+ * if (ADAPT_range_L0_L1_L2_L3 == COARSE) {
+ * ADAPT_length_L0_L1_L2_L3 = [0, 12]
+ * ADAPT_L0_L1_L2_L3 = 215 x 2^ADAPT_length_L0_L1_L2_L3
+ * } else if (ADAPT_range_L0_L1_L2_L3 == FINE) {
+ * ADAPT_length_L0_L1_L2_L3 = [0, 127]
+ * TADAPT_L0_L1_L2_L3 = 215 x (ADAPT_length_L0_L1_L2_L3 + 1)
+ * }
+ *
+ * To double the adaptation duration TADAPT_L0_L1_L2_L3:
+ * 1. If adapt range is COARSE (1'b1), new adapt = old adapt + 1.
+ * 2. If adapt range is FINE (1'b0):
+ * a) If old adapt length is < 64, (new adapt + 1) = 2 * (old adapt + 1).
+ * b) If old adapt length is >= 64, set new adapt to 0x88 using COARSE
+ * range, because new adapt get from equation in a) shall exceed 127.
+ *
+ * Examples:
+ * ADAPT_range_L0_L1_L2_L3 | ADAPT_length_L0_L1_L2_L3 | TADAPT_L0_L1_L2_L3 (PAM-4 UI)
+ * 0 3 131072
+ * 0 7 262144
+ * 0 63 2097152
+ * 0 64 2129920
+ * 0 127 4194304
+ * 1 8 8388608
+ * 1 9 16777216
+ * 1 10 33554432
+ * 1 11 67108864
+ * 1 12 134217728
+ *
+ * Return: new adapt.
+ */
+static inline u32 ufs_qcom_double_t_adapt_l0l1l2l3(u32 old_adapt)
+{
+ u32 adapt_length = old_adapt & 0x7F;
+ u32 new_adapt;
+
+ /* Adapt range == COARSE */
+ if (old_adapt & 0x80) {
+ new_adapt = (adapt_length + 1) | 0x80;
+ } else {
+ if (adapt_length < 64)
+ new_adapt = (adapt_length << 1) + 1;
+ else
+ new_adapt = 0x88;
+ }
+
+ return new_adapt;
+}
+
+static inline void ufs_qcom_limit_max_gear(struct ufs_hba *hba,
+ enum ufs_hs_gear_tag gear)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info;
+ struct ufs_host_params *host_params = &host->host_params;
+
+ host_params->hs_tx_gear = gear;
+ host_params->hs_rx_gear = gear;
+ pwr_info->gear_tx = gear;
+ pwr_info->gear_rx = gear;
+
+ dev_warn(hba->dev, "Limited max gear of both sides to HS-G%d\n", gear);
+}
+
+static void ufs_qcom_fixup_tx_adapt_l0l1l2l3(struct ufs_hba *hba)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info;
+ struct ufs_host_params *host_params = &host->host_params;
+ u32 adapt_l0l1l2l3, new_adapt, actual_adapt;
+ bool limit_speed = false;
+ int err;
+
+ if (host->hw_ver.major != 0x7 || host->hw_ver.minor > 0x1 ||
+ host_params->hs_tx_gear <= UFS_HS_G5 ||
+ pwr_info->gear_tx <= UFS_HS_G5)
+ return;
+
+ err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTINITIALL0L1L2L3), &adapt_l0l1l2l3);
+ if (err)
+ goto out;
+
+ if (adapt_l0l1l2l3 > ADAPT_L0L1L2L3_LENGTH_MAX) {
+ dev_err(hba->dev, "PA_PeerRxHsG6AdaptInitialL0L1L2L3 value (0x%x) exceeds MAX.\n",
+ adapt_l0l1l2l3);
+ err = -EINVAL;
+ goto out;
+ }
+
+ new_adapt = ufs_qcom_double_t_adapt_l0l1l2l3(adapt_l0l1l2l3);
+ dev_dbg(hba->dev, "Original PA_PeerRxHsG6AdaptInitialL0L1L2L3 value = 0x%x, new value = 0x%x\n",
+ adapt_l0l1l2l3, new_adapt);
+
+ /*
+ * 0x8C is the max possible value allowed by UniPro v3.0 spec, some HWs
+ * can accept 0x8D but some cannot.
+ */
+ if (new_adapt <= ADAPT_L0L1L2L3_LENGTH_MAX ||
+ (new_adapt == ADAPT_L0L1L2L3_LENGTH_MAX + 1 && host->hw_ver.minor == 0x1)) {
+ err = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTINITIALL0L1L2L3),
+ new_adapt);
+ if (err)
+ goto out;
+
+ err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTINITIALL0L1L2L3),
+ &actual_adapt);
+ if (err)
+ goto out;
+
+ if (actual_adapt != new_adapt) {
+ limit_speed = true;
+ dev_warn(hba->dev, "Failed to update host PA_PeerRxHsG6AdaptInitialL0L1L2L3 to new value 0x%x, actual value = 0x%x\n",
+ new_adapt, actual_adapt);
+ }
+ } else {
+ limit_speed = true;
+ dev_warn(hba->dev, "New PA_PeerRxHsG6AdaptInitialL0L1L2L3 value (0x%x) is too large!\n",
+ new_adapt);
+ }
+
+ err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTREFRESHL0L1L2L3), &adapt_l0l1l2l3);
+ if (err)
+ goto out;
+
+ if (adapt_l0l1l2l3 > ADAPT_L0L1L2L3_LENGTH_MAX) {
+ dev_err(hba->dev, "PA_PeerRxHsG6AdaptRefreshL0L1L2L3 value (0x%x) exceeds MAX.\n",
+ adapt_l0l1l2l3);
+ err = -EINVAL;
+ goto out;
+ }
+
+ new_adapt = ufs_qcom_double_t_adapt_l0l1l2l3(adapt_l0l1l2l3);
+ dev_dbg(hba->dev, "Original PA_PeerRxHsG6AdaptRefreshL0L1L2L3 value = 0x%x, new value = 0x%x\n",
+ adapt_l0l1l2l3, new_adapt);
+
+ /*
+ * 0x8C is the max possible value allowed by UniPro v3.0 spec, some HWs
+ * can accept 0x8D but some cannot.
+ */
+ if (new_adapt <= ADAPT_L0L1L2L3_LENGTH_MAX ||
+ (new_adapt == ADAPT_L0L1L2L3_LENGTH_MAX + 1 && host->hw_ver.minor == 0x1)) {
+ err = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTREFRESHL0L1L2L3),
+ new_adapt);
+ if (err)
+ goto out;
+
+ err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTREFRESHL0L1L2L3),
+ &actual_adapt);
+ if (err)
+ goto out;
+
+ if (actual_adapt != new_adapt) {
+ limit_speed = true;
+ dev_warn(hba->dev, "Failed to update host PA_PeerRxHsG6AdaptRefreshL0L1L2L3 to new value 0x%x, actual value = 0x%x\n",
+ new_adapt, actual_adapt);
+ }
+ } else {
+ limit_speed = true;
+ dev_warn(hba->dev, "New PA_PeerRxHsG6AdaptRefreshL0L1L2L3 value (0x%x) is too large!\n",
+ new_adapt);
+ }
+
+out:
+ if (limit_speed || err)
+ ufs_qcom_limit_max_gear(hba, UFS_HS_G5);
+}
+
static int ufs_qcom_apply_dev_quirks(struct ufs_hba *hba)
{
int err = 0;
+ ufs_qcom_fixup_tx_adapt_l0l1l2l3(hba);
+
if (hba->dev_quirks & UFS_DEVICE_QUIRK_HOST_PA_SAVECONFIGTIME)
err = ufs_qcom_quirk_host_pa_saveconfigtime(hba);
--
2.34.1
^ permalink raw reply related [flat|nested] 39+ messages in thread* Re: [PATCH v2 07/11] scsi: ufs: ufs-qcom: Fixup PAM-4 TX L0_L1_L2_L3 adaptation pattern length
2026-03-04 13:53 ` [PATCH v2 07/11] scsi: ufs: ufs-qcom: Fixup PAM-4 TX L0_L1_L2_L3 adaptation pattern length Can Guo
@ 2026-03-04 15:10 ` Manivannan Sadhasivam
2026-03-06 11:14 ` Can Guo
0 siblings, 1 reply; 39+ messages in thread
From: Manivannan Sadhasivam @ 2026-03-04 15:10 UTC (permalink / raw)
To: Can Guo
Cc: avri.altman, bvanassche, beanhuo, martin.petersen, linux-scsi,
James E.J. Bottomley, open list:ARM/QUALCOMM MAILING LIST,
open list
On Wed, Mar 04, 2026 at 05:53:09AM -0800, Can Guo wrote:
> If HS-G6 Power Mode change handshake is successful and outbound data Lanes
> are expected to transmit ADAPT, M-TX Lanes shall be configured as
>
> if (Adapt Type == REFRESH)
> TX_HS_ADAPT_LENGTH_L0_L1_L2_L3 = PA_PeerRxHsG6AdaptRefreshL0L1L2L3.
> else if (Adapt Type == INITIAL)
> TX_HS_ADAPT_LENGTH_L0_L1_L2_L3 = PA_PeerRxHsG6AdaptInitialL0L1L2L3.
>
> On some platforms, the ADAPT_L0_L1_L2_L3 duration on Host TX Lanes is only
> a half of theoretical ADAPT_L0_L1_L2_L3 duration TADAPT_L0_L1_L2_L3 (in
> PAM-4 UI) calculated from TX_HS_ADAPT_LENGTH_L0_L1_L2_L3.
>
> For such platforms, the workaround is to double the ADAPT_L0_L1_L2_L3
> duration by uplifting TX_HS_ADAPT_LENGTH_L0_L1_L2_L3. UniPro initializes
> TX_HS_ADAPT_LENGTH_L0_L1_L2_L3 during HS-G6 Power Mode change handshake,
> it would be too late for SW to update TX_HS_ADAPT_LENGTH_L0_L1_L2_L3 post
> HS-G6 Power Mode change. Update PA_PeerRxHsG6AdaptRefreshL0L1L2L3 and
> PA_PeerRxHsG6AdaptInitialL0L1L2L3 post Link Startup and before HS-G6
> Power Mode change, so that the UniPro would use the updated value during
> HS-G6 Power Mode change handshake.
>
> Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
> ---
> drivers/ufs/host/ufs-qcom.c | 175 ++++++++++++++++++++++++++++++++++++
> 1 file changed, 175 insertions(+)
>
> diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
> index 5eb12a999eb1..3a9279066192 100644
> --- a/drivers/ufs/host/ufs-qcom.c
> +++ b/drivers/ufs/host/ufs-qcom.c
> @@ -1079,10 +1079,185 @@ static void ufs_qcom_override_pa_tx_hsg1_sync_len(struct ufs_hba *hba)
> dev_err(hba->dev, "Failed (%d) set PA_TX_HSG1_SYNC_LENGTH\n", err);
> }
>
> +/**
> + * ufs_qcom_double_t_adapt_l0l1l2l3 - Create a new adapt that doubles the
> + * adaptation duration TADAPT_L0_L1_L2_L3 derived from the old adapt.
> + *
> + * @old_adapt: Original ADAPT_L0_L1_L2_L3 capability
> + *
> + * ADAPT_length_L0_L1_L2_L3 formula from M-PHY spec:
> + * if (ADAPT_range_L0_L1_L2_L3 == COARSE) {
> + * ADAPT_length_L0_L1_L2_L3 = [0, 12]
> + * ADAPT_L0_L1_L2_L3 = 215 x 2^ADAPT_length_L0_L1_L2_L3
> + * } else if (ADAPT_range_L0_L1_L2_L3 == FINE) {
> + * ADAPT_length_L0_L1_L2_L3 = [0, 127]
> + * TADAPT_L0_L1_L2_L3 = 215 x (ADAPT_length_L0_L1_L2_L3 + 1)
> + * }
> + *
> + * To double the adaptation duration TADAPT_L0_L1_L2_L3:
> + * 1. If adapt range is COARSE (1'b1), new adapt = old adapt + 1.
> + * 2. If adapt range is FINE (1'b0):
> + * a) If old adapt length is < 64, (new adapt + 1) = 2 * (old adapt + 1).
> + * b) If old adapt length is >= 64, set new adapt to 0x88 using COARSE
> + * range, because new adapt get from equation in a) shall exceed 127.
> + *
> + * Examples:
> + * ADAPT_range_L0_L1_L2_L3 | ADAPT_length_L0_L1_L2_L3 | TADAPT_L0_L1_L2_L3 (PAM-4 UI)
> + * 0 3 131072
> + * 0 7 262144
> + * 0 63 2097152
> + * 0 64 2129920
> + * 0 127 4194304
> + * 1 8 8388608
> + * 1 9 16777216
> + * 1 10 33554432
> + * 1 11 67108864
> + * 1 12 134217728
> + *
> + * Return: new adapt.
> + */
> +static inline u32 ufs_qcom_double_t_adapt_l0l1l2l3(u32 old_adapt)
No need of 'inline' keyword in a .c file. Same comment to other helpers.
Also, can you change the '_l0l1l2l3' suffix to something like '_level' or
'_length'?
> +{
> + u32 adapt_length = old_adapt & 0x7F;
Please add a define for 0x75
> + u32 new_adapt;
> +
> + /* Adapt range == COARSE */
> + if (old_adapt & 0x80) {
This one also.
> + new_adapt = (adapt_length + 1) | 0x80;
> + } else {
> + if (adapt_length < 64)
And this one.
> + new_adapt = (adapt_length << 1) + 1;
> + else
> + new_adapt = 0x88;
> + }
> +
> + return new_adapt;
> +}
> +
> +static inline void ufs_qcom_limit_max_gear(struct ufs_hba *hba,
> + enum ufs_hs_gear_tag gear)
> +{
> + struct ufs_qcom_host *host = ufshcd_get_variant(hba);
> + struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info;
> + struct ufs_host_params *host_params = &host->host_params;
> +
> + host_params->hs_tx_gear = gear;
> + host_params->hs_rx_gear = gear;
> + pwr_info->gear_tx = gear;
> + pwr_info->gear_rx = gear;
> +
> + dev_warn(hba->dev, "Limited max gear of both sides to HS-G%d\n", gear);
s/both sides/host and device
> +}
> +
> +static void ufs_qcom_fixup_tx_adapt_l0l1l2l3(struct ufs_hba *hba)
> +{
> + struct ufs_qcom_host *host = ufshcd_get_variant(hba);
> + struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info;
> + struct ufs_host_params *host_params = &host->host_params;
> + u32 adapt_l0l1l2l3, new_adapt, actual_adapt;
Can you shorten adapt_l0l1l2l3?
> + bool limit_speed = false;
> + int err;
> +
> + if (host->hw_ver.major != 0x7 || host->hw_ver.minor > 0x1 ||
> + host_params->hs_tx_gear <= UFS_HS_G5 ||
> + pwr_info->gear_tx <= UFS_HS_G5)
> + return;
> +
> + err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTINITIALL0L1L2L3), &adapt_l0l1l2l3);
> + if (err)
> + goto out;
> +
> + if (adapt_l0l1l2l3 > ADAPT_L0L1L2L3_LENGTH_MAX) {
> + dev_err(hba->dev, "PA_PeerRxHsG6AdaptInitialL0L1L2L3 value (0x%x) exceeds MAX.\n",
Nit: remove full stop at the end
> + adapt_l0l1l2l3);
> + err = -EINVAL;
-ERANGE
> + goto out;
> + }
> +
> + new_adapt = ufs_qcom_double_t_adapt_l0l1l2l3(adapt_l0l1l2l3);
> + dev_dbg(hba->dev, "Original PA_PeerRxHsG6AdaptInitialL0L1L2L3 value = 0x%x, new value = 0x%x\n",
> + adapt_l0l1l2l3, new_adapt);
> +
> + /*
> + * 0x8C is the max possible value allowed by UniPro v3.0 spec, some HWs
> + * can accept 0x8D but some cannot.
> + */
> + if (new_adapt <= ADAPT_L0L1L2L3_LENGTH_MAX ||
> + (new_adapt == ADAPT_L0L1L2L3_LENGTH_MAX + 1 && host->hw_ver.minor == 0x1)) {
> + err = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTINITIALL0L1L2L3),
> + new_adapt);
> + if (err)
> + goto out;
> +
> + err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTINITIALL0L1L2L3),
> + &actual_adapt);
> + if (err)
> + goto out;
> +
> + if (actual_adapt != new_adapt) {
> + limit_speed = true;
> + dev_warn(hba->dev, "Failed to update host PA_PeerRxHsG6AdaptInitialL0L1L2L3 to new value 0x%x, actual value = 0x%x\n",
This goes beyond 100 column width. Please consider shortening up. Applies to
other prints as well.
> + new_adapt, actual_adapt);
> + }
> + } else {
> + limit_speed = true;
> + dev_warn(hba->dev, "New PA_PeerRxHsG6AdaptInitialL0L1L2L3 value (0x%x) is too large!\n",
> + new_adapt);
> + }
> +
> + err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTREFRESHL0L1L2L3), &adapt_l0l1l2l3);
> + if (err)
> + goto out;
> +
> + if (adapt_l0l1l2l3 > ADAPT_L0L1L2L3_LENGTH_MAX) {
> + dev_err(hba->dev, "PA_PeerRxHsG6AdaptRefreshL0L1L2L3 value (0x%x) exceeds MAX.\n",
> + adapt_l0l1l2l3);
> + err = -EINVAL;
-ERANGE
> + goto out;
> + }
> +
> + new_adapt = ufs_qcom_double_t_adapt_l0l1l2l3(adapt_l0l1l2l3);
> + dev_dbg(hba->dev, "Original PA_PeerRxHsG6AdaptRefreshL0L1L2L3 value = 0x%x, new value = 0x%x\n",
> + adapt_l0l1l2l3, new_adapt);
> +
> + /*
> + * 0x8C is the max possible value allowed by UniPro v3.0 spec, some HWs
> + * can accept 0x8D but some cannot.
> + */
> + if (new_adapt <= ADAPT_L0L1L2L3_LENGTH_MAX ||
> + (new_adapt == ADAPT_L0L1L2L3_LENGTH_MAX + 1 && host->hw_ver.minor == 0x1)) {
> + err = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTREFRESHL0L1L2L3),
> + new_adapt);
> + if (err)
> + goto out;
> +
> + err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTREFRESHL0L1L2L3),
> + &actual_adapt);
> + if (err)
> + goto out;
> +
> + if (actual_adapt != new_adapt) {
> + limit_speed = true;
> + dev_warn(hba->dev, "Failed to update host PA_PeerRxHsG6AdaptRefreshL0L1L2L3 to new value 0x%x, actual value = 0x%x\n",
> + new_adapt, actual_adapt);
> + }
> + } else {
> + limit_speed = true;
> + dev_warn(hba->dev, "New PA_PeerRxHsG6AdaptRefreshL0L1L2L3 value (0x%x) is too large!\n",
> + new_adapt);
I'm assuming it is safe to continue despite the warnings.
- Mani
--
மணிவண்ணன் சதாசிவம்
^ permalink raw reply [flat|nested] 39+ messages in thread* Re: [PATCH v2 07/11] scsi: ufs: ufs-qcom: Fixup PAM-4 TX L0_L1_L2_L3 adaptation pattern length
2026-03-04 15:10 ` Manivannan Sadhasivam
@ 2026-03-06 11:14 ` Can Guo
0 siblings, 0 replies; 39+ messages in thread
From: Can Guo @ 2026-03-06 11:14 UTC (permalink / raw)
To: Manivannan Sadhasivam
Cc: avri.altman, bvanassche, beanhuo, martin.petersen, linux-scsi,
James E.J. Bottomley, open list:ARM/QUALCOMM MAILING LIST,
open list
Hi Mani,
On 3/4/2026 11:10 PM, Manivannan Sadhasivam wrote:
> On Wed, Mar 04, 2026 at 05:53:09AM -0800, Can Guo wrote:
>> If HS-G6 Power Mode change handshake is successful and outbound data Lanes
>> are expected to transmit ADAPT, M-TX Lanes shall be configured as
>>
>> if (Adapt Type == REFRESH)
>> TX_HS_ADAPT_LENGTH_L0_L1_L2_L3 = PA_PeerRxHsG6AdaptRefreshL0L1L2L3.
>> else if (Adapt Type == INITIAL)
>> TX_HS_ADAPT_LENGTH_L0_L1_L2_L3 = PA_PeerRxHsG6AdaptInitialL0L1L2L3.
>>
>> On some platforms, the ADAPT_L0_L1_L2_L3 duration on Host TX Lanes is only
>> a half of theoretical ADAPT_L0_L1_L2_L3 duration TADAPT_L0_L1_L2_L3 (in
>> PAM-4 UI) calculated from TX_HS_ADAPT_LENGTH_L0_L1_L2_L3.
>>
>> For such platforms, the workaround is to double the ADAPT_L0_L1_L2_L3
>> duration by uplifting TX_HS_ADAPT_LENGTH_L0_L1_L2_L3. UniPro initializes
>> TX_HS_ADAPT_LENGTH_L0_L1_L2_L3 during HS-G6 Power Mode change handshake,
>> it would be too late for SW to update TX_HS_ADAPT_LENGTH_L0_L1_L2_L3 post
>> HS-G6 Power Mode change. Update PA_PeerRxHsG6AdaptRefreshL0L1L2L3 and
>> PA_PeerRxHsG6AdaptInitialL0L1L2L3 post Link Startup and before HS-G6
>> Power Mode change, so that the UniPro would use the updated value during
>> HS-G6 Power Mode change handshake.
>>
>> Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
>> ---
>> drivers/ufs/host/ufs-qcom.c | 175 ++++++++++++++++++++++++++++++++++++
>> 1 file changed, 175 insertions(+)
>>
>> diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
>> index 5eb12a999eb1..3a9279066192 100644
>> --- a/drivers/ufs/host/ufs-qcom.c
>> +++ b/drivers/ufs/host/ufs-qcom.c
>> @@ -1079,10 +1079,185 @@ static void ufs_qcom_override_pa_tx_hsg1_sync_len(struct ufs_hba *hba)
>> dev_err(hba->dev, "Failed (%d) set PA_TX_HSG1_SYNC_LENGTH\n", err);
>> }
>>
>> +/**
>> + * ufs_qcom_double_t_adapt_l0l1l2l3 - Create a new adapt that doubles the
>> + * adaptation duration TADAPT_L0_L1_L2_L3 derived from the old adapt.
>> + *
>> + * @old_adapt: Original ADAPT_L0_L1_L2_L3 capability
>> + *
>> + * ADAPT_length_L0_L1_L2_L3 formula from M-PHY spec:
>> + * if (ADAPT_range_L0_L1_L2_L3 == COARSE) {
>> + * ADAPT_length_L0_L1_L2_L3 = [0, 12]
>> + * ADAPT_L0_L1_L2_L3 = 215 x 2^ADAPT_length_L0_L1_L2_L3
>> + * } else if (ADAPT_range_L0_L1_L2_L3 == FINE) {
>> + * ADAPT_length_L0_L1_L2_L3 = [0, 127]
>> + * TADAPT_L0_L1_L2_L3 = 215 x (ADAPT_length_L0_L1_L2_L3 + 1)
>> + * }
>> + *
>> + * To double the adaptation duration TADAPT_L0_L1_L2_L3:
>> + * 1. If adapt range is COARSE (1'b1), new adapt = old adapt + 1.
>> + * 2. If adapt range is FINE (1'b0):
>> + * a) If old adapt length is < 64, (new adapt + 1) = 2 * (old adapt + 1).
>> + * b) If old adapt length is >= 64, set new adapt to 0x88 using COARSE
>> + * range, because new adapt get from equation in a) shall exceed 127.
>> + *
>> + * Examples:
>> + * ADAPT_range_L0_L1_L2_L3 | ADAPT_length_L0_L1_L2_L3 | TADAPT_L0_L1_L2_L3 (PAM-4 UI)
>> + * 0 3 131072
>> + * 0 7 262144
>> + * 0 63 2097152
>> + * 0 64 2129920
>> + * 0 127 4194304
>> + * 1 8 8388608
>> + * 1 9 16777216
>> + * 1 10 33554432
>> + * 1 11 67108864
>> + * 1 12 134217728
>> + *
>> + * Return: new adapt.
>> + */
>> +static inline u32 ufs_qcom_double_t_adapt_l0l1l2l3(u32 old_adapt)
> No need of 'inline' keyword in a .c file. Same comment to other helpers.
OK.
>
> Also, can you change the '_l0l1l2l3' suffix to something like '_level' or
> '_length'?
>
There are many Adapt length attributes in M-PHY spec, their definitions
are similar
but used for different purposes. To make sure we are capture the correct
one,
let's use the full name
>> +{
>> + u32 adapt_length = old_adapt & 0x7F;
> Please add a define for 0x75
Sure.
>
>> + u32 new_adapt;
>> +
>> + /* Adapt range == COARSE */
>> + if (old_adapt & 0x80) {
> This one also.
Will do.
>
>> + new_adapt = (adapt_length + 1) | 0x80;
>> + } else {
>> + if (adapt_length < 64)
> And this one.
Will do.
>
>> + new_adapt = (adapt_length << 1) + 1;
>> + else
>> + new_adapt = 0x88;
>> + }
>> +
>> + return new_adapt;
>> +}
>> +
>> +static inline void ufs_qcom_limit_max_gear(struct ufs_hba *hba,
>> + enum ufs_hs_gear_tag gear)
>> +{
>> + struct ufs_qcom_host *host = ufshcd_get_variant(hba);
>> + struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info;
>> + struct ufs_host_params *host_params = &host->host_params;
>> +
>> + host_params->hs_tx_gear = gear;
>> + host_params->hs_rx_gear = gear;
>> + pwr_info->gear_tx = gear;
>> + pwr_info->gear_rx = gear;
>> +
>> + dev_warn(hba->dev, "Limited max gear of both sides to HS-G%d\n", gear);
> s/both sides/host and device
OK.
>
>> +}
>> +
>> +static void ufs_qcom_fixup_tx_adapt_l0l1l2l3(struct ufs_hba *hba)
>> +{
>> + struct ufs_qcom_host *host = ufshcd_get_variant(hba);
>> + struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info;
>> + struct ufs_host_params *host_params = &host->host_params;
>> + u32 adapt_l0l1l2l3, new_adapt, actual_adapt;
> Can you shorten adapt_l0l1l2l3?
As I explained above, I want to capture the precise Adapt attribute.
>
>> + bool limit_speed = false;
>> + int err;
>> +
>> + if (host->hw_ver.major != 0x7 || host->hw_ver.minor > 0x1 ||
>> + host_params->hs_tx_gear <= UFS_HS_G5 ||
>> + pwr_info->gear_tx <= UFS_HS_G5)
>> + return;
>> +
>> + err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTINITIALL0L1L2L3), &adapt_l0l1l2l3);
>> + if (err)
>> + goto out;
>> +
>> + if (adapt_l0l1l2l3 > ADAPT_L0L1L2L3_LENGTH_MAX) {
>> + dev_err(hba->dev, "PA_PeerRxHsG6AdaptInitialL0L1L2L3 value (0x%x) exceeds MAX.\n",
> Nit: remove full stop at the end
OK.
>
>> + adapt_l0l1l2l3);
>> + err = -EINVAL;
> -ERANGE
Sure.
>
>> + goto out;
>> + }
>> +
>> + new_adapt = ufs_qcom_double_t_adapt_l0l1l2l3(adapt_l0l1l2l3);
>> + dev_dbg(hba->dev, "Original PA_PeerRxHsG6AdaptInitialL0L1L2L3 value = 0x%x, new value = 0x%x\n",
>> + adapt_l0l1l2l3, new_adapt);
>> +
>> + /*
>> + * 0x8C is the max possible value allowed by UniPro v3.0 spec, some HWs
>> + * can accept 0x8D but some cannot.
>> + */
>> + if (new_adapt <= ADAPT_L0L1L2L3_LENGTH_MAX ||
>> + (new_adapt == ADAPT_L0L1L2L3_LENGTH_MAX + 1 && host->hw_ver.minor == 0x1)) {
>> + err = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTINITIALL0L1L2L3),
>> + new_adapt);
>> + if (err)
>> + goto out;
>> +
>> + err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTINITIALL0L1L2L3),
>> + &actual_adapt);
>> + if (err)
>> + goto out;
>> +
>> + if (actual_adapt != new_adapt) {
>> + limit_speed = true;
>> + dev_warn(hba->dev, "Failed to update host PA_PeerRxHsG6AdaptInitialL0L1L2L3 to new value 0x%x, actual value = 0x%x\n",
> This goes beyond 100 column width. Please consider shortening up. Applies to
> other prints as well.
Will shorten them in next version.
>
>> + new_adapt, actual_adapt);
>> + }
>> + } else {
>> + limit_speed = true;
>> + dev_warn(hba->dev, "New PA_PeerRxHsG6AdaptInitialL0L1L2L3 value (0x%x) is too large!\n",
>> + new_adapt);
>> + }
>> +
>> + err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTREFRESHL0L1L2L3), &adapt_l0l1l2l3);
>> + if (err)
>> + goto out;
>> +
>> + if (adapt_l0l1l2l3 > ADAPT_L0L1L2L3_LENGTH_MAX) {
>> + dev_err(hba->dev, "PA_PeerRxHsG6AdaptRefreshL0L1L2L3 value (0x%x) exceeds MAX.\n",
>> + adapt_l0l1l2l3);
>> + err = -EINVAL;
> -ERANGE
>
>> + goto out;
>> + }
>> +
>> + new_adapt = ufs_qcom_double_t_adapt_l0l1l2l3(adapt_l0l1l2l3);
>> + dev_dbg(hba->dev, "Original PA_PeerRxHsG6AdaptRefreshL0L1L2L3 value = 0x%x, new value = 0x%x\n",
>> + adapt_l0l1l2l3, new_adapt);
>> +
>> + /*
>> + * 0x8C is the max possible value allowed by UniPro v3.0 spec, some HWs
>> + * can accept 0x8D but some cannot.
>> + */
>> + if (new_adapt <= ADAPT_L0L1L2L3_LENGTH_MAX ||
>> + (new_adapt == ADAPT_L0L1L2L3_LENGTH_MAX + 1 && host->hw_ver.minor == 0x1)) {
>> + err = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTREFRESHL0L1L2L3),
>> + new_adapt);
>> + if (err)
>> + goto out;
>> +
>> + err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTREFRESHL0L1L2L3),
>> + &actual_adapt);
>> + if (err)
>> + goto out;
>> +
>> + if (actual_adapt != new_adapt) {
>> + limit_speed = true;
>> + dev_warn(hba->dev, "Failed to update host PA_PeerRxHsG6AdaptRefreshL0L1L2L3 to new value 0x%x, actual value = 0x%x\n",
>> + new_adapt, actual_adapt);
>> + }
>> + } else {
>> + limit_speed = true;
>> + dev_warn(hba->dev, "New PA_PeerRxHsG6AdaptRefreshL0L1L2L3 value (0x%x) is too large!\n",
>> + new_adapt);
> I'm assuming it is safe to continue despite the warnings.
Yes, warning here is to give the reason as well as heads up that it is
going to limit the max gear.
Thanks,
Can Guo.
>
> - Mani
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 08/11] scsi: ufs: ufs-qcom: Implement vops tx_eqtr_notify()
2026-03-04 13:53 [PATCH v2 00/11] scsi: ufs: Add TX Equalization support for UFS 5.0 Can Guo
` (6 preceding siblings ...)
2026-03-04 13:53 ` [PATCH v2 07/11] scsi: ufs: ufs-qcom: Fixup PAM-4 TX L0_L1_L2_L3 adaptation pattern length Can Guo
@ 2026-03-04 13:53 ` Can Guo
2026-03-04 15:20 ` Manivannan Sadhasivam
2026-03-05 21:17 ` Bean Huo
2026-03-04 13:53 ` [PATCH v2 09/11] scsi: ufs: ufs-qcom: Implement vops get_rx_fom() Can Guo
` (3 subsequent siblings)
11 siblings, 2 replies; 39+ messages in thread
From: Can Guo @ 2026-03-04 13:53 UTC (permalink / raw)
To: avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Can Guo, Manivannan Sadhasivam, James E.J. Bottomley,
open list:ARM/QUALCOMM MAILING LIST, open list
On some platforms, HW does not support triggering TX EQTR from the most
reliable High-Speed (HS) Gear (HS Gear1), but only allows to trigger TX
EQTR for the target HS Gear from the same HS Gear. To work around the HW
limitation, implement vops tx_eqtr_notify() to change Power Mode to the
target TX EQTR HS Gear prior to TX EQTR procedure and change Power Mode
back to HS Gear1 (the most reliable gear) post TX EQTR procedure.
Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
---
drivers/ufs/host/ufs-qcom.c | 63 +++++++++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)
diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
index 3a9279066192..1e074058f23d 100644
--- a/drivers/ufs/host/ufs-qcom.c
+++ b/drivers/ufs/host/ufs-qcom.c
@@ -2512,6 +2512,68 @@ static u32 ufs_qcom_freq_to_gear_speed(struct ufs_hba *hba, unsigned long freq)
return min_t(u32, gear, hba->max_pwr_info.info.gear_rx);
}
+static int ufs_qcom_change_power_mode(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ enum ufshcd_pmc_policy pmc_policy)
+{
+ int ret;
+
+ ret = ufs_qcom_pwr_change_notify(hba, PRE_CHANGE, pwr_mode);
+ if (ret) {
+ dev_err(hba->dev, "Power change notify (PRE_CHANGE) failed: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = ufshcd_change_power_mode(hba, pwr_mode, pmc_policy);
+ if (ret)
+ return ret;
+
+ ufs_qcom_pwr_change_notify(hba, POST_CHANGE, pwr_mode);
+
+ return ret;
+}
+
+static int ufs_qcom_tx_eqtr_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status,
+ struct ufs_pa_layer_attr *pwr_mode)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ struct ufs_pa_layer_attr pwr_mode_hs_g1 = {
+ .gear_rx = UFS_HS_G1,
+ .gear_tx = UFS_HS_G1,
+ .lane_rx = pwr_mode->lane_rx,
+ .lane_tx = pwr_mode->lane_tx,
+ .pwr_rx = FAST_MODE,
+ .pwr_tx = FAST_MODE,
+ .hs_rate = pwr_mode->hs_rate,
+ };
+ u32 gear = pwr_mode->gear_tx;
+ u32 rate = pwr_mode->hs_rate;
+ int ret;
+
+ if (host->hw_ver.major != 0x7 || host->hw_ver.minor > 0x1)
+ return 0;
+
+ if (status == PRE_CHANGE) {
+ /* PMC to target HS Gear. */
+ ret = ufs_qcom_change_power_mode(hba, pwr_mode,
+ UFSHCD_PMC_POLICY_DONT_FORCE);
+ if (ret)
+ dev_err(hba->dev, "%s: Failed to change power mode to target HS-G%u, Rate-%s: %d\n",
+ __func__, gear, UFS_HS_RATE_STRING(rate), ret);
+ } else {
+ /* PMC back to HS-G1. */
+ ret = ufs_qcom_change_power_mode(hba, &pwr_mode_hs_g1,
+ UFSHCD_PMC_POLICY_DONT_FORCE);
+ if (ret)
+ dev_err(hba->dev, "%s: Failed to change power mode to HS-G1, Rate-%s: %d\n",
+ __func__, UFS_HS_RATE_STRING(rate), ret);
+ }
+
+ return ret;
+}
+
/*
* struct ufs_hba_qcom_vops - UFS QCOM specific variant operations
*
@@ -2542,6 +2604,7 @@ static const struct ufs_hba_variant_ops ufs_hba_qcom_vops = {
.get_outstanding_cqs = ufs_qcom_get_outstanding_cqs,
.config_esi = ufs_qcom_config_esi,
.freq_to_gear_speed = ufs_qcom_freq_to_gear_speed,
+ .tx_eqtr_notify = ufs_qcom_tx_eqtr_notify,
};
static const struct ufs_hba_variant_ops ufs_hba_qcom_sa8255p_vops = {
--
2.34.1
^ permalink raw reply related [flat|nested] 39+ messages in thread* Re: [PATCH v2 08/11] scsi: ufs: ufs-qcom: Implement vops tx_eqtr_notify()
2026-03-04 13:53 ` [PATCH v2 08/11] scsi: ufs: ufs-qcom: Implement vops tx_eqtr_notify() Can Guo
@ 2026-03-04 15:20 ` Manivannan Sadhasivam
2026-03-06 13:41 ` Can Guo
2026-03-05 21:17 ` Bean Huo
1 sibling, 1 reply; 39+ messages in thread
From: Manivannan Sadhasivam @ 2026-03-04 15:20 UTC (permalink / raw)
To: Can Guo
Cc: avri.altman, bvanassche, beanhuo, martin.petersen, linux-scsi,
James E.J. Bottomley, open list:ARM/QUALCOMM MAILING LIST,
open list
On Wed, Mar 04, 2026 at 05:53:10AM -0800, Can Guo wrote:
> On some platforms, HW does not support triggering TX EQTR from the most
> reliable High-Speed (HS) Gear (HS Gear1), but only allows to trigger TX
> EQTR for the target HS Gear from the same HS Gear. To work around the HW
> limitation, implement vops tx_eqtr_notify() to change Power Mode to the
> target TX EQTR HS Gear prior to TX EQTR procedure and change Power Mode
> back to HS Gear1 (the most reliable gear) post TX EQTR procedure.
>
> Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
> ---
> drivers/ufs/host/ufs-qcom.c | 63 +++++++++++++++++++++++++++++++++++++
> 1 file changed, 63 insertions(+)
>
> diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
> index 3a9279066192..1e074058f23d 100644
> --- a/drivers/ufs/host/ufs-qcom.c
> +++ b/drivers/ufs/host/ufs-qcom.c
> @@ -2512,6 +2512,68 @@ static u32 ufs_qcom_freq_to_gear_speed(struct ufs_hba *hba, unsigned long freq)
> return min_t(u32, gear, hba->max_pwr_info.info.gear_rx);
> }
>
> +static int ufs_qcom_change_power_mode(struct ufs_hba *hba,
> + struct ufs_pa_layer_attr *pwr_mode,
> + enum ufshcd_pmc_policy pmc_policy)
> +{
> + int ret;
> +
> + ret = ufs_qcom_pwr_change_notify(hba, PRE_CHANGE, pwr_mode);
> + if (ret) {
> + dev_err(hba->dev, "Power change notify (PRE_CHANGE) failed: %d\n",
> + ret);
> + return ret;
> + }
> +
> + ret = ufshcd_change_power_mode(hba, pwr_mode, pmc_policy);
> + if (ret)
> + return ret;
> +
> + ufs_qcom_pwr_change_notify(hba, POST_CHANGE, pwr_mode);
> +
> + return ret;
return 0;
> +}
> +
> +static int ufs_qcom_tx_eqtr_notify(struct ufs_hba *hba,
> + enum ufs_notify_change_status status,
> + struct ufs_pa_layer_attr *pwr_mode)
> +{
> + struct ufs_qcom_host *host = ufshcd_get_variant(hba);
> + struct ufs_pa_layer_attr pwr_mode_hs_g1 = {
> + .gear_rx = UFS_HS_G1,
> + .gear_tx = UFS_HS_G1,
> + .lane_rx = pwr_mode->lane_rx,
> + .lane_tx = pwr_mode->lane_tx,
> + .pwr_rx = FAST_MODE,
> + .pwr_tx = FAST_MODE,
> + .hs_rate = pwr_mode->hs_rate,
> + };
> + u32 gear = pwr_mode->gear_tx;
> + u32 rate = pwr_mode->hs_rate;
> + int ret;
> +
> + if (host->hw_ver.major != 0x7 || host->hw_ver.minor > 0x1)
> + return 0;
> +
> + if (status == PRE_CHANGE) {
> + /* PMC to target HS Gear. */
> + ret = ufs_qcom_change_power_mode(hba, pwr_mode,
> + UFSHCD_PMC_POLICY_DONT_FORCE);
> + if (ret)
> + dev_err(hba->dev, "%s: Failed to change power mode to target HS-G%u, Rate-%s: %d\n",
Same comment as other patch. Goes over 100 column.
> + __func__, gear, UFS_HS_RATE_STRING(rate), ret);
Can we please not specify the function name in error log?
- Mani
--
மணிவண்ணன் சதாசிவம்
^ permalink raw reply [flat|nested] 39+ messages in thread* Re: [PATCH v2 08/11] scsi: ufs: ufs-qcom: Implement vops tx_eqtr_notify()
2026-03-04 15:20 ` Manivannan Sadhasivam
@ 2026-03-06 13:41 ` Can Guo
0 siblings, 0 replies; 39+ messages in thread
From: Can Guo @ 2026-03-06 13:41 UTC (permalink / raw)
To: Manivannan Sadhasivam
Cc: avri.altman, bvanassche, beanhuo, martin.petersen, linux-scsi,
James E.J. Bottomley, open list:ARM/QUALCOMM MAILING LIST,
open list
On 3/4/2026 11:20 PM, Manivannan Sadhasivam wrote:
> On Wed, Mar 04, 2026 at 05:53:10AM -0800, Can Guo wrote:
>> On some platforms, HW does not support triggering TX EQTR from the most
>> reliable High-Speed (HS) Gear (HS Gear1), but only allows to trigger TX
>> EQTR for the target HS Gear from the same HS Gear. To work around the HW
>> limitation, implement vops tx_eqtr_notify() to change Power Mode to the
>> target TX EQTR HS Gear prior to TX EQTR procedure and change Power Mode
>> back to HS Gear1 (the most reliable gear) post TX EQTR procedure.
>>
>> Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
>> ---
>> drivers/ufs/host/ufs-qcom.c | 63 +++++++++++++++++++++++++++++++++++++
>> 1 file changed, 63 insertions(+)
>>
>> diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
>> index 3a9279066192..1e074058f23d 100644
>> --- a/drivers/ufs/host/ufs-qcom.c
>> +++ b/drivers/ufs/host/ufs-qcom.c
>> @@ -2512,6 +2512,68 @@ static u32 ufs_qcom_freq_to_gear_speed(struct ufs_hba *hba, unsigned long freq)
>> return min_t(u32, gear, hba->max_pwr_info.info.gear_rx);
>> }
>>
>> +static int ufs_qcom_change_power_mode(struct ufs_hba *hba,
>> + struct ufs_pa_layer_attr *pwr_mode,
>> + enum ufshcd_pmc_policy pmc_policy)
>> +{
>> + int ret;
>> +
>> + ret = ufs_qcom_pwr_change_notify(hba, PRE_CHANGE, pwr_mode);
>> + if (ret) {
>> + dev_err(hba->dev, "Power change notify (PRE_CHANGE) failed: %d\n",
>> + ret);
>> + return ret;
>> + }
>> +
>> + ret = ufshcd_change_power_mode(hba, pwr_mode, pmc_policy);
>> + if (ret)
>> + return ret;
>> +
>> + ufs_qcom_pwr_change_notify(hba, POST_CHANGE, pwr_mode);
>> +
>> + return ret;
> return 0;
This function should not exist, I will remove this function in next
version.
>
>> +}
>> +
>> +static int ufs_qcom_tx_eqtr_notify(struct ufs_hba *hba,
>> + enum ufs_notify_change_status status,
>> + struct ufs_pa_layer_attr *pwr_mode)
>> +{
>> + struct ufs_qcom_host *host = ufshcd_get_variant(hba);
>> + struct ufs_pa_layer_attr pwr_mode_hs_g1 = {
>> + .gear_rx = UFS_HS_G1,
>> + .gear_tx = UFS_HS_G1,
>> + .lane_rx = pwr_mode->lane_rx,
>> + .lane_tx = pwr_mode->lane_tx,
>> + .pwr_rx = FAST_MODE,
>> + .pwr_tx = FAST_MODE,
>> + .hs_rate = pwr_mode->hs_rate,
>> + };
>> + u32 gear = pwr_mode->gear_tx;
>> + u32 rate = pwr_mode->hs_rate;
>> + int ret;
>> +
>> + if (host->hw_ver.major != 0x7 || host->hw_ver.minor > 0x1)
>> + return 0;
>> +
>> + if (status == PRE_CHANGE) {
>> + /* PMC to target HS Gear. */
>> + ret = ufs_qcom_change_power_mode(hba, pwr_mode,
>> + UFSHCD_PMC_POLICY_DONT_FORCE);
>> + if (ret)
>> + dev_err(hba->dev, "%s: Failed to change power mode to target HS-G%u, Rate-%s: %d\n",
> Same comment as other patch. Goes over 100 column.
Will fix in next version.
>
>> + __func__, gear, UFS_HS_RATE_STRING(rate), ret);
> Can we please not specify the function name in error log?
There are too many Power Mode changes in Qualcomm's TX EQTR procedure, I
want to print
some hints to help pinpoint issues easier...
Thanks,
Can Guo.
>
> - Mani
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 08/11] scsi: ufs: ufs-qcom: Implement vops tx_eqtr_notify()
2026-03-04 13:53 ` [PATCH v2 08/11] scsi: ufs: ufs-qcom: Implement vops tx_eqtr_notify() Can Guo
2026-03-04 15:20 ` Manivannan Sadhasivam
@ 2026-03-05 21:17 ` Bean Huo
2026-03-06 13:33 ` Can Guo
1 sibling, 1 reply; 39+ messages in thread
From: Bean Huo @ 2026-03-05 21:17 UTC (permalink / raw)
To: Can Guo, avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Manivannan Sadhasivam, James E.J. Bottomley,
open list:ARM/QUALCOMM MAILING LIST, open list
On Wed, 2026-03-04 at 05:53 -0800, Can Guo wrote:
> freq)
> return min_t(u32, gear, hba->max_pwr_info.info.gear_rx);
> }
>
> +static int ufs_qcom_change_power_mode(struct ufs_hba *hba,
> + struct ufs_pa_layer_attr *pwr_mode,
> + enum ufshcd_pmc_policy pmc_policy)
> +{
> + int ret;
> +
> + ret = ufs_qcom_pwr_change_notify(hba, PRE_CHANGE, pwr_mode);
> + if (ret) {
> + dev_err(hba->dev, "Power change notify (PRE_CHANGE) failed:
> %d\n",
> + ret);
> + return ret;
> + }
> +
> + ret = ufshcd_change_power_mode(hba, pwr_mode, pmc_policy);
> + if (ret)
> + return ret;
> +
> + ufs_qcom_pwr_change_notify(hba, POST_CHANGE, pwr_mode);
> +
> + return ret;
> +}
seems Qcom UFS driver does duplicate notify now, above
ufs_qcom_change_power_mode() does PRE/POST itself, then calls core
ufshcd_change_power_mode() which already does PRE/POST, double side effects? or
I am wrong?
Kind regards,
Bean
^ permalink raw reply [flat|nested] 39+ messages in thread* Re: [PATCH v2 08/11] scsi: ufs: ufs-qcom: Implement vops tx_eqtr_notify()
2026-03-05 21:17 ` Bean Huo
@ 2026-03-06 13:33 ` Can Guo
0 siblings, 0 replies; 39+ messages in thread
From: Can Guo @ 2026-03-06 13:33 UTC (permalink / raw)
To: Bean Huo, avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Manivannan Sadhasivam, James E.J. Bottomley,
open list:ARM/QUALCOMM MAILING LIST, open list
Hi Bean,
On 3/6/2026 5:17 AM, Bean Huo wrote:
> On Wed, 2026-03-04 at 05:53 -0800, Can Guo wrote:
>> freq)
>> return min_t(u32, gear, hba->max_pwr_info.info.gear_rx);
>> }
>>
>> +static int ufs_qcom_change_power_mode(struct ufs_hba *hba,
>> + struct ufs_pa_layer_attr *pwr_mode,
>> + enum ufshcd_pmc_policy pmc_policy)
>> +{
>> + int ret;
>> +
>> + ret = ufs_qcom_pwr_change_notify(hba, PRE_CHANGE, pwr_mode);
>> + if (ret) {
>> + dev_err(hba->dev, "Power change notify (PRE_CHANGE) failed:
>> %d\n",
>> + ret);
>> + return ret;
>> + }
>> +
>> + ret = ufshcd_change_power_mode(hba, pwr_mode, pmc_policy);
>> + if (ret)
>> + return ret;
>> +
>> + ufs_qcom_pwr_change_notify(hba, POST_CHANGE, pwr_mode);
>> +
>> + return ret;
>> +}
> seems Qcom UFS driver does duplicate notify now, above
> ufs_qcom_change_power_mode() does PRE/POST itself, then calls core
> ufshcd_change_power_mode() which already does PRE/POST, double side effects? or
> I am wrong?
You are right... I made a mistake when I cleaned up the code. Thanks for
catching it.
Best Regards,
Can Guo.
>
> Kind regards,
> Bean
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 09/11] scsi: ufs: ufs-qcom: Implement vops get_rx_fom()
2026-03-04 13:53 [PATCH v2 00/11] scsi: ufs: Add TX Equalization support for UFS 5.0 Can Guo
` (7 preceding siblings ...)
2026-03-04 13:53 ` [PATCH v2 08/11] scsi: ufs: ufs-qcom: Implement vops tx_eqtr_notify() Can Guo
@ 2026-03-04 13:53 ` Can Guo
2026-03-04 15:39 ` Manivannan Sadhasivam
2026-03-04 13:53 ` [PATCH v2 10/11] scsi: ufs: ufs-qcom: Implement vops apply_tx_eqtr_settings() Can Guo
` (2 subsequent siblings)
11 siblings, 1 reply; 39+ messages in thread
From: Can Guo @ 2026-03-04 13:53 UTC (permalink / raw)
To: avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Can Guo, Alim Akhtar, James E.J. Bottomley,
Manivannan Sadhasivam, open list,
open list:UNIVERSAL FLASH STORAGE HOST CONTROLLER DRIVER...
On some platforms, host's M-PHY RX_FOM Attribute always reads 0, meaning
SW cannot rely on Figure of Merit (FOM) to identify the optimal TX
Equalization settings for device's TX Lanes. Implement the vops
ufs_qcom_get_rx_fom() such that SW can utilize the UFS Eye Opening Monitor
(EOM) to evaluate the TX Equalization settings for device's TX Lanes.
Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
---
drivers/ufs/core/ufs-txeq.c | 6 +-
drivers/ufs/host/ufs-qcom.c | 315 ++++++++++++++++++++++++++++++++++++
drivers/ufs/host/ufs-qcom.h | 42 +++++
include/ufs/ufshcd.h | 3 +
include/ufs/unipro.h | 16 ++
5 files changed, 379 insertions(+), 3 deletions(-)
diff --git a/drivers/ufs/core/ufs-txeq.c b/drivers/ufs/core/ufs-txeq.c
index 2cd2d5156607..02c1a8479910 100644
--- a/drivers/ufs/core/ufs-txeq.c
+++ b/drivers/ufs/core/ufs-txeq.c
@@ -227,9 +227,8 @@ ufshcd_compose_tx_eq_setting(struct ufshcd_tx_eq_settings *settings,
*
* Returns 0 on success, negative error code otherwise
*/
-static int ufshcd_apply_tx_eq_settings(struct ufs_hba *hba,
- struct ufshcd_tx_eq_params *params,
- u32 gear)
+int ufshcd_apply_tx_eq_settings(struct ufs_hba *hba,
+ struct ufshcd_tx_eq_params *params, u32 gear)
{
u32 setting;
int ret;
@@ -259,6 +258,7 @@ static int ufshcd_apply_tx_eq_settings(struct ufs_hba *hba,
return ret;
}
+EXPORT_SYMBOL_GPL(ufshcd_apply_tx_eq_settings);
/**
* ufshcd_evaluate_fom - Update TX EQ params based on FOM results
diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
index 1e074058f23d..b8fa4670ddd6 100644
--- a/drivers/ufs/host/ufs-qcom.c
+++ b/drivers/ufs/host/ufs-qcom.c
@@ -2534,6 +2534,320 @@ static int ufs_qcom_change_power_mode(struct ufs_hba *hba,
return ret;
}
+static int ufs_qcom_host_eom_config(struct ufs_hba *hba, int lane, int v_step,
+ int t_step, enum ufs_eom_eye_pos eye_pos,
+ u32 target_test_count)
+{
+ u32 volt_step, timing_step;
+ int ret;
+
+ if (v_step > 127 || v_step < -127) {
+ dev_err(hba->dev, "Invalid EOM Voltage Step: %d\n", v_step);
+ return -EINVAL;
+ }
+
+ if (t_step > 63 || t_step < -63) {
+ dev_err(hba->dev, "Invalid EOM Timing Step: %d\n", t_step);
+ return -EINVAL;
+ }
+
+ if (v_step < 0)
+ volt_step = BIT(6) | (u32)(-v_step);
+ else
+ volt_step = (u32)v_step;
+
+ if (t_step < 0)
+ timing_step = BIT(6) | (u32)(-t_step);
+ else
+ timing_step = (u32)t_step;
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_ENABLE,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ BIT(eye_pos) | BIT(6));
+ if (ret) {
+ dev_err(hba->dev, "Failed to enable Host EOM on Lane %d: %d\n",
+ lane, ret);
+ return ret;
+ }
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_TIMING_STEPS,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ timing_step);
+ if (ret) {
+ dev_err(hba->dev, "Failed to set Host EOM timing step on Lane %d: %d\n",
+ lane, ret);
+ return ret;
+ }
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_VOLTAGE_STEPS,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ volt_step);
+ if (ret) {
+ dev_err(hba->dev, "Failed to set Host EOM voltage step on Lane %d: %d\n",
+ lane, ret);
+ return ret;
+ }
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_TARGET_TEST_COUNT,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ target_test_count);
+ if (ret)
+ dev_err(hba->dev, "Failed to set Host EOM target test count on Lane %d: %d\n",
+ lane, ret);
+
+ return ret;
+}
+
+static int ufs_qcom_host_eom_may_stop(struct ufs_hba *hba, int lane,
+ u32 target_test_count, u32 *err_count)
+{
+ u32 start, tested_count, error_count;
+ int ret;
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(RX_EYEMON_START,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ &start);
+ if (ret) {
+ dev_err(hba->dev, "Failed to get Host EOM start status on Lane %d: %d\n",
+ lane, ret);
+ return ret;
+ }
+
+ if (start & 0x1)
+ return -EAGAIN;
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(RX_EYEMON_TESTED_COUNT,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ &tested_count);
+ if (ret) {
+ dev_err(hba->dev, "Failed to get Host EOM tested count on Lane %d: %d\n",
+ lane, ret);
+ return ret;
+ }
+
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(RX_EYEMON_ERROR_COUNT,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ &error_count);
+ if (ret) {
+ dev_err(hba->dev, "Failed to get Host EOM error count on Lane %d: %d\n",
+ lane, ret);
+ return ret;
+ }
+
+ /* EOM can stop */
+ if ((tested_count >= target_test_count - 3) || error_count > 0) {
+ *err_count = error_count;
+
+ /* Disable EOM */
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_ENABLE,
+ UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
+ 0x0);
+ if (ret) {
+ dev_err(hba->dev, "Failed to disable Host EOM on Lane %d: %d\n",
+ lane, ret);
+ return ret;
+ }
+ } else {
+ return -EAGAIN;
+ }
+
+ return ret;
+}
+
+static int ufs_qcom_host_eom_scan(struct ufs_hba *hba, int num_lanes,
+ const struct ufs_eom_coord *eom_coord,
+ u32 target_test_count, u32 *err_count)
+{
+ bool eom_stopped[PA_MAXDATALANES] = { 0 };
+ enum ufs_eom_eye_pos eye_pos;
+ int lane, v_step, t_step, ret;
+ u32 setting;
+
+ if (!err_count || !eom_coord)
+ return -EINVAL;
+
+ if (target_test_count < 8) {
+ dev_err(hba->dev, "%s: Target test count (%u) too small for Host EOM\n",
+ __func__, target_test_count);
+ return -EINVAL;
+ }
+
+ v_step = eom_coord->v_step;
+ t_step = eom_coord->t_step;
+ eye_pos = eom_coord->eye_pos;
+
+ for (lane = 0; lane < num_lanes; lane++) {
+ ret = ufs_qcom_host_eom_config(hba, lane, v_step, t_step,
+ eye_pos, target_test_count);
+ if (ret) {
+ dev_err(hba->dev, "Failed to config Host EOM: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ /*
+ * Trigger a PACP_PWR_req to kick start EOM, but not to really change
+ * the Power Mode.
+ */
+ ret = ufshcd_uic_change_pwr_mode(hba, FAST_MODE << 4 | FAST_MODE);
+ if (ret) {
+ dev_err(hba->dev, "Failed to change power mode to kick start Host EOM: %d\n",
+ ret);
+ return ret;
+ }
+
+more_burst:
+ /* Create burst on Host RX Lane. */
+ ufshcd_dme_peer_get(hba, UIC_ARG_MIB(PA_LOCALVERINFO), &setting);
+
+ for (lane = 0; lane < num_lanes; lane++) {
+ if (eom_stopped[lane])
+ continue;
+
+ ret = ufs_qcom_host_eom_may_stop(hba, lane, target_test_count,
+ &err_count[lane]);
+ if (!ret) {
+ eom_stopped[lane] = true;
+ } else if (ret == -EAGAIN) {
+ /* Need more burst to excercise EOM */
+ goto more_burst;
+ } else {
+ dev_err(hba->dev, "Failed to stop Host EOM: %d\n", ret);
+ return ret;
+ }
+
+ dev_dbg(hba->dev, "%s: Host RX Lane %d EOM, v_step %d, t_step %d, error count %u\n",
+ __func__, lane, v_step, t_step, err_count[lane]);
+ }
+
+ return ret;
+}
+
+static int ufs_qcom_host_sw_rx_fom(struct ufs_hba *hba, int num_lanes, u32 *fom)
+{
+ const struct ufs_eom_coord *eom_coord = sw_rx_fom_eom_coords_g6;
+ u32 eom_err_count[PA_MAXDATALANES] = { 0 };
+ u32 curr_ahit;
+ int lane, i, ret;
+
+ if (!fom)
+ return -EINVAL;
+
+ /* Stop the auto hibernate idle timer */
+ curr_ahit = ufshcd_readl(hba, REG_AUTO_HIBERNATE_IDLE_TIMER);
+ if (curr_ahit)
+ ufshcd_writel(hba, 0, REG_AUTO_HIBERNATE_IDLE_TIMER);
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXHSADAPTTYPE), PA_NO_ADAPT);
+ if (ret) {
+ dev_err(hba->dev, "%s: Failed to select NO_ADAPT before starting Host EOM: %d\n",
+ __func__, ret);
+ goto out;
+ }
+
+ for (i = 0; i < SW_RX_FOM_EOM_COORDS; i++, eom_coord++) {
+ ret = ufs_qcom_host_eom_scan(hba, num_lanes, eom_coord, 0x3F,
+ eom_err_count);
+ if (ret) {
+ dev_err(hba->dev, "%s: Failed to run Host EOM scan: %d\n",
+ __func__, ret);
+ break;
+ }
+
+ for (lane = 0; lane < num_lanes; lane++) {
+ /* Bad coordinates have no weights */
+ if (eom_err_count[lane])
+ continue;
+ fom[lane] += SW_RX_FOM_EOM_COORDS_WEIGHT;
+ }
+ }
+
+out:
+ /* Restore the auto hibernate idle timer */
+ if (curr_ahit)
+ ufshcd_writel(hba, curr_ahit, REG_AUTO_HIBERNATE_IDLE_TIMER);
+
+ return ret;
+}
+
+static int ufs_qcom_get_rx_fom(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter)
+{
+ struct ufshcd_tx_eq_params *params __free(kfree) =
+ kzalloc(sizeof(*params), GFP_KERNEL);
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ struct ufs_pa_layer_attr old_pwr_info;
+ u32 fom[PA_MAXDATALANES] = { 0 };
+ u32 gear = pwr_mode->gear_tx;
+ u32 rate = pwr_mode->hs_rate;
+ int lane, ret;
+
+ if (host->hw_ver.major != 0x7 || host->hw_ver.minor > 0x1 ||
+ gear <= UFS_HS_G5 || !d_iter || !d_iter->is_new)
+ return 0;
+
+ if (gear < UFS_HS_G1 || gear >= UFS_HS_GEAR_MAX)
+ return -EINVAL;
+
+ if (!params)
+ return -ENOMEM;
+
+ memcpy(&old_pwr_info, &hba->pwr_info, sizeof(struct ufs_pa_layer_attr));
+
+ memcpy(params, &hba->tx_eq_params[gear - 1], sizeof(struct ufshcd_tx_eq_params));
+ for (lane = 0; lane < d_iter->num_lanes; lane++) {
+ params->device[lane].preshoot = d_iter->preshoot;
+ params->device[lane].deemphasis = d_iter->deemphasis;
+ }
+
+ /* Use TX EQTR settings as Device's TX Equalization settings. */
+ ret = ufshcd_apply_tx_eq_settings(hba, params, gear);
+ if (ret) {
+ dev_err(hba->dev, "%s: Failed to apply TX EQ settings for HS-G%u: %d\n",
+ __func__, gear, ret);
+ return ret;
+ }
+
+ /* Force PMC to target HS Gear to use new TX Equalization settings. */
+ ret = ufs_qcom_change_power_mode(hba, pwr_mode, UFSHCD_PMC_POLICY_FORCE);
+ if (ret) {
+ dev_err(hba->dev, "%s: Failed to change power mode to HS-G%u, Rate-%s: %d\n",
+ __func__, gear, UFS_HS_RATE_STRING(rate), ret);
+ return ret;
+ }
+
+ ret = ufs_qcom_host_sw_rx_fom(hba, d_iter->num_lanes, fom);
+ if (ret) {
+ dev_err(hba->dev, "Failed to get Host SW FOM for Device TX Lane (PreShoot: %u, DeEmphasis: %u): %d\n",
+ d_iter->preshoot, d_iter->deemphasis, ret);
+ return ret;
+ }
+
+ /* Restore Device's TX Equalization settings. */
+ ret = ufshcd_apply_tx_eq_settings(hba, &hba->tx_eq_params[gear - 1], gear);
+ if (ret) {
+ dev_err(hba->dev, "%s: Failed to apply TX EQ settings for HS-G%u: %d\n",
+ __func__, gear, ret);
+ return ret;
+ }
+
+ /* Restore Power Mode. */
+ ret = ufs_qcom_change_power_mode(hba, &old_pwr_info, UFSHCD_PMC_POLICY_FORCE);
+ if (ret) {
+ dev_err(hba->dev, "%s: Failed to retore power mode to HS-G%u: %d\n",
+ __func__, old_pwr_info.gear_tx, ret);
+ return ret;
+ }
+
+ for (lane = 0; lane < d_iter->num_lanes; lane++)
+ d_iter->fom[lane] = fom[lane];
+
+ return ret;
+}
+
static int ufs_qcom_tx_eqtr_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status,
struct ufs_pa_layer_attr *pwr_mode)
@@ -2604,6 +2918,7 @@ static const struct ufs_hba_variant_ops ufs_hba_qcom_vops = {
.get_outstanding_cqs = ufs_qcom_get_outstanding_cqs,
.config_esi = ufs_qcom_config_esi,
.freq_to_gear_speed = ufs_qcom_freq_to_gear_speed,
+ .get_rx_fom = ufs_qcom_get_rx_fom,
.tx_eqtr_notify = ufs_qcom_tx_eqtr_notify,
};
diff --git a/drivers/ufs/host/ufs-qcom.h b/drivers/ufs/host/ufs-qcom.h
index 1111ab34da01..66fb42453e5c 100644
--- a/drivers/ufs/host/ufs-qcom.h
+++ b/drivers/ufs/host/ufs-qcom.h
@@ -33,6 +33,48 @@
#define DL_VS_CLK_CFG_MASK GENMASK(9, 0)
#define DME_VS_CORE_CLK_CTRL_DME_HW_CGC_EN BIT(9)
+#define SW_RX_FOM_EOM_COORDS 23
+#define SW_RX_FOM_EOM_COORDS_WEIGHT (127 / SW_RX_FOM_EOM_COORDS)
+
+enum ufs_eom_eye_pos {
+ UFS_EOM_EYE_M,
+ UFS_EOM_EYE_L,
+ UFS_EOM_EYE_U,
+ UFS_EOM_EYE_MAX,
+};
+
+struct ufs_eom_coord {
+ int t_step;
+ int v_step;
+ int eye_pos;
+};
+
+static const struct ufs_eom_coord sw_rx_fom_eom_coords_g6[SW_RX_FOM_EOM_COORDS] = {
+ [0] = { -2, -15, UFS_EOM_EYE_M },
+ [1] = { 0, -15, UFS_EOM_EYE_M },
+ [2] = { 2, -15, UFS_EOM_EYE_M },
+ [3] = { -4, -10, UFS_EOM_EYE_M },
+ [4] = { -2, -10, UFS_EOM_EYE_M },
+ [5] = { 0, -10, UFS_EOM_EYE_M },
+ [6] = { 2, -10, UFS_EOM_EYE_M },
+ [7] = { 4, -10, UFS_EOM_EYE_M },
+ [8] = { -6, 0, UFS_EOM_EYE_M },
+ [9] = { -4, 0, UFS_EOM_EYE_M },
+ [10] = { -2, 0, UFS_EOM_EYE_M },
+ [11] = { 0, 0, UFS_EOM_EYE_M },
+ [12] = { 2, 0, UFS_EOM_EYE_M },
+ [13] = { 4, 0, UFS_EOM_EYE_M },
+ [14] = { 6, 0, UFS_EOM_EYE_M },
+ [15] = { -4, 10, UFS_EOM_EYE_M },
+ [16] = { -2, 10, UFS_EOM_EYE_M },
+ [17] = { 0, 10, UFS_EOM_EYE_M },
+ [18] = { 2, 10, UFS_EOM_EYE_M },
+ [19] = { 4, 10, UFS_EOM_EYE_M },
+ [20] = { -2, 15, UFS_EOM_EYE_M },
+ [21] = { 0, 15, UFS_EOM_EYE_M },
+ [22] = { 2, 15, UFS_EOM_EYE_M },
+};
+
/* Qualcomm MCQ Configuration */
#define UFS_QCOM_MCQCAP_QCFGPTR 224 /* 0xE0 in hex */
#define UFS_QCOM_MCQ_CONFIG_OFFSET (UFS_QCOM_MCQCAP_QCFGPTR * 0x200) /* 0x1C000 */
diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h
index 6130ee9aeb03..7b248823c044 100644
--- a/include/ufs/ufshcd.h
+++ b/include/ufs/ufshcd.h
@@ -1486,6 +1486,9 @@ extern int ufshcd_config_pwr_mode(struct ufs_hba *hba,
struct ufs_pa_layer_attr *desired_pwr_mode,
enum ufshcd_pmc_policy pmc_policy);
extern int ufshcd_uic_change_pwr_mode(struct ufs_hba *hba, u8 mode);
+extern int ufshcd_apply_tx_eq_settings(struct ufs_hba *hba,
+ struct ufshcd_tx_eq_params *params,
+ u32 gear);
/* UIC command interfaces for DME primitives */
#define DME_LOCAL 0
diff --git a/include/ufs/unipro.h b/include/ufs/unipro.h
index c6f215bb5ed3..35f4f5fba700 100644
--- a/include/ufs/unipro.h
+++ b/include/ufs/unipro.h
@@ -32,6 +32,8 @@
#define TX_LCC_SEQUENCER 0x0032
#define TX_MIN_ACTIVATETIME 0x0033
#define TX_PWM_G6_G7_SYNC_LENGTH 0x0034
+#define TX_HS_DEEMPHASIS_SETTING 0x0037
+#define TX_HS_PRESHOOT_SETTING 0x003B
#define TX_REFCLKFREQ 0x00EB
#define TX_CFGCLKFREQVAL 0x00EC
#define CFGEXTRATTR 0x00F0
@@ -76,6 +78,20 @@
#define RX_REFCLKFREQ 0x00EB
#define RX_CFGCLKFREQVAL 0x00EC
#define CFGWIDEINLN 0x00F0
+#define RX_EYEMON_CAP 0x00F1
+#define RX_EYEMON_TIMING_MAX_STEPS_CAP 0x00F2
+#define RX_EYEMON_TIMING_MAX_OFFSET_CAP 0x00F3
+#define RX_EYEMON_VOLTAGE_MAX_STEPS_CAP 0x00F4
+#define RX_EYEMON_VOLTAGE_MAX_OFFSET_CAP 0x00F5
+#define RX_EYEMON_ENABLE 0x00F6
+#define RX_EYEMON_TIMING_STEPS 0x00F7
+#define RX_EYEMON_VOLTAGE_STEPS 0x00F8
+#define RX_EYEMON_TARGET_TEST_COUNT 0x00F9
+#define RX_EYEMON_TESTED_COUNT 0x00FA
+#define RX_EYEMON_ERROR_COUNT 0x00FB
+#define RX_EYEMON_START 0x00FC
+#define RX_EYEMON_EXTENDED_ERROR_COUNT 0x00FD
+
#define ENARXDIRECTCFG4 0x00F2
#define ENARXDIRECTCFG3 0x00F3
#define ENARXDIRECTCFG2 0x00F4
--
2.34.1
^ permalink raw reply related [flat|nested] 39+ messages in thread* Re: [PATCH v2 09/11] scsi: ufs: ufs-qcom: Implement vops get_rx_fom()
2026-03-04 13:53 ` [PATCH v2 09/11] scsi: ufs: ufs-qcom: Implement vops get_rx_fom() Can Guo
@ 2026-03-04 15:39 ` Manivannan Sadhasivam
2026-03-06 14:04 ` Can Guo
0 siblings, 1 reply; 39+ messages in thread
From: Manivannan Sadhasivam @ 2026-03-04 15:39 UTC (permalink / raw)
To: Can Guo
Cc: avri.altman, bvanassche, beanhuo, martin.petersen, linux-scsi,
Alim Akhtar, James E.J. Bottomley, open list,
open list:UNIVERSAL FLASH STORAGE HOST CONTROLLER DRIVER...
On Wed, Mar 04, 2026 at 05:53:11AM -0800, Can Guo wrote:
> On some platforms, host's M-PHY RX_FOM Attribute always reads 0, meaning
> SW cannot rely on Figure of Merit (FOM) to identify the optimal TX
> Equalization settings for device's TX Lanes. Implement the vops
> ufs_qcom_get_rx_fom() such that SW can utilize the UFS Eye Opening Monitor
> (EOM) to evaluate the TX Equalization settings for device's TX Lanes.
>
> Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
> ---
> drivers/ufs/core/ufs-txeq.c | 6 +-
> drivers/ufs/host/ufs-qcom.c | 315 ++++++++++++++++++++++++++++++++++++
> drivers/ufs/host/ufs-qcom.h | 42 +++++
> include/ufs/ufshcd.h | 3 +
> include/ufs/unipro.h | 16 ++
> 5 files changed, 379 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/ufs/core/ufs-txeq.c b/drivers/ufs/core/ufs-txeq.c
> index 2cd2d5156607..02c1a8479910 100644
> --- a/drivers/ufs/core/ufs-txeq.c
> +++ b/drivers/ufs/core/ufs-txeq.c
> @@ -227,9 +227,8 @@ ufshcd_compose_tx_eq_setting(struct ufshcd_tx_eq_settings *settings,
> *
> * Returns 0 on success, negative error code otherwise
> */
> -static int ufshcd_apply_tx_eq_settings(struct ufs_hba *hba,
> - struct ufshcd_tx_eq_params *params,
> - u32 gear)
> +int ufshcd_apply_tx_eq_settings(struct ufs_hba *hba,
> + struct ufshcd_tx_eq_params *params, u32 gear)
> {
> u32 setting;
> int ret;
> @@ -259,6 +258,7 @@ static int ufshcd_apply_tx_eq_settings(struct ufs_hba *hba,
>
> return ret;
> }
> +EXPORT_SYMBOL_GPL(ufshcd_apply_tx_eq_settings);
>
> /**
> * ufshcd_evaluate_fom - Update TX EQ params based on FOM results
> diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
> index 1e074058f23d..b8fa4670ddd6 100644
> --- a/drivers/ufs/host/ufs-qcom.c
> +++ b/drivers/ufs/host/ufs-qcom.c
> @@ -2534,6 +2534,320 @@ static int ufs_qcom_change_power_mode(struct ufs_hba *hba,
> return ret;
> }
>
> +static int ufs_qcom_host_eom_config(struct ufs_hba *hba, int lane, int v_step,
> + int t_step, enum ufs_eom_eye_pos eye_pos,
> + u32 target_test_count)
> +{
> + u32 volt_step, timing_step;
> + int ret;
> +
> + if (v_step > 127 || v_step < -127) {
> + dev_err(hba->dev, "Invalid EOM Voltage Step: %d\n", v_step);
> + return -EINVAL;
-ERANGE
> + }
> +
> + if (t_step > 63 || t_step < -63) {
> + dev_err(hba->dev, "Invalid EOM Timing Step: %d\n", t_step);
> + return -EINVAL;
-ERANGE
> + }
> +
> + if (v_step < 0)
> + volt_step = BIT(6) | (u32)(-v_step);
What does BIT(6) correspond to? Create a define maybe?
> + else
> + volt_step = (u32)v_step;
> +
> + if (t_step < 0)
> + timing_step = BIT(6) | (u32)(-t_step);
> + else
> + timing_step = (u32)t_step;
> +
> + ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_ENABLE,
> + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
> + BIT(eye_pos) | BIT(6));
> + if (ret) {
> + dev_err(hba->dev, "Failed to enable Host EOM on Lane %d: %d\n",
> + lane, ret);
> + return ret;
> + }
> +
> + ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_TIMING_STEPS,
> + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
> + timing_step);
> + if (ret) {
> + dev_err(hba->dev, "Failed to set Host EOM timing step on Lane %d: %d\n",
> + lane, ret);
> + return ret;
> + }
> +
> + ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_VOLTAGE_STEPS,
> + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
> + volt_step);
> + if (ret) {
> + dev_err(hba->dev, "Failed to set Host EOM voltage step on Lane %d: %d\n",
> + lane, ret);
> + return ret;
> + }
> +
> + ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_TARGET_TEST_COUNT,
> + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
> + target_test_count);
> + if (ret)
> + dev_err(hba->dev, "Failed to set Host EOM target test count on Lane %d: %d\n",
> + lane, ret);
> +
> + return ret;
> +}
> +
> +static int ufs_qcom_host_eom_may_stop(struct ufs_hba *hba, int lane,
> + u32 target_test_count, u32 *err_count)
> +{
> + u32 start, tested_count, error_count;
> + int ret;
> +
> + ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(RX_EYEMON_START,
> + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
> + &start);
> + if (ret) {
> + dev_err(hba->dev, "Failed to get Host EOM start status on Lane %d: %d\n",
> + lane, ret);
> + return ret;
> + }
> +
> + if (start & 0x1)
> + return -EAGAIN;
> +
> + ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(RX_EYEMON_TESTED_COUNT,
> + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
> + &tested_count);
> + if (ret) {
> + dev_err(hba->dev, "Failed to get Host EOM tested count on Lane %d: %d\n",
> + lane, ret);
> + return ret;
> + }
> +
> + ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(RX_EYEMON_ERROR_COUNT,
> + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
> + &error_count);
> + if (ret) {
> + dev_err(hba->dev, "Failed to get Host EOM error count on Lane %d: %d\n",
> + lane, ret);
> + return ret;
> + }
> +
> + /* EOM can stop */
> + if ((tested_count >= target_test_count - 3) || error_count > 0) {
> + *err_count = error_count;
> +
> + /* Disable EOM */
> + ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_ENABLE,
> + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
> + 0x0);
> + if (ret) {
> + dev_err(hba->dev, "Failed to disable Host EOM on Lane %d: %d\n",
> + lane, ret);
> + return ret;
> + }
> + } else {
> + return -EAGAIN;
> + }
> +
> + return ret;
return 0;
> +}
> +
> +static int ufs_qcom_host_eom_scan(struct ufs_hba *hba, int num_lanes,
> + const struct ufs_eom_coord *eom_coord,
> + u32 target_test_count, u32 *err_count)
> +{
> + bool eom_stopped[PA_MAXDATALANES] = { 0 };
> + enum ufs_eom_eye_pos eye_pos;
> + int lane, v_step, t_step, ret;
> + u32 setting;
> +
> + if (!err_count || !eom_coord)
> + return -EINVAL;
> +
> + if (target_test_count < 8) {
> + dev_err(hba->dev, "%s: Target test count (%u) too small for Host EOM\n",
> + __func__, target_test_count);
No __func__ please.
> + return -EINVAL;
-ERANGE
> + }
> +
> + v_step = eom_coord->v_step;
> + t_step = eom_coord->t_step;
> + eye_pos = eom_coord->eye_pos;
> +
> + for (lane = 0; lane < num_lanes; lane++) {
> + ret = ufs_qcom_host_eom_config(hba, lane, v_step, t_step,
> + eye_pos, target_test_count);
> + if (ret) {
> + dev_err(hba->dev, "Failed to config Host EOM: %d\n",
> + ret);
> + return ret;
> + }
> + }
> +
> + /*
> + * Trigger a PACP_PWR_req to kick start EOM, but not to really change
> + * the Power Mode.
> + */
> + ret = ufshcd_uic_change_pwr_mode(hba, FAST_MODE << 4 | FAST_MODE);
> + if (ret) {
> + dev_err(hba->dev, "Failed to change power mode to kick start Host EOM: %d\n",
> + ret);
> + return ret;
> + }
> +
> +more_burst:
> + /* Create burst on Host RX Lane. */
> + ufshcd_dme_peer_get(hba, UIC_ARG_MIB(PA_LOCALVERINFO), &setting);
> +
> + for (lane = 0; lane < num_lanes; lane++) {
> + if (eom_stopped[lane])
> + continue;
> +
> + ret = ufs_qcom_host_eom_may_stop(hba, lane, target_test_count,
> + &err_count[lane]);
> + if (!ret) {
> + eom_stopped[lane] = true;
> + } else if (ret == -EAGAIN) {
> + /* Need more burst to excercise EOM */
> + goto more_burst;
> + } else {
> + dev_err(hba->dev, "Failed to stop Host EOM: %d\n", ret);
> + return ret;
> + }
> +
> + dev_dbg(hba->dev, "%s: Host RX Lane %d EOM, v_step %d, t_step %d, error count %u\n",
> + __func__, lane, v_step, t_step, err_count[lane]);
> + }
> +
> + return ret;
return 0;
> +}
> +
> +static int ufs_qcom_host_sw_rx_fom(struct ufs_hba *hba, int num_lanes, u32 *fom)
> +{
> + const struct ufs_eom_coord *eom_coord = sw_rx_fom_eom_coords_g6;
> + u32 eom_err_count[PA_MAXDATALANES] = { 0 };
> + u32 curr_ahit;
> + int lane, i, ret;
> +
> + if (!fom)
> + return -EINVAL;
> +
> + /* Stop the auto hibernate idle timer */
> + curr_ahit = ufshcd_readl(hba, REG_AUTO_HIBERNATE_IDLE_TIMER);
> + if (curr_ahit)
> + ufshcd_writel(hba, 0, REG_AUTO_HIBERNATE_IDLE_TIMER);
> +
> + ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXHSADAPTTYPE), PA_NO_ADAPT);
> + if (ret) {
> + dev_err(hba->dev, "%s: Failed to select NO_ADAPT before starting Host EOM: %d\n",
> + __func__, ret);
> + goto out;
> + }
> +
> + for (i = 0; i < SW_RX_FOM_EOM_COORDS; i++, eom_coord++) {
> + ret = ufs_qcom_host_eom_scan(hba, num_lanes, eom_coord, 0x3F,
> + eom_err_count);
> + if (ret) {
> + dev_err(hba->dev, "%s: Failed to run Host EOM scan: %d\n",
> + __func__, ret);
> + break;
> + }
> +
> + for (lane = 0; lane < num_lanes; lane++) {
> + /* Bad coordinates have no weights */
> + if (eom_err_count[lane])
> + continue;
> + fom[lane] += SW_RX_FOM_EOM_COORDS_WEIGHT;
> + }
> + }
> +
> +out:
> + /* Restore the auto hibernate idle timer */
> + if (curr_ahit)
> + ufshcd_writel(hba, curr_ahit, REG_AUTO_HIBERNATE_IDLE_TIMER);
> +
> + return ret;
> +}
> +
> +static int ufs_qcom_get_rx_fom(struct ufs_hba *hba,
> + struct ufs_pa_layer_attr *pwr_mode,
> + struct tx_eqtr_iter *h_iter,
> + struct tx_eqtr_iter *d_iter)
> +{
> + struct ufshcd_tx_eq_params *params __free(kfree) =
> + kzalloc(sizeof(*params), GFP_KERNEL);
> + struct ufs_qcom_host *host = ufshcd_get_variant(hba);
> + struct ufs_pa_layer_attr old_pwr_info;
> + u32 fom[PA_MAXDATALANES] = { 0 };
> + u32 gear = pwr_mode->gear_tx;
> + u32 rate = pwr_mode->hs_rate;
> + int lane, ret;
> +
> + if (host->hw_ver.major != 0x7 || host->hw_ver.minor > 0x1 ||
> + gear <= UFS_HS_G5 || !d_iter || !d_iter->is_new)
> + return 0;
> +
> + if (gear < UFS_HS_G1 || gear >= UFS_HS_GEAR_MAX)
> + return -EINVAL;
-ERANGE
> +
> + if (!params)
> + return -ENOMEM;
> +
> + memcpy(&old_pwr_info, &hba->pwr_info, sizeof(struct ufs_pa_layer_attr));
> +
> + memcpy(params, &hba->tx_eq_params[gear - 1], sizeof(struct ufshcd_tx_eq_params));
> + for (lane = 0; lane < d_iter->num_lanes; lane++) {
> + params->device[lane].preshoot = d_iter->preshoot;
> + params->device[lane].deemphasis = d_iter->deemphasis;
> + }
> +
> + /* Use TX EQTR settings as Device's TX Equalization settings. */
> + ret = ufshcd_apply_tx_eq_settings(hba, params, gear);
> + if (ret) {
> + dev_err(hba->dev, "%s: Failed to apply TX EQ settings for HS-G%u: %d\n",
> + __func__, gear, ret);
> + return ret;
> + }
> +
> + /* Force PMC to target HS Gear to use new TX Equalization settings. */
> + ret = ufs_qcom_change_power_mode(hba, pwr_mode, UFSHCD_PMC_POLICY_FORCE);
> + if (ret) {
> + dev_err(hba->dev, "%s: Failed to change power mode to HS-G%u, Rate-%s: %d\n",
> + __func__, gear, UFS_HS_RATE_STRING(rate), ret);
> + return ret;
> + }
> +
> + ret = ufs_qcom_host_sw_rx_fom(hba, d_iter->num_lanes, fom);
> + if (ret) {
> + dev_err(hba->dev, "Failed to get Host SW FOM for Device TX Lane (PreShoot: %u, DeEmphasis: %u): %d\n",
> + d_iter->preshoot, d_iter->deemphasis, ret);
> + return ret;
> + }
> +
> + /* Restore Device's TX Equalization settings. */
> + ret = ufshcd_apply_tx_eq_settings(hba, &hba->tx_eq_params[gear - 1], gear);
> + if (ret) {
> + dev_err(hba->dev, "%s: Failed to apply TX EQ settings for HS-G%u: %d\n",
> + __func__, gear, ret);
> + return ret;
> + }
> +
> + /* Restore Power Mode. */
> + ret = ufs_qcom_change_power_mode(hba, &old_pwr_info, UFSHCD_PMC_POLICY_FORCE);
> + if (ret) {
> + dev_err(hba->dev, "%s: Failed to retore power mode to HS-G%u: %d\n",
> + __func__, old_pwr_info.gear_tx, ret);
> + return ret;
> + }
> +
> + for (lane = 0; lane < d_iter->num_lanes; lane++)
> + d_iter->fom[lane] = fom[lane];
> +
> + return ret;
return 0;
- Mani
--
மணிவண்ணன் சதாசிவம்
^ permalink raw reply [flat|nested] 39+ messages in thread* Re: [PATCH v2 09/11] scsi: ufs: ufs-qcom: Implement vops get_rx_fom()
2026-03-04 15:39 ` Manivannan Sadhasivam
@ 2026-03-06 14:04 ` Can Guo
0 siblings, 0 replies; 39+ messages in thread
From: Can Guo @ 2026-03-06 14:04 UTC (permalink / raw)
To: Manivannan Sadhasivam
Cc: avri.altman, bvanassche, beanhuo, martin.petersen, linux-scsi,
Alim Akhtar, James E.J. Bottomley, open list,
open list:UNIVERSAL FLASH STORAGE HOST CONTROLLER DRIVER...
On 3/4/2026 11:39 PM, Manivannan Sadhasivam wrote:
> On Wed, Mar 04, 2026 at 05:53:11AM -0800, Can Guo wrote:
>> On some platforms, host's M-PHY RX_FOM Attribute always reads 0, meaning
>> SW cannot rely on Figure of Merit (FOM) to identify the optimal TX
>> Equalization settings for device's TX Lanes. Implement the vops
>> ufs_qcom_get_rx_fom() such that SW can utilize the UFS Eye Opening Monitor
>> (EOM) to evaluate the TX Equalization settings for device's TX Lanes.
>>
>> Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
>> ---
>> drivers/ufs/core/ufs-txeq.c | 6 +-
>> drivers/ufs/host/ufs-qcom.c | 315 ++++++++++++++++++++++++++++++++++++
>> drivers/ufs/host/ufs-qcom.h | 42 +++++
>> include/ufs/ufshcd.h | 3 +
>> include/ufs/unipro.h | 16 ++
>> 5 files changed, 379 insertions(+), 3 deletions(-)
>>
>> diff --git a/drivers/ufs/core/ufs-txeq.c b/drivers/ufs/core/ufs-txeq.c
>> index 2cd2d5156607..02c1a8479910 100644
>> --- a/drivers/ufs/core/ufs-txeq.c
>> +++ b/drivers/ufs/core/ufs-txeq.c
>> @@ -227,9 +227,8 @@ ufshcd_compose_tx_eq_setting(struct ufshcd_tx_eq_settings *settings,
>> *
>> * Returns 0 on success, negative error code otherwise
>> */
>> -static int ufshcd_apply_tx_eq_settings(struct ufs_hba *hba,
>> - struct ufshcd_tx_eq_params *params,
>> - u32 gear)
>> +int ufshcd_apply_tx_eq_settings(struct ufs_hba *hba,
>> + struct ufshcd_tx_eq_params *params, u32 gear)
>> {
>> u32 setting;
>> int ret;
>> @@ -259,6 +258,7 @@ static int ufshcd_apply_tx_eq_settings(struct ufs_hba *hba,
>>
>> return ret;
>> }
>> +EXPORT_SYMBOL_GPL(ufshcd_apply_tx_eq_settings);
>>
>> /**
>> * ufshcd_evaluate_fom - Update TX EQ params based on FOM results
>> diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
>> index 1e074058f23d..b8fa4670ddd6 100644
>> --- a/drivers/ufs/host/ufs-qcom.c
>> +++ b/drivers/ufs/host/ufs-qcom.c
>> @@ -2534,6 +2534,320 @@ static int ufs_qcom_change_power_mode(struct ufs_hba *hba,
>> return ret;
>> }
>>
>> +static int ufs_qcom_host_eom_config(struct ufs_hba *hba, int lane, int v_step,
>> + int t_step, enum ufs_eom_eye_pos eye_pos,
>> + u32 target_test_count)
>> +{
>> + u32 volt_step, timing_step;
>> + int ret;
>> +
>> + if (v_step > 127 || v_step < -127) {
>> + dev_err(hba->dev, "Invalid EOM Voltage Step: %d\n", v_step);
>> + return -EINVAL;
> -ERANGE
Done.
>
>> + }
>> +
>> + if (t_step > 63 || t_step < -63) {
>> + dev_err(hba->dev, "Invalid EOM Timing Step: %d\n", t_step);
>> + return -EINVAL;
> -ERANGE
Done.
>
>> + }
>> +
>> + if (v_step < 0)
>> + volt_step = BIT(6) | (u32)(-v_step);
> What does BIT(6) correspond to? Create a define maybe?
Will do.
>
>> + else
>> + volt_step = (u32)v_step;
>> +
>> + if (t_step < 0)
>> + timing_step = BIT(6) | (u32)(-t_step);
>> + else
>> + timing_step = (u32)t_step;
>> +
>> + ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_ENABLE,
>> + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
>> + BIT(eye_pos) | BIT(6));
>> + if (ret) {
>> + dev_err(hba->dev, "Failed to enable Host EOM on Lane %d: %d\n",
>> + lane, ret);
>> + return ret;
>> + }
>> +
>> + ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_TIMING_STEPS,
>> + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
>> + timing_step);
>> + if (ret) {
>> + dev_err(hba->dev, "Failed to set Host EOM timing step on Lane %d: %d\n",
>> + lane, ret);
>> + return ret;
>> + }
>> +
>> + ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_VOLTAGE_STEPS,
>> + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
>> + volt_step);
>> + if (ret) {
>> + dev_err(hba->dev, "Failed to set Host EOM voltage step on Lane %d: %d\n",
>> + lane, ret);
>> + return ret;
>> + }
>> +
>> + ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_TARGET_TEST_COUNT,
>> + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
>> + target_test_count);
>> + if (ret)
>> + dev_err(hba->dev, "Failed to set Host EOM target test count on Lane %d: %d\n",
>> + lane, ret);
>> +
>> + return ret;
>> +}
>> +
>> +static int ufs_qcom_host_eom_may_stop(struct ufs_hba *hba, int lane,
>> + u32 target_test_count, u32 *err_count)
>> +{
>> + u32 start, tested_count, error_count;
>> + int ret;
>> +
>> + ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(RX_EYEMON_START,
>> + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
>> + &start);
>> + if (ret) {
>> + dev_err(hba->dev, "Failed to get Host EOM start status on Lane %d: %d\n",
>> + lane, ret);
>> + return ret;
>> + }
>> +
>> + if (start & 0x1)
>> + return -EAGAIN;
>> +
>> + ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(RX_EYEMON_TESTED_COUNT,
>> + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
>> + &tested_count);
>> + if (ret) {
>> + dev_err(hba->dev, "Failed to get Host EOM tested count on Lane %d: %d\n",
>> + lane, ret);
>> + return ret;
>> + }
>> +
>> + ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(RX_EYEMON_ERROR_COUNT,
>> + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
>> + &error_count);
>> + if (ret) {
>> + dev_err(hba->dev, "Failed to get Host EOM error count on Lane %d: %d\n",
>> + lane, ret);
>> + return ret;
>> + }
>> +
>> + /* EOM can stop */
>> + if ((tested_count >= target_test_count - 3) || error_count > 0) {
>> + *err_count = error_count;
>> +
>> + /* Disable EOM */
>> + ret = ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RX_EYEMON_ENABLE,
>> + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
>> + 0x0);
>> + if (ret) {
>> + dev_err(hba->dev, "Failed to disable Host EOM on Lane %d: %d\n",
>> + lane, ret);
>> + return ret;
>> + }
>> + } else {
>> + return -EAGAIN;
>> + }
>> +
>> + return ret;
> return 0;
Done.
>> +}
>> +
>> +static int ufs_qcom_host_eom_scan(struct ufs_hba *hba, int num_lanes,
>> + const struct ufs_eom_coord *eom_coord,
>> + u32 target_test_count, u32 *err_count)
>> +{
>> + bool eom_stopped[PA_MAXDATALANES] = { 0 };
>> + enum ufs_eom_eye_pos eye_pos;
>> + int lane, v_step, t_step, ret;
>> + u32 setting;
>> +
>> + if (!err_count || !eom_coord)
>> + return -EINVAL;
>> +
>> + if (target_test_count < 8) {
>> + dev_err(hba->dev, "%s: Target test count (%u) too small for Host EOM\n",
>> + __func__, target_test_count);
> No __func__ please.
Done.
>
>> + return -EINVAL;
> -ERANGE
Done.
>
>> + }
>> +
>> + v_step = eom_coord->v_step;
>> + t_step = eom_coord->t_step;
>> + eye_pos = eom_coord->eye_pos;
>> +
>> + for (lane = 0; lane < num_lanes; lane++) {
>> + ret = ufs_qcom_host_eom_config(hba, lane, v_step, t_step,
>> + eye_pos, target_test_count);
>> + if (ret) {
>> + dev_err(hba->dev, "Failed to config Host EOM: %d\n",
>> + ret);
>> + return ret;
>> + }
>> + }
>> +
>> + /*
>> + * Trigger a PACP_PWR_req to kick start EOM, but not to really change
>> + * the Power Mode.
>> + */
>> + ret = ufshcd_uic_change_pwr_mode(hba, FAST_MODE << 4 | FAST_MODE);
>> + if (ret) {
>> + dev_err(hba->dev, "Failed to change power mode to kick start Host EOM: %d\n",
>> + ret);
>> + return ret;
>> + }
>> +
>> +more_burst:
>> + /* Create burst on Host RX Lane. */
>> + ufshcd_dme_peer_get(hba, UIC_ARG_MIB(PA_LOCALVERINFO), &setting);
>> +
>> + for (lane = 0; lane < num_lanes; lane++) {
>> + if (eom_stopped[lane])
>> + continue;
>> +
>> + ret = ufs_qcom_host_eom_may_stop(hba, lane, target_test_count,
>> + &err_count[lane]);
>> + if (!ret) {
>> + eom_stopped[lane] = true;
>> + } else if (ret == -EAGAIN) {
>> + /* Need more burst to excercise EOM */
>> + goto more_burst;
>> + } else {
>> + dev_err(hba->dev, "Failed to stop Host EOM: %d\n", ret);
>> + return ret;
>> + }
>> +
>> + dev_dbg(hba->dev, "%s: Host RX Lane %d EOM, v_step %d, t_step %d, error count %u\n",
>> + __func__, lane, v_step, t_step, err_count[lane]);
>> + }
>> +
>> + return ret;
> return 0;
>
>> +}
>> +
>> +static int ufs_qcom_host_sw_rx_fom(struct ufs_hba *hba, int num_lanes, u32 *fom)
>> +{
>> + const struct ufs_eom_coord *eom_coord = sw_rx_fom_eom_coords_g6;
>> + u32 eom_err_count[PA_MAXDATALANES] = { 0 };
>> + u32 curr_ahit;
>> + int lane, i, ret;
>> +
>> + if (!fom)
>> + return -EINVAL;
>> +
>> + /* Stop the auto hibernate idle timer */
>> + curr_ahit = ufshcd_readl(hba, REG_AUTO_HIBERNATE_IDLE_TIMER);
>> + if (curr_ahit)
>> + ufshcd_writel(hba, 0, REG_AUTO_HIBERNATE_IDLE_TIMER);
>> +
>> + ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXHSADAPTTYPE), PA_NO_ADAPT);
>> + if (ret) {
>> + dev_err(hba->dev, "%s: Failed to select NO_ADAPT before starting Host EOM: %d\n",
>> + __func__, ret);
>> + goto out;
>> + }
>> +
>> + for (i = 0; i < SW_RX_FOM_EOM_COORDS; i++, eom_coord++) {
>> + ret = ufs_qcom_host_eom_scan(hba, num_lanes, eom_coord, 0x3F,
>> + eom_err_count);
>> + if (ret) {
>> + dev_err(hba->dev, "%s: Failed to run Host EOM scan: %d\n",
>> + __func__, ret);
>> + break;
>> + }
>> +
>> + for (lane = 0; lane < num_lanes; lane++) {
>> + /* Bad coordinates have no weights */
>> + if (eom_err_count[lane])
>> + continue;
>> + fom[lane] += SW_RX_FOM_EOM_COORDS_WEIGHT;
>> + }
>> + }
>> +
>> +out:
>> + /* Restore the auto hibernate idle timer */
>> + if (curr_ahit)
>> + ufshcd_writel(hba, curr_ahit, REG_AUTO_HIBERNATE_IDLE_TIMER);
>> +
>> + return ret;
>> +}
>> +
>> +static int ufs_qcom_get_rx_fom(struct ufs_hba *hba,
>> + struct ufs_pa_layer_attr *pwr_mode,
>> + struct tx_eqtr_iter *h_iter,
>> + struct tx_eqtr_iter *d_iter)
>> +{
>> + struct ufshcd_tx_eq_params *params __free(kfree) =
>> + kzalloc(sizeof(*params), GFP_KERNEL);
>> + struct ufs_qcom_host *host = ufshcd_get_variant(hba);
>> + struct ufs_pa_layer_attr old_pwr_info;
>> + u32 fom[PA_MAXDATALANES] = { 0 };
>> + u32 gear = pwr_mode->gear_tx;
>> + u32 rate = pwr_mode->hs_rate;
>> + int lane, ret;
>> +
>> + if (host->hw_ver.major != 0x7 || host->hw_ver.minor > 0x1 ||
>> + gear <= UFS_HS_G5 || !d_iter || !d_iter->is_new)
>> + return 0;
>> +
>> + if (gear < UFS_HS_G1 || gear >= UFS_HS_GEAR_MAX)
>> + return -EINVAL;
> -ERANGE
>
>> +
>> + if (!params)
>> + return -ENOMEM;
>> +
>> + memcpy(&old_pwr_info, &hba->pwr_info, sizeof(struct ufs_pa_layer_attr));
>> +
>> + memcpy(params, &hba->tx_eq_params[gear - 1], sizeof(struct ufshcd_tx_eq_params));
>> + for (lane = 0; lane < d_iter->num_lanes; lane++) {
>> + params->device[lane].preshoot = d_iter->preshoot;
>> + params->device[lane].deemphasis = d_iter->deemphasis;
>> + }
>> +
>> + /* Use TX EQTR settings as Device's TX Equalization settings. */
>> + ret = ufshcd_apply_tx_eq_settings(hba, params, gear);
>> + if (ret) {
>> + dev_err(hba->dev, "%s: Failed to apply TX EQ settings for HS-G%u: %d\n",
>> + __func__, gear, ret);
>> + return ret;
>> + }
>> +
>> + /* Force PMC to target HS Gear to use new TX Equalization settings. */
>> + ret = ufs_qcom_change_power_mode(hba, pwr_mode, UFSHCD_PMC_POLICY_FORCE);
>> + if (ret) {
>> + dev_err(hba->dev, "%s: Failed to change power mode to HS-G%u, Rate-%s: %d\n",
>> + __func__, gear, UFS_HS_RATE_STRING(rate), ret);
>> + return ret;
>> + }
>> +
>> + ret = ufs_qcom_host_sw_rx_fom(hba, d_iter->num_lanes, fom);
>> + if (ret) {
>> + dev_err(hba->dev, "Failed to get Host SW FOM for Device TX Lane (PreShoot: %u, DeEmphasis: %u): %d\n",
>> + d_iter->preshoot, d_iter->deemphasis, ret);
>> + return ret;
>> + }
>> +
>> + /* Restore Device's TX Equalization settings. */
>> + ret = ufshcd_apply_tx_eq_settings(hba, &hba->tx_eq_params[gear - 1], gear);
>> + if (ret) {
>> + dev_err(hba->dev, "%s: Failed to apply TX EQ settings for HS-G%u: %d\n",
>> + __func__, gear, ret);
>> + return ret;
>> + }
>> +
>> + /* Restore Power Mode. */
>> + ret = ufs_qcom_change_power_mode(hba, &old_pwr_info, UFSHCD_PMC_POLICY_FORCE);
>> + if (ret) {
>> + dev_err(hba->dev, "%s: Failed to retore power mode to HS-G%u: %d\n",
>> + __func__, old_pwr_info.gear_tx, ret);
>> + return ret;
>> + }
>> +
>> + for (lane = 0; lane < d_iter->num_lanes; lane++)
>> + d_iter->fom[lane] = fom[lane];
>> +
>> + return ret;
> return 0;
Done.
> - Mani
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 10/11] scsi: ufs: ufs-qcom: Implement vops apply_tx_eqtr_settings()
2026-03-04 13:53 [PATCH v2 00/11] scsi: ufs: Add TX Equalization support for UFS 5.0 Can Guo
` (8 preceding siblings ...)
2026-03-04 13:53 ` [PATCH v2 09/11] scsi: ufs: ufs-qcom: Implement vops get_rx_fom() Can Guo
@ 2026-03-04 13:53 ` Can Guo
2026-03-04 15:41 ` Manivannan Sadhasivam
2026-03-04 13:53 ` [PATCH v2 11/11] scsi: ufs: ufs-qcom: Enable TX Equalization Can Guo
2026-03-05 15:04 ` [PATCH v2 00/11] scsi: ufs: Add TX Equalization support for UFS 5.0 Bean Huo
11 siblings, 1 reply; 39+ messages in thread
From: Can Guo @ 2026-03-04 13:53 UTC (permalink / raw)
To: avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Can Guo, Manivannan Sadhasivam, James E.J. Bottomley,
open list:ARM/QUALCOMM MAILING LIST, open list
On some platforms, when Host Software triggers TX Equalization Training,
HW does not take TX EQTR settings programmed in PA_TxEQTRSetting, instead
HW takes TX EQTR settings from PA_TxEQG1Setting. Implement vops
apply_tx_eqtr_setting() to work around it by programming TX EQTR settings
to PA_TxEQG1Setting during TX EQTR procedure.
Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
---
drivers/ufs/host/ufs-qcom.c | 33 +++++++++++++++++++++++++++++++++
drivers/ufs/host/ufs-qcom.h | 2 ++
2 files changed, 35 insertions(+)
diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
index b8fa4670ddd6..89bea823a08b 100644
--- a/drivers/ufs/host/ufs-qcom.c
+++ b/drivers/ufs/host/ufs-qcom.c
@@ -2848,6 +2848,28 @@ static int ufs_qcom_get_rx_fom(struct ufs_hba *hba,
return ret;
}
+static int ufs_qcom_apply_tx_eqtr_settings(struct ufs_hba *hba,
+ struct ufs_pa_layer_attr *pwr_mode,
+ struct tx_eqtr_iter *h_iter,
+ struct tx_eqtr_iter *d_iter)
+{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ u32 setting = 0;
+ int lane, ret;
+
+ if (host->hw_ver.major != 0x7 || host->hw_ver.minor > 0x1)
+ return 0;
+
+ for (lane = 0; lane < h_iter->num_lanes; lane++) {
+ setting |= TX_HS_PRESHOOT_BITS(lane, h_iter->preshoot);
+ setting |= TX_HS_DEEMPHASIS_BITS(lane, h_iter->deemphasis);
+ }
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXEQG1SETTING), setting);
+
+ return ret;
+}
+
static int ufs_qcom_tx_eqtr_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status,
struct ufs_pa_layer_attr *pwr_mode)
@@ -2870,6 +2892,11 @@ static int ufs_qcom_tx_eqtr_notify(struct ufs_hba *hba,
return 0;
if (status == PRE_CHANGE) {
+ ret = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_TXEQG1SETTING),
+ &host->saved_tx_eq_g1_setting);
+ if (ret)
+ return ret;
+
/* PMC to target HS Gear. */
ret = ufs_qcom_change_power_mode(hba, pwr_mode,
UFSHCD_PMC_POLICY_DONT_FORCE);
@@ -2877,6 +2904,11 @@ static int ufs_qcom_tx_eqtr_notify(struct ufs_hba *hba,
dev_err(hba->dev, "%s: Failed to change power mode to target HS-G%u, Rate-%s: %d\n",
__func__, gear, UFS_HS_RATE_STRING(rate), ret);
} else {
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXEQG1SETTING),
+ host->saved_tx_eq_g1_setting);
+ if (ret)
+ return ret;
+
/* PMC back to HS-G1. */
ret = ufs_qcom_change_power_mode(hba, &pwr_mode_hs_g1,
UFSHCD_PMC_POLICY_DONT_FORCE);
@@ -2919,6 +2951,7 @@ static const struct ufs_hba_variant_ops ufs_hba_qcom_vops = {
.config_esi = ufs_qcom_config_esi,
.freq_to_gear_speed = ufs_qcom_freq_to_gear_speed,
.get_rx_fom = ufs_qcom_get_rx_fom,
+ .apply_tx_eqtr_settings = ufs_qcom_apply_tx_eqtr_settings,
.tx_eqtr_notify = ufs_qcom_tx_eqtr_notify,
};
diff --git a/drivers/ufs/host/ufs-qcom.h b/drivers/ufs/host/ufs-qcom.h
index 66fb42453e5c..ebe4e07c7da1 100644
--- a/drivers/ufs/host/ufs-qcom.h
+++ b/drivers/ufs/host/ufs-qcom.h
@@ -350,6 +350,8 @@ struct ufs_qcom_host {
u32 phy_gear;
bool esi_enabled;
+
+ u32 saved_tx_eq_g1_setting;
};
struct ufs_qcom_drvdata {
--
2.34.1
^ permalink raw reply related [flat|nested] 39+ messages in thread* Re: [PATCH v2 10/11] scsi: ufs: ufs-qcom: Implement vops apply_tx_eqtr_settings()
2026-03-04 13:53 ` [PATCH v2 10/11] scsi: ufs: ufs-qcom: Implement vops apply_tx_eqtr_settings() Can Guo
@ 2026-03-04 15:41 ` Manivannan Sadhasivam
2026-03-06 14:05 ` Can Guo
0 siblings, 1 reply; 39+ messages in thread
From: Manivannan Sadhasivam @ 2026-03-04 15:41 UTC (permalink / raw)
To: Can Guo
Cc: avri.altman, bvanassche, beanhuo, martin.petersen, linux-scsi,
James E.J. Bottomley, open list:ARM/QUALCOMM MAILING LIST,
open list
On Wed, Mar 04, 2026 at 05:53:12AM -0800, Can Guo wrote:
> On some platforms, when Host Software triggers TX Equalization Training,
> HW does not take TX EQTR settings programmed in PA_TxEQTRSetting, instead
> HW takes TX EQTR settings from PA_TxEQG1Setting. Implement vops
> apply_tx_eqtr_setting() to work around it by programming TX EQTR settings
> to PA_TxEQG1Setting during TX EQTR procedure.
>
> Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
> ---
> drivers/ufs/host/ufs-qcom.c | 33 +++++++++++++++++++++++++++++++++
> drivers/ufs/host/ufs-qcom.h | 2 ++
> 2 files changed, 35 insertions(+)
>
> diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
> index b8fa4670ddd6..89bea823a08b 100644
> --- a/drivers/ufs/host/ufs-qcom.c
> +++ b/drivers/ufs/host/ufs-qcom.c
> @@ -2848,6 +2848,28 @@ static int ufs_qcom_get_rx_fom(struct ufs_hba *hba,
> return ret;
> }
>
> +static int ufs_qcom_apply_tx_eqtr_settings(struct ufs_hba *hba,
> + struct ufs_pa_layer_attr *pwr_mode,
> + struct tx_eqtr_iter *h_iter,
> + struct tx_eqtr_iter *d_iter)
> +{
> + struct ufs_qcom_host *host = ufshcd_get_variant(hba);
> + u32 setting = 0;
> + int lane, ret;
> +
> + if (host->hw_ver.major != 0x7 || host->hw_ver.minor > 0x1)
> + return 0;
> +
> + for (lane = 0; lane < h_iter->num_lanes; lane++) {
> + setting |= TX_HS_PRESHOOT_BITS(lane, h_iter->preshoot);
> + setting |= TX_HS_DEEMPHASIS_BITS(lane, h_iter->deemphasis);
> + }
> +
> + ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXEQG1SETTING), setting);
nit: return ...
- Mani
--
மணிவண்ணன் சதாசிவம்
^ permalink raw reply [flat|nested] 39+ messages in thread* Re: [PATCH v2 10/11] scsi: ufs: ufs-qcom: Implement vops apply_tx_eqtr_settings()
2026-03-04 15:41 ` Manivannan Sadhasivam
@ 2026-03-06 14:05 ` Can Guo
0 siblings, 0 replies; 39+ messages in thread
From: Can Guo @ 2026-03-06 14:05 UTC (permalink / raw)
To: Manivannan Sadhasivam
Cc: avri.altman, bvanassche, beanhuo, martin.petersen, linux-scsi,
James E.J. Bottomley, open list:ARM/QUALCOMM MAILING LIST,
open list
On 3/4/2026 11:41 PM, Manivannan Sadhasivam wrote:
> On Wed, Mar 04, 2026 at 05:53:12AM -0800, Can Guo wrote:
>> On some platforms, when Host Software triggers TX Equalization Training,
>> HW does not take TX EQTR settings programmed in PA_TxEQTRSetting, instead
>> HW takes TX EQTR settings from PA_TxEQG1Setting. Implement vops
>> apply_tx_eqtr_setting() to work around it by programming TX EQTR settings
>> to PA_TxEQG1Setting during TX EQTR procedure.
>>
>> Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
>> ---
>> drivers/ufs/host/ufs-qcom.c | 33 +++++++++++++++++++++++++++++++++
>> drivers/ufs/host/ufs-qcom.h | 2 ++
>> 2 files changed, 35 insertions(+)
>>
>> diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
>> index b8fa4670ddd6..89bea823a08b 100644
>> --- a/drivers/ufs/host/ufs-qcom.c
>> +++ b/drivers/ufs/host/ufs-qcom.c
>> @@ -2848,6 +2848,28 @@ static int ufs_qcom_get_rx_fom(struct ufs_hba *hba,
>> return ret;
>> }
>>
>> +static int ufs_qcom_apply_tx_eqtr_settings(struct ufs_hba *hba,
>> + struct ufs_pa_layer_attr *pwr_mode,
>> + struct tx_eqtr_iter *h_iter,
>> + struct tx_eqtr_iter *d_iter)
>> +{
>> + struct ufs_qcom_host *host = ufshcd_get_variant(hba);
>> + u32 setting = 0;
>> + int lane, ret;
>> +
>> + if (host->hw_ver.major != 0x7 || host->hw_ver.minor > 0x1)
>> + return 0;
>> +
>> + for (lane = 0; lane < h_iter->num_lanes; lane++) {
>> + setting |= TX_HS_PRESHOOT_BITS(lane, h_iter->preshoot);
>> + setting |= TX_HS_DEEMPHASIS_BITS(lane, h_iter->deemphasis);
>> + }
>> +
>> + ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXEQG1SETTING), setting);
> nit: return ...
Sure Sir.
Thanks.
Can Guo.
>
> - Mani
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 11/11] scsi: ufs: ufs-qcom: Enable TX Equalization
2026-03-04 13:53 [PATCH v2 00/11] scsi: ufs: Add TX Equalization support for UFS 5.0 Can Guo
` (9 preceding siblings ...)
2026-03-04 13:53 ` [PATCH v2 10/11] scsi: ufs: ufs-qcom: Implement vops apply_tx_eqtr_settings() Can Guo
@ 2026-03-04 13:53 ` Can Guo
2026-03-05 15:04 ` [PATCH v2 00/11] scsi: ufs: Add TX Equalization support for UFS 5.0 Bean Huo
11 siblings, 0 replies; 39+ messages in thread
From: Can Guo @ 2026-03-04 13:53 UTC (permalink / raw)
To: avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Can Guo, Manivannan Sadhasivam, James E.J. Bottomley,
open list:UNIVERSAL FLASH STORAGE HOST CONTROLLER DRIVER...,
open list
Enable TX Equalization for hosts with HW version 0x7 and onwards.
Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
---
drivers/ufs/host/ufs-qcom.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
index 89bea823a08b..3dee1d5f9e6f 100644
--- a/drivers/ufs/host/ufs-qcom.c
+++ b/drivers/ufs/host/ufs-qcom.c
@@ -1391,6 +1391,8 @@ static void ufs_qcom_set_host_caps(struct ufs_hba *hba)
static void ufs_qcom_set_caps(struct ufs_hba *hba)
{
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+
hba->caps |= UFSHCD_CAP_CLK_GATING | UFSHCD_CAP_HIBERN8_WITH_CLK_GATING;
hba->caps |= UFSHCD_CAP_CLK_SCALING | UFSHCD_CAP_WB_WITH_CLK_SCALING;
hba->caps |= UFSHCD_CAP_AUTO_BKOPS_SUSPEND;
@@ -1398,6 +1400,9 @@ static void ufs_qcom_set_caps(struct ufs_hba *hba)
hba->caps |= UFSHCD_CAP_AGGR_POWER_COLLAPSE;
hba->caps |= UFSHCD_CAP_RPM_AUTOSUSPEND;
+ if (host->hw_ver.major >= 0x7)
+ hba->caps |= UFSHCD_CAP_TX_EQUALIZATION;
+
ufs_qcom_set_host_caps(hba);
}
--
2.34.1
^ permalink raw reply related [flat|nested] 39+ messages in thread* Re: [PATCH v2 00/11] scsi: ufs: Add TX Equalization support for UFS 5.0
2026-03-04 13:53 [PATCH v2 00/11] scsi: ufs: Add TX Equalization support for UFS 5.0 Can Guo
` (10 preceding siblings ...)
2026-03-04 13:53 ` [PATCH v2 11/11] scsi: ufs: ufs-qcom: Enable TX Equalization Can Guo
@ 2026-03-05 15:04 ` Bean Huo
2026-03-06 14:14 ` Can Guo
11 siblings, 1 reply; 39+ messages in thread
From: Bean Huo @ 2026-03-05 15:04 UTC (permalink / raw)
To: Can Guo, avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Matthias Brugger, AngeloGioacchino Del Regno,
open list:ARM/Mediatek SoC support:Keyword:mediatek,
moderated list:ARM/Mediatek SoC support:Keyword:mediatek,
moderated list:ARM/Mediatek SoC support:Keyword:mediatek
Can,
On Wed, 2026-03-04 at 05:53 -0800, Can Guo wrote:
> Hi,
>
> The UFS 5.0 standard was published today,
Myabe replace time sensitive wording “published today” with stable wording?
> introducing support for HS-G6
> (23.2 Gbps per lane) through the new UniPro V3.0 interconnect layer and
> M-PHY V6.0 physical layer specifications. To achieve reliable operation
> at these higher speeds, UniPro V3.0 introduces TX Equalization and
> Pre-Coding mechanisms that are essential for signal integrity.
>
> This patch series implements TX Equalization support in the UFS core
> driver as specified in UFSHCI v5.0, along with the necessary vendor
> operations and a reference implementation for Qualcomm UFS host
> controllers.
>
> Background
> ==========
>
> TX Equalization is a signal conditioning technique that compensates for
> channel impairments at high data rates (HS-G4 through HS-G6). It works
> by adjusting two key parameters:
>
> - PreShoot: Pre-emphasis applied before the main signal transition
> - DeEmphasis: De-emphasis applied after the main signal transition
>
> UniPro V3.0 defines TX Equalization Training (EQTR) procedure to
> automatically discover optimal TX Equalization settings. The EQTR
> procedure:
>
> 1. Starts from the most reliable link state (HS-G1)
> 2. Iterates through all possible PreShoot and DeEmphasis combinations
> 3. Evaluates signal quality using Figure of Merit (FOM) measurements
> 4. Selects the best settings for both host and device TX lanes
>
what happens when EQTR fails, mabye you have this comments in the patch.
Kind regards,
Bean
^ permalink raw reply [flat|nested] 39+ messages in thread* Re: [PATCH v2 00/11] scsi: ufs: Add TX Equalization support for UFS 5.0
2026-03-05 15:04 ` [PATCH v2 00/11] scsi: ufs: Add TX Equalization support for UFS 5.0 Bean Huo
@ 2026-03-06 14:14 ` Can Guo
0 siblings, 0 replies; 39+ messages in thread
From: Can Guo @ 2026-03-06 14:14 UTC (permalink / raw)
To: Bean Huo, avri.altman, bvanassche, beanhuo, martin.petersen
Cc: linux-scsi, Matthias Brugger, AngeloGioacchino Del Regno,
open list:ARM/Mediatek SoC support:Keyword:mediatek,
moderated list:ARM/Mediatek SoC support:Keyword:mediatek,
moderated list:ARM/Mediatek SoC support:Keyword:mediatek
On 3/5/2026 11:04 PM, Bean Huo wrote:
> Can,
>
> On Wed, 2026-03-04 at 05:53 -0800, Can Guo wrote:
>> Hi,
>>
>> The UFS 5.0 standard was published today,
> Myabe replace time sensitive wording “published today” with stable wording?
That is also my plan for next version, thanks!
>
>
>> introducing support for HS-G6
>> (23.2 Gbps per lane) through the new UniPro V3.0 interconnect layer and
>> M-PHY V6.0 physical layer specifications. To achieve reliable operation
>> at these higher speeds, UniPro V3.0 introduces TX Equalization and
>> Pre-Coding mechanisms that are essential for signal integrity.
>>
>> This patch series implements TX Equalization support in the UFS core
>> driver as specified in UFSHCI v5.0, along with the necessary vendor
>> operations and a reference implementation for Qualcomm UFS host
>> controllers.
>>
>> Background
>> ==========
>>
>> TX Equalization is a signal conditioning technique that compensates for
>> channel impairments at high data rates (HS-G4 through HS-G6). It works
>> by adjusting two key parameters:
>>
>> - PreShoot: Pre-emphasis applied before the main signal transition
>> - DeEmphasis: De-emphasis applied after the main signal transition
>>
>> UniPro V3.0 defines TX Equalization Training (EQTR) procedure to
>> automatically discover optimal TX Equalization settings. The EQTR
>> procedure:
>>
>> 1. Starts from the most reliable link state (HS-G1)
>> 2. Iterates through all possible PreShoot and DeEmphasis combinations
>> 3. Evaluates signal quality using Figure of Merit (FOM) measurements
>> 4. Selects the best settings for both host and device TX lanes
>>
> what happens when EQTR fails, mabye you have this comments in the patch.
This is a good question. In this version, once TX EQTR fails,
ufshcd_config_pwr_mode()
would bail with error before calling ufshcd_change_power_mode(). But I
am planning
to loosen it in next version, that is, if TX EQTR fails the TX
Equalization settings will remain
unchanged and ufshcd_config_pwr_mode() would anyways go ahead change
power mode.
Thanks,
Can Guo.
>
> Kind regards,
> Bean
>
>
^ permalink raw reply [flat|nested] 39+ messages in thread