Drastic environmental changes, such as significant temperature shifts, can
impact link signal integrity. In such cases, refreshing TX Equalization is
necessary to compensate for these environmental changes.
Add a debugfs entry, 'tx_eq_ctrl', to allow userspace to manually trigger
the TX Equalization training (EQTR) procedure and apply the identified
optimal settings on the fly. These entries are created on a per-gear basis
for High Speed Gear 4 (HS-G4) and above, as TX EQTR is not supported for
lower gears.
The 'tx_eq_ctrl' entry currently accepts the 'refresh' command to initiate
the procedure. The interface is designed to be scalable to support
additional commands in the future.
Reading the 'tx_eq_ctrl' entry provides a usage hint to the user,
ensuring the interface is self-documenting.
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
|--tx_eq_ctrl
Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
---
drivers/ufs/core/ufs-debugfs.c | 61 ++++++++++++++++++++++++++
drivers/ufs/core/ufs-txeq.c | 78 +++++++++++++++++++++++++++++++++-
drivers/ufs/core/ufshcd-priv.h | 5 ++-
drivers/ufs/core/ufshcd.c | 7 +--
4 files changed, 143 insertions(+), 8 deletions(-)
diff --git a/drivers/ufs/core/ufs-debugfs.c b/drivers/ufs/core/ufs-debugfs.c
index 6f7562846f5b..b3bb2c850ad2 100644
--- a/drivers/ufs/core/ufs-debugfs.c
+++ b/drivers/ufs/core/ufs-debugfs.c
@@ -383,9 +383,70 @@ static const struct file_operations ufs_tx_eqtr_record_fops = {
.release = single_release,
};
+static ssize_t ufs_tx_eq_ctrl_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ u32 gear = (u32)(uintptr_t)file->f_inode->i_private;
+ struct ufs_hba *hba = hba_from_file(file);
+ char kbuf[32];
+ int ret;
+
+ if (count >= sizeof(kbuf))
+ return -EINVAL;
+
+ if (copy_from_user(kbuf, buf, count))
+ return -EFAULT;
+
+ kbuf[count] = '\0';
+
+ if (!ufshcd_is_tx_eq_supported(hba))
+ return -EOPNOTSUPP;
+
+ if (hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL ||
+ !hba->max_pwr_info.is_valid)
+ return -EBUSY;
+
+ if (!hba->ufs_device_wlun)
+ return -ENODEV;
+
+ if (sysfs_streq(kbuf, "refresh")) {
+ ret = ufs_debugfs_get_user_access(hba);
+ if (ret)
+ return ret;
+ ret = ufshcd_refresh_tx_eq(hba, gear);
+ ufs_debugfs_put_user_access(hba);
+ } else {
+ /* Unknown operation */
+ return -EINVAL;
+ }
+
+ return ret ? ret : count;
+}
+
+static int ufs_tx_eq_ctrl_show(struct seq_file *s, void *data)
+{
+ seq_puts(s, "write 'refresh' to refresh TX Equalization settings\n");
+ return 0;
+}
+
+static int ufs_tx_eq_ctrl_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ufs_tx_eq_ctrl_show, inode->i_private);
+}
+
+static const struct file_operations ufs_tx_eq_ctrl_fops = {
+ .owner = THIS_MODULE,
+ .open = ufs_tx_eq_ctrl_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .write = ufs_tx_eq_ctrl_write,
+ .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 },
+ { "tx_eq_ctrl", 0600, &ufs_tx_eq_ctrl_fops },
{ }
};
diff --git a/drivers/ufs/core/ufs-txeq.c b/drivers/ufs/core/ufs-txeq.c
index d77fa3f5e16d..c68b232b1598 100644
--- a/drivers/ufs/core/ufs-txeq.c
+++ b/drivers/ufs/core/ufs-txeq.c
@@ -1099,6 +1099,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: execute the TX EQTR procedure
*
* This function finds and sets the TX Equalization settings for the given
* target power mode.
@@ -1106,7 +1107,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;
@@ -1141,7 +1143,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);
@@ -1212,3 +1214,75 @@ void ufshcd_apply_valid_tx_eq_settings(struct ufs_hba *hba)
}
}
}
+
+/**
+ * ufshcd_refresh_tx_eq - Retrain TX Equalization and apply new settings
+ * @hba: per-adapter instance
+ * @gear: target High-Speed (HS) gear for retraining
+ *
+ * This function initiates a refresh of the TX Equalization settings for a
+ * specific HS gear. It scales the clocks to maximum frequency, negotiates the
+ * power mode with the device, retrains TX EQ and applies new TX EQ settings
+ * through a Power Mode change.
+ *
+ * Returns 0 on success, non-zero error code otherwise
+ */
+int ufshcd_refresh_tx_eq(struct ufs_hba *hba, u32 gear)
+{
+ struct ufs_pa_layer_attr new_pwr_info, final_params = {};
+ int ret;
+
+ if (!ufshcd_is_tx_eq_supported(hba) || !use_adaptive_txeq)
+ return -EOPNOTSUPP;
+
+ if (gear < adaptive_txeq_gear)
+ return -ERANGE;
+
+ ufshcd_hold(hba);
+
+ ret = ufshcd_pause_command_processing(hba, 1 * USEC_PER_SEC);
+ if (ret) {
+ ufshcd_release(hba);
+ return ret;
+ }
+
+ /* 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, "Negotiated Gear (%u) does not match target Gear (%u)\n",
+ final_params.gear_tx, gear);
+ goto out;
+ }
+
+ ret = ufshcd_config_tx_eq_settings(hba, &final_params, true);
+ if (ret) {
+ dev_err(hba->dev, "Failed to config TX Equalization for HS-G%u, Rate-%s: %d\n",
+ 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 2303d57bf874..fa5419e33d54 100644
--- a/drivers/ufs/core/ufshcd-priv.h
+++ b/drivers/ufs/core/ufshcd-priv.h
@@ -80,6 +80,7 @@ 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_pause_command_processing(struct ufs_hba *hba, u64 timeout_us);
void ufshcd_resume_command_processing(struct ufs_hba *hba);
+int ufshcd_scale_clks(struct ufs_hba *hba, unsigned long freq, bool scale_up);
/**
* enum ufs_descr_fmt - UFS string descriptor format
@@ -108,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_refresh_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 6fef24612be1..f2846ac49775 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();
@@ -4887,7 +4884,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_warn(hba->dev, "Failed to configure TX Equalization for HS-G%u, Rate-%s: %d\n",
final_params.gear_tx,
--
2.34.1
On 3/8/26 8:14 AM, Can Guo wrote:
> Drastic environmental changes, such as significant temperature shifts, can
> impact link signal integrity. In such cases, refreshing TX Equalization is
> necessary to compensate for these environmental changes.
>
> Add a debugfs entry, 'tx_eq_ctrl', to allow userspace to manually trigger
> the TX Equalization training (EQTR) procedure and apply the identified
> optimal settings on the fly. These entries are created on a per-gear basis
> for High Speed Gear 4 (HS-G4) and above, as TX EQTR is not supported for
> lower gears.
>
> The 'tx_eq_ctrl' entry currently accepts the 'refresh' command to initiate
> the procedure. The interface is designed to be scalable to support
> additional commands in the future.
>
> Reading the 'tx_eq_ctrl' entry provides a usage hint to the user,
> ensuring the interface is self-documenting.
>
> 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
> |--tx_eq_ctrl
>
> Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
> ---
> drivers/ufs/core/ufs-debugfs.c | 61 ++++++++++++++++++++++++++
> drivers/ufs/core/ufs-txeq.c | 78 +++++++++++++++++++++++++++++++++-
> drivers/ufs/core/ufshcd-priv.h | 5 ++-
> drivers/ufs/core/ufshcd.c | 7 +--
> 4 files changed, 143 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/ufs/core/ufs-debugfs.c b/drivers/ufs/core/ufs-debugfs.c
> index 6f7562846f5b..b3bb2c850ad2 100644
> --- a/drivers/ufs/core/ufs-debugfs.c
> +++ b/drivers/ufs/core/ufs-debugfs.c
> @@ -383,9 +383,70 @@ static const struct file_operations ufs_tx_eqtr_record_fops = {
> .release = single_release,
> };
>
> +static ssize_t ufs_tx_eq_ctrl_write(struct file *file, const char __user *buf,
> + size_t count, loff_t *ppos)
> +{
> + u32 gear = (u32)(uintptr_t)file->f_inode->i_private;
> + struct ufs_hba *hba = hba_from_file(file);
> + char kbuf[32];
> + int ret;
> +
> + if (count >= sizeof(kbuf))
> + return -EINVAL;
> +
> + if (copy_from_user(kbuf, buf, count))
> + return -EFAULT;
> +
> + kbuf[count] = '\0';
> +
> + if (!ufshcd_is_tx_eq_supported(hba))
> + return -EOPNOTSUPP;
> +
> + if (hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL ||
> + !hba->max_pwr_info.is_valid)
> + return -EBUSY;
> +
> + if (!hba->ufs_device_wlun)
> + return -ENODEV;
> +
> + if (sysfs_streq(kbuf, "refresh")) {
> + ret = ufs_debugfs_get_user_access(hba);
> + if (ret)
> + return ret;
> + ret = ufshcd_refresh_tx_eq(hba, gear);
> + ufs_debugfs_put_user_access(hba);
> + } else {
> + /* Unknown operation */
> + return -EINVAL;
> + }
> +
> + return ret ? ret : count;
> +}
> +
> +static int ufs_tx_eq_ctrl_show(struct seq_file *s, void *data)
> +{
> + seq_puts(s, "write 'refresh' to refresh TX Equalization settings\n");
> + return 0;
> +}
In the above two functions, since the standard uses the terminology
"TX equalization training", wouldn't it be more appropriate to use the
word "retrain" instead of "refresh"?
> +/**
> + * ufshcd_refresh_tx_eq - Retrain TX Equalization and apply new settings
Shouldn't the word "refresh" be changed into "retrain" to make the
function name consistent with the one-line description of this function?
Thanks,
Bart.
Hi Bart,
On 3/14/2026 6:30 AM, Bart Van Assche wrote:
> On 3/8/26 8:14 AM, Can Guo wrote:
>> Drastic environmental changes, such as significant temperature
>> shifts, can
>> impact link signal integrity. In such cases, refreshing TX
>> Equalization is
>> necessary to compensate for these environmental changes.
>>
>> Add a debugfs entry, 'tx_eq_ctrl', to allow userspace to manually
>> trigger
>> the TX Equalization training (EQTR) procedure and apply the identified
>> optimal settings on the fly. These entries are created on a per-gear
>> basis
>> for High Speed Gear 4 (HS-G4) and above, as TX EQTR is not supported for
>> lower gears.
>>
>> The 'tx_eq_ctrl' entry currently accepts the 'refresh' command to
>> initiate
>> the procedure. The interface is designed to be scalable to support
>> additional commands in the future.
>>
>> Reading the 'tx_eq_ctrl' entry provides a usage hint to the user,
>> ensuring the interface is self-documenting.
>>
>> 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
>> |--tx_eq_ctrl
>>
>> Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
>> ---
>> drivers/ufs/core/ufs-debugfs.c | 61 ++++++++++++++++++++++++++
>> drivers/ufs/core/ufs-txeq.c | 78 +++++++++++++++++++++++++++++++++-
>> drivers/ufs/core/ufshcd-priv.h | 5 ++-
>> drivers/ufs/core/ufshcd.c | 7 +--
>> 4 files changed, 143 insertions(+), 8 deletions(-)
>>
>> diff --git a/drivers/ufs/core/ufs-debugfs.c
>> b/drivers/ufs/core/ufs-debugfs.c
>> index 6f7562846f5b..b3bb2c850ad2 100644
>> --- a/drivers/ufs/core/ufs-debugfs.c
>> +++ b/drivers/ufs/core/ufs-debugfs.c
>> @@ -383,9 +383,70 @@ static const struct file_operations
>> ufs_tx_eqtr_record_fops = {
>> .release = single_release,
>> };
>> +static ssize_t ufs_tx_eq_ctrl_write(struct file *file, const char
>> __user *buf,
>> + size_t count, loff_t *ppos)
>> +{
>> + u32 gear = (u32)(uintptr_t)file->f_inode->i_private;
>> + struct ufs_hba *hba = hba_from_file(file);
>> + char kbuf[32];
>> + int ret;
>> +
>> + if (count >= sizeof(kbuf))
>> + return -EINVAL;
>> +
>> + if (copy_from_user(kbuf, buf, count))
>> + return -EFAULT;
>> +
>> + kbuf[count] = '\0';
>> +
>> + if (!ufshcd_is_tx_eq_supported(hba))
>> + return -EOPNOTSUPP;
>> +
>> + if (hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL ||
>> + !hba->max_pwr_info.is_valid)
>> + return -EBUSY;
>> +
>> + if (!hba->ufs_device_wlun)
>> + return -ENODEV;
>> +
>> + if (sysfs_streq(kbuf, "refresh")) {
>> + ret = ufs_debugfs_get_user_access(hba);
>> + if (ret)
>> + return ret;
>> + ret = ufshcd_refresh_tx_eq(hba, gear);
>> + ufs_debugfs_put_user_access(hba);
>> + } else {
>> + /* Unknown operation */
>> + return -EINVAL;
>> + }
>> +
>> + return ret ? ret : count;
>> +}
>> +
>> +static int ufs_tx_eq_ctrl_show(struct seq_file *s, void *data)
>> +{
>> + seq_puts(s, "write 'refresh' to refresh TX Equalization
>> settings\n");
>> + return 0;
>> +}
>
> In the above two functions, since the standard uses the terminology
> "TX equalization training", wouldn't it be more appropriate to use the
> word "retrain" instead of "refresh"?
I chose 'refresh' because the code conducts more than just retraining of
TX EQ,
the code also carries out a Power Mode change after that, and only by
doing a
Power Mode change, the new (optimal) TX EQ settings are really used by
both Host
and Device.
>
>> +/**
>> + * ufshcd_refresh_tx_eq - Retrain TX Equalization and apply new
>> settings
>
> Shouldn't the word "refresh" be changed into "retrain" to make the
> function name consistent with the one-line description of this function?
Here refresh = retrain TX EQ + a Power Mode change
Thanks,
Can Guo.
>
> Thanks,
>
> Bart.
On 3/14/26 3:45 AM, Can Guo wrote: > I chose 'refresh' because the code conducts more than just retraining of > TX EQ, > the code also carries out a Power Mode change after that, and only by > doing a > Power Mode change, the new (optimal) TX EQ settings are really used by > both Host > and Device. Thanks for the feedback. Not sure what others think but I still think that "retrain" makes it more clear what happens than "refresh". Bart.
On Mon, 2026-03-16 at 10:14 -0700, Bart Van Assche wrote: > On 3/14/26 3:45 AM, Can Guo wrote: > > I chose 'refresh' because the code conducts more than just > > retraining of > > TX EQ, > > the code also carries out a Power Mode change after that, and only > > by > > doing a > > Power Mode change, the new (optimal) TX EQ settings are really used > > by > > both Host > > and Device. > > Thanks for the feedback. Not sure what others think but I still think > that "retrain" makes it more clear what happens than "refresh". > > Bart. Hi Can and Bart, I agree with Bart’s opinion. Thanks Peter
On 3/17/2026 9:05 PM, Peter Wang (王信友) wrote: > > On Mon, 2026-03-16 at 10:14 -0700, Bart Van Assche wrote: > > On 3/14/26 3:45 AM, Can Guo wrote: > > > I chose 'refresh' because the code conducts more than just > > > retraining of > > > TX EQ, > > > the code also carries out a Power Mode change after that, and only > > > by > > > doing a > > > Power Mode change, the new (optimal) TX EQ settings are really used > > > by > > > both Host > > > and Device. > > > > Thanks for the feedback. Not sure what others think but I still think > > that "retrain" makes it more clear what happens than "refresh". > > > > Bart. > > Hi Can and Bart, > > I agree with Bart’s opinion. I will move back to 'retrain' Thanks for the suggestions. Can Guo. > > Thanks > Peter > > > > ************* MEDIATEK Confidentiality Notice ******************** > The information contained in this e-mail message (including any > attachments) may be confidential, proprietary, privileged, or otherwise > exempt from disclosure under applicable laws. It is intended to be > conveyed only to the designated recipient(s). Any use, dissemination, > distribution, printing, retaining or copying of this e-mail (including its > attachments) by unintended recipient(s) is strictly prohibited and may > be unlawful. If you are not an intended recipient of this e-mail, or believe > that you have received this e-mail in error, please notify the sender > immediately (by replying to this e-mail), delete any and all copies of > this e-mail (including any attachments) from your system, and do not > disclose the content of this e-mail to any other person. Thank you!
© 2016 - 2026 Red Hat, Inc.