.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;
+
+ 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;
+
+ kbuf[count] = '\0';
+
+ if (sysfs_streq(kbuf, "retrain")) {
+ ret = ufs_debugfs_get_user_access(hba);
+ if (ret)
+ return ret;
+ ret = ufshcd_retrain_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 'retrain' to retrain 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 },
{ }
};
struct ufshcd_tx_eq_params *params,
u32 gear)
{
+ struct ufshcd_tx_eqtr_record *rec = params->eqtr_record;
u32 adapt_eqtr;
int ret;
+ if (rec && rec->saved_adapt_eqtr) {
+ adapt_eqtr = rec->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;
return -EINVAL;
}
+ if (rec)
+ rec->saved_adapt_eqtr = (u16)adapt_eqtr;
+
+set_adapt_eqtr:
ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXADAPTLENGTH_EQTR), adapt_eqtr);
if (ret)
dev_err(hba->dev, "Failed to set adapt length for TX EQTR: %d\n", ret);
/**
* ufshcd_update_tx_eq_params - Update TX Equalization params
* @params: TX EQ parameters data structure
+ * @pwr_mode: target power mode containing gear and rate
* @eqtr_data: TX EQTR data structure
*
- * Update TX Equalization params using results from TX EQTR data.
+ * Update TX Equalization params using results from TX EQTR data. Check also
+ * the TX EQTR FOM value for each TX lane in the TX EQTR data. If a TX lane got
+ * a FOM value of 0, restore the TX Equalization settings from the last known
+ * valid TX Equalization params for that specific TX lane.
*/
static inline void
ufshcd_update_tx_eq_params(struct ufshcd_tx_eq_params *params,
+ struct ufs_pa_layer_attr *pwr_mode,
struct ufshcd_tx_eqtr_data *eqtr_data)
{
struct ufshcd_tx_eqtr_record *rec = params->eqtr_record;
+ if (params->is_valid) {
+ int lane;
+
+ for (lane = 0; lane < pwr_mode->lane_tx; lane++)
+ if (eqtr_data->host[lane].fom_val == 0)
+ eqtr_data->host[lane] = params->host[lane];
+
+ for (lane = 0; lane < pwr_mode->lane_rx; lane++)
+ if (eqtr_data->device[lane].fom_val == 0)
+ eqtr_data->device[lane] = params->device[lane];
+ }
+
memcpy(params->host, eqtr_data->host, sizeof(params->host));
memcpy(params->device, eqtr_data->device, sizeof(params->device));
dev_info(hba->dev, "TX EQTR procedure completed! Time elapsed: %llu ms\n",
ktime_to_ms(ktime_sub(ktime_get(), start)));
- ufshcd_update_tx_eq_params(params, eqtr_data);
+ ufshcd_update_tx_eq_params(params, pwr_mode, eqtr_data);
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
+ * @force_tx_eqtr: execute the TX EQTR procedure
*
* 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 ufs_pa_layer_attr *pwr_mode,
+ bool force_tx_eqtr)
{
struct ufshcd_tx_eq_params *params;
u32 gear, rate;
}
params = &hba->tx_eq_params[gear - 1];
- if (!params->is_valid) {
+ if (!params->is_valid || force_tx_eqtr) {
int ret;
ret = ufshcd_tx_eqtr(hba, params, pwr_mode);
}
}
}
+
+/**
+ * ufshcd_retrain_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
+ * by conducting a Power Mode change.
+ *
+ * Returns 0 on success, non-zero error code otherwise
+ */
+int ufshcd_retrain_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);
+ ret = -EINVAL;
+ 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_to_str(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_to_str(final_params.hs_rate), ret);
+
+out:
+ ufshcd_resume_command_processing(hba);
+ ufshcd_release(hba);
+
+ return ret;
+}
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);
*
* 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();
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,