From: Can Guo <can.guo@oss.qualcomm.com>
To: avri.altman@wdc.com, bvanassche@acm.org, beanhuo@micron.com,
peter.wang@mediatek.com, martin.petersen@oracle.com,
mani@kernel.org
Cc: linux-scsi@vger.kernel.org, Can Guo <can.guo@oss.qualcomm.com>,
Alim Akhtar <alim.akhtar@samsung.com>,
"James E.J. Bottomley" <James.Bottomley@HansenPartnership.com>,
"Bao D. Nguyen" <quic_nguyenb@quicinc.com>,
linux-kernel@vger.kernel.org (open list)
Subject: [PATCH v4 05/12] scsi: ufs: core: Add debugfs entries for TX Equalization params
Date: Fri, 20 Mar 2026 20:10:14 -0700 [thread overview]
Message-ID: <20260321031021.1722459-6-can.guo@oss.qualcomm.com> (raw)
In-Reply-To: <20260321031021.1722459-1-can.guo@oss.qualcomm.com>
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
Reviewed-by: Bart Van Assche <bvanassche@acm.org>
Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
---
drivers/ufs/core/ufs-debugfs.c | 229 +++++++++++++++++++++++++++++++++
drivers/ufs/core/ufs-txeq.c | 7 +-
drivers/ufs/core/ufshcd-priv.h | 2 +
3 files changed, 237 insertions(+), 1 deletion(-)
diff --git a/drivers/ufs/core/ufs-debugfs.c b/drivers/ufs/core/ufs-debugfs.c
index e3baed6c70bd..831758b45163 100644
--- a/drivers/ufs/core/ufs-debugfs.c
+++ b/drivers/ufs/core/ufs-debugfs.c
@@ -209,6 +209,204 @@ static const struct ufs_debugfs_attr ufs_attrs[] = {
{ }
};
+static int ufs_tx_eq_params_show(struct seq_file *s, void *data)
+{
+ const char *file_name = s->file->f_path.dentry->d_name.name;
+ u32 gear = (u32)(uintptr_t)s->file->f_inode->i_private;
+ struct ufs_hba *hba = hba_from_file(s->file);
+ struct ufshcd_tx_eq_settings *settings;
+ struct ufs_pa_layer_attr *pwr_info;
+ struct ufshcd_tx_eq_params *params;
+ 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;
+ }
+
+ if (!hba->max_pwr_info.is_valid) {
+ seq_puts(s, "Max power info is invalid\n");
+ return 0;
+ }
+
+ pwr_info = &hba->max_pwr_info.info;
+ 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_to_str(rate));
+ return 0;
+ }
+
+ if (strcmp(file_name, "host_tx_eq_params") == 0) {
+ settings = params->host;
+ num_lanes = pwr_info->lane_tx;
+ 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 = pwr_info->lane_rx;
+ 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_to_str(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)
+{
+ const char *file_name = s->file->f_path.dentry->d_name.name;
+ u8 (*fom_array)[TX_HS_NUM_PRESHOOT][TX_HS_NUM_DEEMPHASIS];
+ u32 gear = (u32)(uintptr_t)s->file->f_inode->i_private;
+ unsigned long preshoot_bitmap, deemphasis_bitmap;
+ struct ufs_hba *hba = hba_from_file(s->file);
+ struct ufs_pa_layer_attr *pwr_info;
+ struct ufshcd_tx_eq_params *params;
+ struct ufshcd_tx_eqtr_record *rec;
+ u32 rate = hba->pwr_info.hs_rate;
+ u8 preshoot, deemphasis;
+ u32 num_lanes;
+ char name[32];
+ 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;
+ }
+
+ if (!hba->max_pwr_info.is_valid) {
+ seq_puts(s, "Max power info is invalid\n");
+ return 0;
+ }
+
+ pwr_info = &hba->max_pwr_info.info;
+ 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_to_str(rate));
+ return 0;
+ }
+
+ rec = params->eqtr_record;
+ if (!rec || !rec->last_record_index) {
+ seq_printf(s, "No TX EQTR records found for HS-G%u, Rate-%s.\n",
+ gear, ufs_hs_rate_to_str(rate));
+ return 0;
+ }
+
+ if (strcmp(file_name, "host_tx_eqtr_record") == 0) {
+ preshoot_bitmap = (hba->host_preshoot_cap << 0x1) | 0x1;
+ deemphasis_bitmap = (hba->host_deemphasis_cap << 0x1) | 0x1;
+ num_lanes = pwr_info->lane_tx;
+ fom_array = rec->host_fom;
+ snprintf(name, sizeof(name), "%s", "Host");
+ } else if (strcmp(file_name, "device_tx_eqtr_record") == 0) {
+ preshoot_bitmap = (hba->device_preshoot_cap << 0x1) | 0x1;
+ deemphasis_bitmap = (hba->device_deemphasis_cap << 0x1) | 0x1;
+ num_lanes = pwr_info->lane_rx;
+ fom_array = rec->device_fom;
+ snprintf(name, sizeof(name), "%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_to_str(rate));
+ seq_printf(s, "Most recent record index: %d\n",
+ rec->last_record_index);
+ seq_printf(s, "Most recent record timestamp: %llu us\n",
+ ktime_to_us(rec->last_record_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)) {
+ u8 fom = fom_array[lane][preshoot][deemphasis];
+ u8 fom_val = fom & RX_FOM_VALUE_MASK;
+ bool precode_en = fom & RX_FOM_PRECODING_EN_BIT;
+
+ if (ufshcd_is_txeq_presets_used(hba) &&
+ !ufshcd_is_txeq_preset_selected(preshoot, deemphasis))
+ 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 +428,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, sizeof(name), "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)
diff --git a/drivers/ufs/core/ufs-txeq.c b/drivers/ufs/core/ufs-txeq.c
index 90ee54363519..89ed932fe24e 100644
--- a/drivers/ufs/core/ufs-txeq.c
+++ b/drivers/ufs/core/ufs-txeq.c
@@ -375,7 +375,12 @@ static int ufshcd_get_rx_fom(struct ufs_hba *hba,
return ret;
}
-static bool ufshcd_is_txeq_preset_selected(u8 preshoot, u8 deemphasis)
+bool ufshcd_is_txeq_presets_used(struct ufs_hba *hba)
+{
+ return use_txeq_presets;
+}
+
+bool ufshcd_is_txeq_preset_selected(u8 preshoot, u8 deemphasis)
{
int i;
diff --git a/drivers/ufs/core/ufshcd-priv.h b/drivers/ufs/core/ufshcd-priv.h
index 20ec8d8ac0a4..13a957dc11d0 100644
--- a/drivers/ufs/core/ufshcd-priv.h
+++ b/drivers/ufs/core/ufshcd-priv.h
@@ -108,6 +108,8 @@ 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);
+bool ufshcd_is_txeq_presets_used(struct ufs_hba *hba);
+bool ufshcd_is_txeq_preset_selected(u8 preshoot, u8 deemphasis);
/* Wrapper functions for safely calling variant operations */
static inline const char *ufshcd_get_var_name(struct ufs_hba *hba)
--
2.34.1
next prev parent reply other threads:[~2026-03-21 3:11 UTC|newest]
Thread overview: 35+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-21 3:10 [PATCH v4 00/12] scsi: ufs: Add TX Equalization support for UFS 5.0 Can Guo
2026-03-21 3:10 ` [PATCH v4 01/12] scsi: ufs: core: Introduce a new ufshcd vops negotiate_pwr_mode() Can Guo
2026-03-23 9:10 ` Bean Huo
2026-03-21 3:10 ` [PATCH v4 02/12] scsi: ufs: core: Pass force_pmc to ufshcd_config_pwr_mode() as a parameter Can Guo
2026-03-23 9:11 ` Bean Huo
2026-03-21 3:10 ` [PATCH v4 03/12] scsi: ufs: core: Add UFS_HS_G6 and UFS_HS_GEAR_MAX to enum ufs_hs_gear_tag Can Guo
2026-03-23 9:11 ` Bean Huo
2026-03-21 3:10 ` [PATCH v4 04/12] scsi: ufs: core: Add support for TX Equalization Can Guo
2026-03-22 9:48 ` Bean Huo
2026-03-22 14:26 ` Bean Huo
2026-03-23 6:05 ` Can Guo
2026-03-23 9:15 ` Bean Huo
2026-03-24 7:46 ` Peter Wang (王信友)
2026-03-24 10:09 ` Can Guo
2026-03-24 11:54 ` Peter Wang (王信友)
2026-03-24 12:32 ` Can Guo
2026-03-21 3:10 ` Can Guo [this message]
2026-03-23 9:16 ` [PATCH v4 05/12] scsi: ufs: core: Add debugfs entries for TX Equalization params Bean Huo
2026-03-21 3:10 ` [PATCH v4 06/12] scsi: ufs: core: Add helpers to pause and resume command processing Can Guo
2026-03-23 9:17 ` Bean Huo
2026-03-21 3:10 ` [PATCH v4 07/12] scsi: ufs: core: Add support to retrain TX Equalization via debugfs Can Guo
2026-03-22 13:36 ` Bean Huo
2026-03-23 6:13 ` Can Guo
2026-03-23 9:21 ` Bean Huo
2026-03-21 3:10 ` [PATCH v4 08/12] scsi: ufs: ufs-qcom: Fixup PAM-4 TX L0_L1_L2_L3 adaptation pattern length Can Guo
2026-03-23 9:22 ` Bean Huo
2026-03-21 3:10 ` [PATCH v4 09/12] scsi: ufs: ufs-qcom: Implement vops tx_eqtr_notify() Can Guo
2026-03-23 9:23 ` Bean Huo
2026-03-21 3:10 ` [PATCH v4 10/12] scsi: ufs: ufs-qcom: Implement vops get_rx_fom() Can Guo
2026-03-23 9:24 ` Bean Huo
2026-03-21 3:10 ` [PATCH v4 11/12] scsi: ufs: ufs-qcom: Implement vops apply_tx_eqtr_settings() Can Guo
2026-03-23 9:24 ` Bean Huo
2026-03-21 3:10 ` [PATCH v4 12/12] scsi: ufs: ufs-qcom: Enable TX Equalization Can Guo
2026-03-23 9:25 ` Bean Huo
2026-03-23 16:50 ` [PATCH v4 00/12] scsi: ufs: Add TX Equalization support for UFS 5.0 Bart Van Assche
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260321031021.1722459-6-can.guo@oss.qualcomm.com \
--to=can.guo@oss.qualcomm.com \
--cc=James.Bottomley@HansenPartnership.com \
--cc=alim.akhtar@samsung.com \
--cc=avri.altman@wdc.com \
--cc=beanhuo@micron.com \
--cc=bvanassche@acm.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-scsi@vger.kernel.org \
--cc=mani@kernel.org \
--cc=martin.petersen@oracle.com \
--cc=peter.wang@mediatek.com \
--cc=quic_nguyenb@quicinc.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox