xref: /linux/drivers/ufs/core/ufs-txeq.c (revision a85d6ff99411eb21536a750ad02205e8a97894c6)
103e5d38eSCan Guo // SPDX-License-Identifier: GPL-2.0-only
203e5d38eSCan Guo /*
303e5d38eSCan Guo  * Copyright (C) 2026 Qualcomm Technologies, Inc.
403e5d38eSCan Guo  *
503e5d38eSCan Guo  * Author:
603e5d38eSCan Guo  *	Can Guo <can.guo@oss.qualcomm.com>
703e5d38eSCan Guo  */
803e5d38eSCan Guo 
903e5d38eSCan Guo #include <linux/bitops.h>
1003e5d38eSCan Guo #include <linux/delay.h>
1103e5d38eSCan Guo #include <linux/errno.h>
1203e5d38eSCan Guo #include <linux/kernel.h>
1303e5d38eSCan Guo #include <ufs/ufshcd.h>
1403e5d38eSCan Guo #include <ufs/unipro.h>
1503e5d38eSCan Guo #include "ufshcd-priv.h"
1603e5d38eSCan Guo 
1703e5d38eSCan Guo static bool use_adaptive_txeq;
1803e5d38eSCan Guo module_param(use_adaptive_txeq, bool, 0644);
1903e5d38eSCan Guo MODULE_PARM_DESC(use_adaptive_txeq, "Find and apply optimal TX Equalization settings before changing Power Mode (default: false)");
2003e5d38eSCan Guo 
2103e5d38eSCan Guo static int txeq_gear_set(const char *val, const struct kernel_param *kp)
2203e5d38eSCan Guo {
2303e5d38eSCan Guo 	return param_set_uint_minmax(val, kp, UFS_HS_G1, UFS_HS_GEAR_MAX);
2403e5d38eSCan Guo }
2503e5d38eSCan Guo 
2603e5d38eSCan Guo static const struct kernel_param_ops txeq_gear_ops = {
2703e5d38eSCan Guo 	.set = txeq_gear_set,
2803e5d38eSCan Guo 	.get = param_get_uint,
2903e5d38eSCan Guo };
3003e5d38eSCan Guo 
3103e5d38eSCan Guo static unsigned int adaptive_txeq_gear = UFS_HS_G6;
3203e5d38eSCan Guo module_param_cb(adaptive_txeq_gear, &txeq_gear_ops, &adaptive_txeq_gear, 0644);
3303e5d38eSCan Guo MODULE_PARM_DESC(adaptive_txeq_gear, "For HS-Gear[n] and above, adaptive txeq shall be used");
3403e5d38eSCan Guo 
3503e5d38eSCan Guo static bool use_txeq_presets;
3603e5d38eSCan Guo module_param(use_txeq_presets, bool, 0644);
3703e5d38eSCan Guo MODULE_PARM_DESC(use_txeq_presets, "Use only the 8 TX Equalization Presets (pre-defined Pre-Shoot & De-Emphasis combinations) for TX EQTR (default: false)");
3803e5d38eSCan Guo 
3903e5d38eSCan Guo static bool txeq_presets_selected[UFS_TX_EQ_PRESET_MAX] = {[0 ... (UFS_TX_EQ_PRESET_MAX - 1)] = 1};
4003e5d38eSCan Guo module_param_array(txeq_presets_selected, bool, NULL, 0644);
4103e5d38eSCan Guo MODULE_PARM_DESC(txeq_presets_selected, "Use only the selected Presets out of the 8 TX Equalization Presets for TX EQTR");
4203e5d38eSCan Guo 
4303e5d38eSCan Guo /*
4403e5d38eSCan Guo  * ufs_tx_eq_preset - Table of minimum required list of presets.
4503e5d38eSCan Guo  *
4603e5d38eSCan Guo  * A HS-G6 capable M-TX shall support the presets defined in M-PHY v6.0 spec.
4703e5d38eSCan Guo  * Preset	Pre-Shoot(dB)	De-Emphasis(dB)
4803e5d38eSCan Guo  * P0		0.0		0.0
4903e5d38eSCan Guo  * P1		0.0		0.8
5003e5d38eSCan Guo  * P2		0.0		1.6
5103e5d38eSCan Guo  * P3		0.8		0.0
5203e5d38eSCan Guo  * P4		1.6		0.0
5303e5d38eSCan Guo  * P5		0.8		0.8
5403e5d38eSCan Guo  * P6		0.8		1.6
5503e5d38eSCan Guo  * P7		1.6		0.8
5603e5d38eSCan Guo  */
5703e5d38eSCan Guo static const struct __ufs_tx_eq_preset {
5803e5d38eSCan Guo 	u8 preshoot;
5903e5d38eSCan Guo 	u8 deemphasis;
6003e5d38eSCan Guo } ufs_tx_eq_preset[UFS_TX_EQ_PRESET_MAX] = {
6103e5d38eSCan Guo 	[UFS_TX_EQ_PRESET_P0] = {UFS_TX_HS_PRESHOOT_DB_0P0, UFS_TX_HS_DEEMPHASIS_DB_0P0},
6203e5d38eSCan Guo 	[UFS_TX_EQ_PRESET_P1] = {UFS_TX_HS_PRESHOOT_DB_0P0, UFS_TX_HS_DEEMPHASIS_DB_0P8},
6303e5d38eSCan Guo 	[UFS_TX_EQ_PRESET_P2] = {UFS_TX_HS_PRESHOOT_DB_0P0, UFS_TX_HS_DEEMPHASIS_DB_1P6},
6403e5d38eSCan Guo 	[UFS_TX_EQ_PRESET_P3] = {UFS_TX_HS_PRESHOOT_DB_0P8, UFS_TX_HS_DEEMPHASIS_DB_0P0},
6503e5d38eSCan Guo 	[UFS_TX_EQ_PRESET_P4] = {UFS_TX_HS_PRESHOOT_DB_1P6, UFS_TX_HS_DEEMPHASIS_DB_0P0},
6603e5d38eSCan Guo 	[UFS_TX_EQ_PRESET_P5] = {UFS_TX_HS_PRESHOOT_DB_0P8, UFS_TX_HS_DEEMPHASIS_DB_0P8},
6703e5d38eSCan Guo 	[UFS_TX_EQ_PRESET_P6] = {UFS_TX_HS_PRESHOOT_DB_0P8, UFS_TX_HS_DEEMPHASIS_DB_1P6},
6803e5d38eSCan Guo 	[UFS_TX_EQ_PRESET_P7] = {UFS_TX_HS_PRESHOOT_DB_1P6, UFS_TX_HS_DEEMPHASIS_DB_0P8},
6903e5d38eSCan Guo };
7003e5d38eSCan Guo 
7103e5d38eSCan Guo /*
7203e5d38eSCan Guo  * pa_peer_rx_adapt_initial - Table of UniPro PA_PeerRxHSGnAdaptInitial
7303e5d38eSCan Guo  * attribute IDs for High Speed (HS) Gears.
7403e5d38eSCan Guo  *
7503e5d38eSCan Guo  * This table maps HS Gears to their respective UniPro PA_PeerRxHSGnAdaptInitial
7603e5d38eSCan Guo  * attribute IDs. Entries for Gears 1-3 are 0 (unsupported).
7703e5d38eSCan Guo  */
7803e5d38eSCan Guo static const u32 pa_peer_rx_adapt_initial[UFS_HS_GEAR_MAX] = {
7903e5d38eSCan Guo 	0,
8003e5d38eSCan Guo 	0,
8103e5d38eSCan Guo 	0,
8203e5d38eSCan Guo 	PA_PEERRXHSG4ADAPTINITIAL,
8303e5d38eSCan Guo 	PA_PEERRXHSG5ADAPTINITIAL,
8403e5d38eSCan Guo 	PA_PEERRXHSG6ADAPTINITIALL0L3
8503e5d38eSCan Guo };
8603e5d38eSCan Guo 
8703e5d38eSCan Guo /*
8803e5d38eSCan Guo  * rx_adapt_initial_cap - Table of M-PHY RX_HS_Gn_ADAPT_INITIAL_Capability
8903e5d38eSCan Guo  * attribute IDs for High Speed (HS) Gears.
9003e5d38eSCan Guo  *
9103e5d38eSCan Guo  * This table maps HS Gears to their respective M-PHY
9203e5d38eSCan Guo  * RX_HS_Gn_ADAPT_INITIAL_Capability attribute IDs. Entries for Gears 1-3 are 0
9303e5d38eSCan Guo  * (unsupported).
9403e5d38eSCan Guo  */
9503e5d38eSCan Guo static const u32 rx_adapt_initial_cap[UFS_HS_GEAR_MAX] = {
9603e5d38eSCan Guo 	0,
9703e5d38eSCan Guo 	0,
9803e5d38eSCan Guo 	0,
9903e5d38eSCan Guo 	RX_HS_G4_ADAPT_INITIAL_CAP,
10003e5d38eSCan Guo 	RX_HS_G5_ADAPT_INITIAL_CAP,
10103e5d38eSCan Guo 	RX_HS_G6_ADAPT_INITIAL_CAP
10203e5d38eSCan Guo };
10303e5d38eSCan Guo 
10403e5d38eSCan Guo /*
10503e5d38eSCan Guo  * pa_tx_eq_setting - Table of UniPro PA_TxEQGnSetting attribute IDs for High
10603e5d38eSCan Guo  * Speed (HS) Gears.
10703e5d38eSCan Guo  *
10803e5d38eSCan Guo  * This table maps HS Gears to their respective UniPro PA_TxEQGnSetting
10903e5d38eSCan Guo  * attribute IDs.
11003e5d38eSCan Guo  */
11103e5d38eSCan Guo static const u32 pa_tx_eq_setting[UFS_HS_GEAR_MAX] = {
11203e5d38eSCan Guo 	PA_TXEQG1SETTING,
11303e5d38eSCan Guo 	PA_TXEQG2SETTING,
11403e5d38eSCan Guo 	PA_TXEQG3SETTING,
11503e5d38eSCan Guo 	PA_TXEQG4SETTING,
11603e5d38eSCan Guo 	PA_TXEQG5SETTING,
11703e5d38eSCan Guo 	PA_TXEQG6SETTING
11803e5d38eSCan Guo };
11903e5d38eSCan Guo 
12003e5d38eSCan Guo /**
12103e5d38eSCan Guo  * ufshcd_configure_precoding - Configure Pre-Coding for all active lanes
12203e5d38eSCan Guo  * @hba: per adapter instance
12303e5d38eSCan Guo  * @params: TX EQ parameters data structure
12403e5d38eSCan Guo  *
12503e5d38eSCan Guo  * Bit[7] in RX_FOM indicates that the receiver needs to enable Pre-Coding when
12603e5d38eSCan Guo  * set. Pre-Coding must be enabled on both the transmitter and receiver to
12703e5d38eSCan Guo  * ensure proper operation.
12803e5d38eSCan Guo  *
12903e5d38eSCan Guo  * Returns 0 on success, non-zero error code otherwise
13003e5d38eSCan Guo  */
13103e5d38eSCan Guo static int ufshcd_configure_precoding(struct ufs_hba *hba,
13203e5d38eSCan Guo 				      struct ufshcd_tx_eq_params *params)
13303e5d38eSCan Guo {
13403e5d38eSCan Guo 	struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info;
13503e5d38eSCan Guo 	u32 local_precode_en = 0;
13603e5d38eSCan Guo 	u32 peer_precode_en = 0;
13703e5d38eSCan Guo 	int lane, ret;
13803e5d38eSCan Guo 
13903e5d38eSCan Guo 	/* Enable Pre-Coding for Host's TX & Device's RX pair */
14003e5d38eSCan Guo 	for (lane = 0; lane < pwr_info->lane_tx; lane++) {
14103e5d38eSCan Guo 		if (params->host[lane].precode_en) {
14203e5d38eSCan Guo 			local_precode_en |= PRECODEEN_TX_BIT(lane);
14303e5d38eSCan Guo 			peer_precode_en |= PRECODEEN_RX_BIT(lane);
14403e5d38eSCan Guo 		}
14503e5d38eSCan Guo 	}
14603e5d38eSCan Guo 
14703e5d38eSCan Guo 	/* Enable Pre-Coding for Device's TX & Host's RX pair */
14803e5d38eSCan Guo 	for (lane = 0; lane < pwr_info->lane_rx; lane++) {
14903e5d38eSCan Guo 		if (params->device[lane].precode_en) {
15003e5d38eSCan Guo 			peer_precode_en |= PRECODEEN_TX_BIT(lane);
15103e5d38eSCan Guo 			local_precode_en |= PRECODEEN_RX_BIT(lane);
15203e5d38eSCan Guo 		}
15303e5d38eSCan Guo 	}
15403e5d38eSCan Guo 
15503e5d38eSCan Guo 	if (!local_precode_en && !peer_precode_en) {
15603e5d38eSCan Guo 		dev_dbg(hba->dev, "Pre-Coding is not required for Host and Device\n");
15703e5d38eSCan Guo 		return 0;
15803e5d38eSCan Guo 	}
15903e5d38eSCan Guo 
16003e5d38eSCan Guo 	/* Set local PA_PreCodeEn */
16103e5d38eSCan Guo 	ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PRECODEEN), local_precode_en);
16203e5d38eSCan Guo 	if (ret) {
16303e5d38eSCan Guo 		dev_err(hba->dev, "Failed to set local PA_PreCodeEn: %d\n", ret);
16403e5d38eSCan Guo 		return ret;
16503e5d38eSCan Guo 	}
16603e5d38eSCan Guo 
16703e5d38eSCan Guo 	/* Set peer PA_PreCodeEn */
16803e5d38eSCan Guo 	ret = ufshcd_dme_peer_set(hba, UIC_ARG_MIB(PA_PRECODEEN), peer_precode_en);
16903e5d38eSCan Guo 	if (ret) {
17003e5d38eSCan Guo 		dev_err(hba->dev, "Failed to set peer PA_PreCodeEn: %d\n", ret);
17103e5d38eSCan Guo 		return ret;
17203e5d38eSCan Guo 	}
17303e5d38eSCan Guo 
17403e5d38eSCan Guo 	dev_dbg(hba->dev, "Local PA_PreCodeEn: 0x%02x, Peer PA_PreCodeEn: 0x%02x\n",
17503e5d38eSCan Guo 		local_precode_en, peer_precode_en);
17603e5d38eSCan Guo 
17703e5d38eSCan Guo 	return 0;
17803e5d38eSCan Guo }
17903e5d38eSCan Guo 
18003e5d38eSCan Guo void ufshcd_print_tx_eq_params(struct ufs_hba *hba)
18103e5d38eSCan Guo {
18203e5d38eSCan Guo 	struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info;
18303e5d38eSCan Guo 	struct ufshcd_tx_eq_params *params;
18403e5d38eSCan Guo 	u32 gear = hba->pwr_info.gear_tx;
18503e5d38eSCan Guo 	int lane;
18603e5d38eSCan Guo 
18703e5d38eSCan Guo 	if (!ufshcd_is_tx_eq_supported(hba))
18803e5d38eSCan Guo 		return;
18903e5d38eSCan Guo 
19003e5d38eSCan Guo 	if (gear < UFS_HS_G1 || gear > UFS_HS_GEAR_MAX)
19103e5d38eSCan Guo 		return;
19203e5d38eSCan Guo 
19303e5d38eSCan Guo 	params = &hba->tx_eq_params[gear - 1];
19403e5d38eSCan Guo 	if (!params->is_valid || !params->is_applied)
19503e5d38eSCan Guo 		return;
19603e5d38eSCan Guo 
19703e5d38eSCan Guo 	for (lane = 0; lane < pwr_info->lane_tx; lane++)
19803e5d38eSCan Guo 		dev_dbg(hba->dev, "Host TX Lane %d: PreShoot %u, DeEmphasis %u, FOM %u, PreCodeEn %d\n",
19903e5d38eSCan Guo 			lane, params->host[lane].preshoot,
20003e5d38eSCan Guo 			params->host[lane].deemphasis,
20103e5d38eSCan Guo 			params->host[lane].fom_val,
20203e5d38eSCan Guo 			params->host[lane].precode_en);
20303e5d38eSCan Guo 
20403e5d38eSCan Guo 	for (lane = 0; lane < pwr_info->lane_rx; lane++)
20503e5d38eSCan Guo 		dev_dbg(hba->dev, "Device TX Lane %d: PreShoot %u, DeEmphasis %u, FOM %u, PreCodeEn %d\n",
20603e5d38eSCan Guo 			lane, params->device[lane].preshoot,
20703e5d38eSCan Guo 			params->device[lane].deemphasis,
20803e5d38eSCan Guo 			params->device[lane].fom_val,
20903e5d38eSCan Guo 			params->device[lane].precode_en);
21003e5d38eSCan Guo }
21103e5d38eSCan Guo 
21203e5d38eSCan Guo static inline u32
21303e5d38eSCan Guo ufshcd_compose_tx_eq_setting(struct ufshcd_tx_eq_settings *settings,
21403e5d38eSCan Guo 			     int num_lanes)
21503e5d38eSCan Guo {
21603e5d38eSCan Guo 	u32 setting = 0;
21703e5d38eSCan Guo 	int lane;
21803e5d38eSCan Guo 
21903e5d38eSCan Guo 	for (lane = 0; lane < num_lanes; lane++, settings++) {
22003e5d38eSCan Guo 		setting |= TX_HS_PRESHOOT_BITS(lane, settings->preshoot);
22103e5d38eSCan Guo 		setting |= TX_HS_DEEMPHASIS_BITS(lane, settings->deemphasis);
22203e5d38eSCan Guo 	}
22303e5d38eSCan Guo 
22403e5d38eSCan Guo 	return setting;
22503e5d38eSCan Guo }
22603e5d38eSCan Guo 
22703e5d38eSCan Guo /**
22803e5d38eSCan Guo  * ufshcd_apply_tx_eq_settings - Apply TX Equalization settings for target gear
22903e5d38eSCan Guo  * @hba: per adapter instance
23003e5d38eSCan Guo  * @params: TX EQ parameters data structure
23103e5d38eSCan Guo  * @gear: target gear
23203e5d38eSCan Guo  *
23303e5d38eSCan Guo  * Returns 0 on success, negative error code otherwise
23403e5d38eSCan Guo  */
235*26605db7SCan Guo int ufshcd_apply_tx_eq_settings(struct ufs_hba *hba,
236*26605db7SCan Guo 				struct ufshcd_tx_eq_params *params, u32 gear)
23703e5d38eSCan Guo {
23803e5d38eSCan Guo 	struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info;
23903e5d38eSCan Guo 	u32 setting;
24003e5d38eSCan Guo 	int ret;
24103e5d38eSCan Guo 
24203e5d38eSCan Guo 	/* Compose settings for Host's TX Lanes */
24303e5d38eSCan Guo 	setting = ufshcd_compose_tx_eq_setting(params->host, pwr_info->lane_tx);
24403e5d38eSCan Guo 	ret = ufshcd_dme_set(hba, UIC_ARG_MIB(pa_tx_eq_setting[gear - 1]), setting);
24503e5d38eSCan Guo 	if (ret)
24603e5d38eSCan Guo 		return ret;
24703e5d38eSCan Guo 
24803e5d38eSCan Guo 	/* Compose settings for Device's TX Lanes */
24903e5d38eSCan Guo 	setting = ufshcd_compose_tx_eq_setting(params->device, pwr_info->lane_rx);
25003e5d38eSCan Guo 	ret = ufshcd_dme_peer_set(hba, UIC_ARG_MIB(pa_tx_eq_setting[gear - 1]), setting);
25103e5d38eSCan Guo 	if (ret)
25203e5d38eSCan Guo 		return ret;
25303e5d38eSCan Guo 
25403e5d38eSCan Guo 	/* Configure Pre-Coding */
25503e5d38eSCan Guo 	if (gear >= UFS_HS_G6) {
25603e5d38eSCan Guo 		ret = ufshcd_configure_precoding(hba, params);
25703e5d38eSCan Guo 		if (ret) {
25803e5d38eSCan Guo 			dev_err(hba->dev, "Failed to configure pre-coding: %d\n", ret);
25903e5d38eSCan Guo 			return ret;
26003e5d38eSCan Guo 		}
26103e5d38eSCan Guo 	}
26203e5d38eSCan Guo 
26303e5d38eSCan Guo 	return 0;
26403e5d38eSCan Guo }
265*26605db7SCan Guo EXPORT_SYMBOL_GPL(ufshcd_apply_tx_eq_settings);
26603e5d38eSCan Guo 
26703e5d38eSCan Guo /**
26803e5d38eSCan Guo  * ufshcd_evaluate_tx_eqtr_fom - Evaluate TX EQTR FOM results
26903e5d38eSCan Guo  * @hba: per adapter instance
27003e5d38eSCan Guo  * @pwr_mode: target power mode containing gear and rate information
27103e5d38eSCan Guo  * @eqtr_data: TX EQTR data structure
27203e5d38eSCan Guo  * @h_iter: host TX EQTR iterator data structure
27303e5d38eSCan Guo  * @d_iter: device TX EQTR iterator data structure
27403e5d38eSCan Guo  *
27503e5d38eSCan Guo  * Evaluate TX EQTR FOM results, update host and device TX EQTR data accordingy
27603e5d38eSCan Guo  * if FOM have been improved compared to previous iteration, and record TX EQTR
27703e5d38eSCan Guo  * FOM results.
27803e5d38eSCan Guo  */
27903e5d38eSCan Guo static void ufshcd_evaluate_tx_eqtr_fom(struct ufs_hba *hba,
28003e5d38eSCan Guo 					struct ufs_pa_layer_attr *pwr_mode,
28103e5d38eSCan Guo 					struct ufshcd_tx_eqtr_data *eqtr_data,
28203e5d38eSCan Guo 					struct tx_eqtr_iter *h_iter,
28303e5d38eSCan Guo 					struct tx_eqtr_iter *d_iter)
28403e5d38eSCan Guo {
28503e5d38eSCan Guo 	u8 preshoot, deemphasis, fom_value;
28603e5d38eSCan Guo 	bool precode_en;
28703e5d38eSCan Guo 	int lane;
28803e5d38eSCan Guo 
28903e5d38eSCan Guo 	for (lane = 0; h_iter->is_updated && lane < pwr_mode->lane_tx; lane++) {
29003e5d38eSCan Guo 		preshoot = h_iter->preshoot;
29103e5d38eSCan Guo 		deemphasis = h_iter->deemphasis;
29203e5d38eSCan Guo 		fom_value = h_iter->fom[lane] & RX_FOM_VALUE_MASK;
29303e5d38eSCan Guo 		precode_en = h_iter->fom[lane] & RX_FOM_PRECODING_EN_BIT;
29403e5d38eSCan Guo 
29503e5d38eSCan Guo 		/* Record host TX EQTR FOM */
29603e5d38eSCan Guo 		eqtr_data->host_fom[lane][preshoot][deemphasis] = h_iter->fom[lane];
29703e5d38eSCan Guo 
29803e5d38eSCan Guo 		/* Check if FOM has been improved for host's TX Lanes */
29903e5d38eSCan Guo 		if (fom_value > eqtr_data->host[lane].fom_val) {
30003e5d38eSCan Guo 			eqtr_data->host[lane].preshoot = preshoot;
30103e5d38eSCan Guo 			eqtr_data->host[lane].deemphasis = deemphasis;
30203e5d38eSCan Guo 			eqtr_data->host[lane].fom_val = fom_value;
30303e5d38eSCan Guo 			eqtr_data->host[lane].precode_en = precode_en;
30403e5d38eSCan Guo 		}
30503e5d38eSCan Guo 
30603e5d38eSCan Guo 		dev_dbg(hba->dev, "TX EQTR: Host TX Lane %d: PreShoot %u, DeEmphasis %u, FOM value %u, PreCodeEn %d\n",
30703e5d38eSCan Guo 			lane, preshoot, deemphasis, fom_value, precode_en);
30803e5d38eSCan Guo 	}
30903e5d38eSCan Guo 
31003e5d38eSCan Guo 	for (lane = 0; d_iter->is_updated && lane < pwr_mode->lane_rx; lane++) {
31103e5d38eSCan Guo 		preshoot = d_iter->preshoot;
31203e5d38eSCan Guo 		deemphasis = d_iter->deemphasis;
31303e5d38eSCan Guo 		fom_value = d_iter->fom[lane] & RX_FOM_VALUE_MASK;
31403e5d38eSCan Guo 		precode_en = d_iter->fom[lane] & RX_FOM_PRECODING_EN_BIT;
31503e5d38eSCan Guo 
31603e5d38eSCan Guo 		/* Record device TX EQTR FOM */
31703e5d38eSCan Guo 		eqtr_data->device_fom[lane][preshoot][deemphasis] = d_iter->fom[lane];
31803e5d38eSCan Guo 
31903e5d38eSCan Guo 		/* Check if FOM has been improved for Device's TX Lanes */
32003e5d38eSCan Guo 		if (fom_value > eqtr_data->device[lane].fom_val) {
32103e5d38eSCan Guo 			eqtr_data->device[lane].preshoot = preshoot;
32203e5d38eSCan Guo 			eqtr_data->device[lane].deemphasis = deemphasis;
32303e5d38eSCan Guo 			eqtr_data->device[lane].fom_val = fom_value;
32403e5d38eSCan Guo 			eqtr_data->device[lane].precode_en = precode_en;
32503e5d38eSCan Guo 		}
32603e5d38eSCan Guo 
32703e5d38eSCan Guo 		dev_dbg(hba->dev, "TX EQTR: Device TX Lane %d: PreShoot %u, DeEmphasis %u, FOM value %u, PreCodeEn %d\n",
32803e5d38eSCan Guo 			lane, preshoot, deemphasis, fom_value, precode_en);
32903e5d38eSCan Guo 	}
33003e5d38eSCan Guo }
33103e5d38eSCan Guo 
33203e5d38eSCan Guo /**
33303e5d38eSCan Guo  * ufshcd_get_rx_fom - Get Figure of Merit (FOM) for both sides
33403e5d38eSCan Guo  * @hba: per adapter instance
33503e5d38eSCan Guo  * @pwr_mode: target power mode containing gear and rate information
33603e5d38eSCan Guo  * @h_iter: host TX EQTR iterator data structure
33703e5d38eSCan Guo  * @d_iter: device TX EQTR iterator data structure
33803e5d38eSCan Guo  *
33903e5d38eSCan Guo  * Returns 0 on success, negative error code otherwise
34003e5d38eSCan Guo  */
34103e5d38eSCan Guo static int ufshcd_get_rx_fom(struct ufs_hba *hba,
34203e5d38eSCan Guo 			     struct ufs_pa_layer_attr *pwr_mode,
34303e5d38eSCan Guo 			     struct tx_eqtr_iter *h_iter,
34403e5d38eSCan Guo 			     struct tx_eqtr_iter *d_iter)
34503e5d38eSCan Guo {
34603e5d38eSCan Guo 	int lane, ret;
34703e5d38eSCan Guo 	u32 fom;
34803e5d38eSCan Guo 
34903e5d38eSCan Guo 	/* Get FOM of host's TX lanes from device's RX_FOM. */
35003e5d38eSCan Guo 	for (lane = 0; lane < pwr_mode->lane_tx; lane++) {
35103e5d38eSCan Guo 		ret = ufshcd_dme_peer_get(hba, UIC_ARG_MIB_SEL(RX_FOM,
35203e5d38eSCan Guo 					  UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
35303e5d38eSCan Guo 					  &fom);
35403e5d38eSCan Guo 		if (ret)
35503e5d38eSCan Guo 			return ret;
35603e5d38eSCan Guo 
35703e5d38eSCan Guo 		h_iter->fom[lane] = (u8)fom;
35803e5d38eSCan Guo 	}
35903e5d38eSCan Guo 
36003e5d38eSCan Guo 	/* Get FOM of device's TX lanes from host's RX_FOM. */
36103e5d38eSCan Guo 	for (lane = 0; lane < pwr_mode->lane_rx; lane++) {
36203e5d38eSCan Guo 		ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(RX_FOM,
36303e5d38eSCan Guo 				     UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
36403e5d38eSCan Guo 				     &fom);
36503e5d38eSCan Guo 		if (ret)
36603e5d38eSCan Guo 			return ret;
36703e5d38eSCan Guo 
36803e5d38eSCan Guo 		d_iter->fom[lane] = (u8)fom;
36903e5d38eSCan Guo 	}
37003e5d38eSCan Guo 
37103e5d38eSCan Guo 	ret = ufshcd_vops_get_rx_fom(hba, pwr_mode, h_iter, d_iter);
37203e5d38eSCan Guo 	if (ret)
37303e5d38eSCan Guo 		dev_err(hba->dev, "Failed to get FOM via vops: %d\n", ret);
37403e5d38eSCan Guo 
37503e5d38eSCan Guo 	return ret;
37603e5d38eSCan Guo }
37703e5d38eSCan Guo 
37810c40143SCan Guo bool ufshcd_is_txeq_presets_used(struct ufs_hba *hba)
37910c40143SCan Guo {
38010c40143SCan Guo 	return use_txeq_presets;
38110c40143SCan Guo }
38210c40143SCan Guo 
38310c40143SCan Guo bool ufshcd_is_txeq_preset_selected(u8 preshoot, u8 deemphasis)
38403e5d38eSCan Guo {
38503e5d38eSCan Guo 	int i;
38603e5d38eSCan Guo 
38703e5d38eSCan Guo 	for (i = 0; i < UFS_TX_EQ_PRESET_MAX; i++) {
38803e5d38eSCan Guo 		if (!txeq_presets_selected[i])
38903e5d38eSCan Guo 			continue;
39003e5d38eSCan Guo 
39103e5d38eSCan Guo 		if (preshoot == ufs_tx_eq_preset[i].preshoot &&
39203e5d38eSCan Guo 		    deemphasis == ufs_tx_eq_preset[i].deemphasis)
39303e5d38eSCan Guo 			return true;
39403e5d38eSCan Guo 	}
39503e5d38eSCan Guo 
39603e5d38eSCan Guo 	return false;
39703e5d38eSCan Guo }
39803e5d38eSCan Guo 
39903e5d38eSCan Guo /**
40003e5d38eSCan Guo  * tx_eqtr_iter_try_update - Try to update a TX EQTR iterator
40103e5d38eSCan Guo  * @iter: TX EQTR iterator data structure
40203e5d38eSCan Guo  * @preshoot: PreShoot value
40303e5d38eSCan Guo  * @deemphasis: DeEmphasis value
40403e5d38eSCan Guo  *
40503e5d38eSCan Guo  * This function validates whether the provided PreShoot and DeEmphasis
40603e5d38eSCan Guo  * combination can be used or not. If yes, it updates the TX EQTR iterator with
40703e5d38eSCan Guo  * the provided PreShoot and DeEmphasis, it also sets the is_updated flag
40803e5d38eSCan Guo  * to indicate the iterator has been updated.
40903e5d38eSCan Guo  */
41003e5d38eSCan Guo static void tx_eqtr_iter_try_update(struct tx_eqtr_iter *iter,
41103e5d38eSCan Guo 				    u8 preshoot, u8 deemphasis)
41203e5d38eSCan Guo {
41303e5d38eSCan Guo 	if (!test_bit(preshoot, &iter->preshoot_bitmap) ||
41403e5d38eSCan Guo 	    !test_bit(deemphasis, &iter->deemphasis_bitmap) ||
41503e5d38eSCan Guo 	    (use_txeq_presets && !ufshcd_is_txeq_preset_selected(preshoot, deemphasis))) {
41603e5d38eSCan Guo 		iter->is_updated = false;
41703e5d38eSCan Guo 		return;
41803e5d38eSCan Guo 	}
41903e5d38eSCan Guo 
42003e5d38eSCan Guo 	iter->preshoot = preshoot;
42103e5d38eSCan Guo 	iter->deemphasis = deemphasis;
42203e5d38eSCan Guo 	iter->is_updated = true;
42303e5d38eSCan Guo }
42403e5d38eSCan Guo 
42503e5d38eSCan Guo /**
42603e5d38eSCan Guo  * tx_eqtr_iter_update() - Update host and deviceTX EQTR iterators
42703e5d38eSCan Guo  * @preshoot: PreShoot value
42803e5d38eSCan Guo  * @deemphasis: DeEmphasis value
42903e5d38eSCan Guo  * @h_iter: Host TX EQTR iterator data structure
43003e5d38eSCan Guo  * @d_iter: Device TX EQTR iterator data structure
43103e5d38eSCan Guo  *
43203e5d38eSCan Guo  * Updates host and device TX Equalization training iterators with the
43303e5d38eSCan Guo  * provided PreShoot and DeEmphasis.
43403e5d38eSCan Guo  *
43503e5d38eSCan Guo  * Return: true if host and/or device TX Equalization training iterator has
43603e5d38eSCan Guo  * been updated to the provided PreShoot and DeEmphasis, false otherwise.
43703e5d38eSCan Guo  */
43803e5d38eSCan Guo static bool tx_eqtr_iter_update(u8 preshoot, u8 deemphasis,
43903e5d38eSCan Guo 				struct tx_eqtr_iter *h_iter,
44003e5d38eSCan Guo 				struct tx_eqtr_iter *d_iter)
44103e5d38eSCan Guo {
44203e5d38eSCan Guo 	tx_eqtr_iter_try_update(h_iter, preshoot, deemphasis);
44303e5d38eSCan Guo 	tx_eqtr_iter_try_update(d_iter, preshoot, deemphasis);
44403e5d38eSCan Guo 
44503e5d38eSCan Guo 	return h_iter->is_updated || d_iter->is_updated;
44603e5d38eSCan Guo }
44703e5d38eSCan Guo 
44803e5d38eSCan Guo /**
44903e5d38eSCan Guo  * ufshcd_tx_eqtr_iter_init - Initialize host and device TX EQTR iterators
45003e5d38eSCan Guo  * @hba: per adapter instance
45103e5d38eSCan Guo  * @h_iter: host TX EQTR iterator data structure
45203e5d38eSCan Guo  * @d_iter: device TX EQTR iterator data structure
45303e5d38eSCan Guo  *
45403e5d38eSCan Guo  * This function initializes the TX EQTR iterator structures for both host and
45503e5d38eSCan Guo  * device by reading their TX equalization capabilities. The capabilities are
45603e5d38eSCan Guo  * cached in the hba structure to avoid redundant DME operations in subsequent
45703e5d38eSCan Guo  * calls. In the TX EQTR procedure, the iterator structures are updated by
45803e5d38eSCan Guo  * tx_eqtr_iter_update() to systematically iterate through supported TX
45903e5d38eSCan Guo  * Equalization setting combinations.
46003e5d38eSCan Guo  *
46103e5d38eSCan Guo  * Returns 0 on success, negative error code otherwise
46203e5d38eSCan Guo  */
46303e5d38eSCan Guo static int ufshcd_tx_eqtr_iter_init(struct ufs_hba *hba,
46403e5d38eSCan Guo 				    struct tx_eqtr_iter *h_iter,
46503e5d38eSCan Guo 				    struct tx_eqtr_iter *d_iter)
46603e5d38eSCan Guo {
46703e5d38eSCan Guo 	u32 cap;
46803e5d38eSCan Guo 	int ret;
46903e5d38eSCan Guo 
47003e5d38eSCan Guo 	if (!hba->host_preshoot_cap) {
47103e5d38eSCan Guo 		ret = ufshcd_dme_get(hba, UIC_ARG_MIB(TX_HS_PRESHOOT_SETTING_CAP), &cap);
47203e5d38eSCan Guo 		if (ret)
47303e5d38eSCan Guo 			return ret;
47403e5d38eSCan Guo 
47503e5d38eSCan Guo 		hba->host_preshoot_cap = cap & TX_EQTR_CAP_MASK;
47603e5d38eSCan Guo 	}
47703e5d38eSCan Guo 
47803e5d38eSCan Guo 	if (!hba->host_deemphasis_cap) {
47903e5d38eSCan Guo 		ret = ufshcd_dme_get(hba, UIC_ARG_MIB(TX_HS_DEEMPHASIS_SETTING_CAP), &cap);
48003e5d38eSCan Guo 		if (ret)
48103e5d38eSCan Guo 			return ret;
48203e5d38eSCan Guo 
48303e5d38eSCan Guo 		hba->host_deemphasis_cap = cap & TX_EQTR_CAP_MASK;
48403e5d38eSCan Guo 	}
48503e5d38eSCan Guo 
48603e5d38eSCan Guo 	if (!hba->device_preshoot_cap) {
48703e5d38eSCan Guo 		ret = ufshcd_dme_peer_get(hba, UIC_ARG_MIB(TX_HS_PRESHOOT_SETTING_CAP), &cap);
48803e5d38eSCan Guo 		if (ret)
48903e5d38eSCan Guo 			return ret;
49003e5d38eSCan Guo 
49103e5d38eSCan Guo 		hba->device_preshoot_cap = cap & TX_EQTR_CAP_MASK;
49203e5d38eSCan Guo 	}
49303e5d38eSCan Guo 
49403e5d38eSCan Guo 	if (!hba->device_deemphasis_cap) {
49503e5d38eSCan Guo 		ret = ufshcd_dme_peer_get(hba, UIC_ARG_MIB(TX_HS_DEEMPHASIS_SETTING_CAP), &cap);
49603e5d38eSCan Guo 		if (ret)
49703e5d38eSCan Guo 			return ret;
49803e5d38eSCan Guo 
49903e5d38eSCan Guo 		hba->device_deemphasis_cap = cap & TX_EQTR_CAP_MASK;
50003e5d38eSCan Guo 	}
50103e5d38eSCan Guo 
50203e5d38eSCan Guo 	/*
50303e5d38eSCan Guo 	 * Support PreShoot & DeEmphasis of value 0 is mandatory, hence they are
50403e5d38eSCan Guo 	 * not reflected in PreShoot/DeEmphasis capabilities. Left shift the
50503e5d38eSCan Guo 	 * capability bitmap by 1 and set bit[0] to reflect value 0 is
50603e5d38eSCan Guo 	 * supported, such that test_bit() can be used later for convenience.
50703e5d38eSCan Guo 	 */
50803e5d38eSCan Guo 	h_iter->preshoot_bitmap = (hba->host_preshoot_cap << 0x1) | 0x1;
50903e5d38eSCan Guo 	h_iter->deemphasis_bitmap = (hba->host_deemphasis_cap << 0x1) | 0x1;
51003e5d38eSCan Guo 	d_iter->preshoot_bitmap = (hba->device_preshoot_cap << 0x1) | 0x1;
51103e5d38eSCan Guo 	d_iter->deemphasis_bitmap = (hba->device_deemphasis_cap << 0x1) | 0x1;
51203e5d38eSCan Guo 
51303e5d38eSCan Guo 	return 0;
51403e5d38eSCan Guo }
51503e5d38eSCan Guo 
51603e5d38eSCan Guo /**
51703e5d38eSCan Guo  * adapt_cap_to_t_adapt - Calculate TAdapt from adapt capability
51803e5d38eSCan Guo  * @adapt_cap: Adapt capability
51903e5d38eSCan Guo  *
52003e5d38eSCan Guo  * For NRZ:
52103e5d38eSCan Guo  *   IF (ADAPT_range = FINE)
52203e5d38eSCan Guo  *     TADAPT = 650 x (ADAPT_length + 1)
52303e5d38eSCan Guo  *   ELSE (IF ADAPT_range = COARSE)
52403e5d38eSCan Guo  *     TADAPT = 650 x 2^ADAPT_length
52503e5d38eSCan Guo  *
52603e5d38eSCan Guo  * Returns calculated TAdapt value in term of Unit Intervals (UI)
52703e5d38eSCan Guo  */
52803e5d38eSCan Guo static inline u64 adapt_cap_to_t_adapt(u32 adapt_cap)
52903e5d38eSCan Guo {
53003e5d38eSCan Guo 	u64 tadapt;
53103e5d38eSCan Guo 	u8 adapt_length = adapt_cap & ADAPT_LENGTH_MASK;
53203e5d38eSCan Guo 
53303e5d38eSCan Guo 	if (!IS_ADAPT_RANGE_COARSE(adapt_cap))
53403e5d38eSCan Guo 		tadapt = TADAPT_FACTOR * (adapt_length + 1);
53503e5d38eSCan Guo 	else
53603e5d38eSCan Guo 		tadapt = TADAPT_FACTOR * (1 << adapt_length);
53703e5d38eSCan Guo 
53803e5d38eSCan Guo 	return tadapt;
53903e5d38eSCan Guo }
54003e5d38eSCan Guo 
54103e5d38eSCan Guo /**
54203e5d38eSCan Guo  * adapt_cap_to_t_adapt_l0l3 - Calculate TAdapt_L0_L3 from adapt capability
54303e5d38eSCan Guo  * @adapt_cap: Adapt capability
54403e5d38eSCan Guo  *
54503e5d38eSCan Guo  * For PAM-4:
54603e5d38eSCan Guo  *   IF (ADAPT_range = FINE)
54703e5d38eSCan Guo  *     TADAPT_L0_L3 = 2^9 x ADAPT_length
54803e5d38eSCan Guo  *   ELSE IF (ADAPT_range = COARSE)
54903e5d38eSCan Guo  *     TADAPT_L0_L3 = 2^9 x (2^ADAPT_length)
55003e5d38eSCan Guo  *
55103e5d38eSCan Guo  * Returns calculated TAdapt value in term of Unit Intervals (UI)
55203e5d38eSCan Guo  */
55303e5d38eSCan Guo static inline u64 adapt_cap_to_t_adapt_l0l3(u32 adapt_cap)
55403e5d38eSCan Guo {
55503e5d38eSCan Guo 	u64 tadapt;
55603e5d38eSCan Guo 	u8 adapt_length = adapt_cap & ADAPT_LENGTH_MASK;
55703e5d38eSCan Guo 
55803e5d38eSCan Guo 	if (!IS_ADAPT_RANGE_COARSE(adapt_cap))
55903e5d38eSCan Guo 		tadapt = TADAPT_L0L3_FACTOR * adapt_length;
56003e5d38eSCan Guo 	else
56103e5d38eSCan Guo 		tadapt = TADAPT_L0L3_FACTOR * (1 << adapt_length);
56203e5d38eSCan Guo 
56303e5d38eSCan Guo 	return tadapt;
56403e5d38eSCan Guo }
56503e5d38eSCan Guo 
56603e5d38eSCan Guo /**
56703e5d38eSCan Guo  * adapt_cap_to_t_adapt_l0l1l2l3 - Calculate TAdapt_L0_L1_L2_L3 from adapt capability
56803e5d38eSCan Guo  * @adapt_cap: Adapt capability
56903e5d38eSCan Guo  *
57003e5d38eSCan Guo  * For PAM-4:
57103e5d38eSCan Guo  *   IF (ADAPT_range_L0_L1_L2_L3 = FINE)
57203e5d38eSCan Guo  *     TADAPT_L0_L1_L2_L3 = 2^15 x (ADAPT_length_L0_L1_L2_L3 + 1)
57303e5d38eSCan Guo  *   ELSE IF (ADAPT_range_L0_L1_L2_L3 = COARSE)
57403e5d38eSCan Guo  *     TADAPT_L0_L1_L2_L3 = 2^15 x 2^ADAPT_length_L0_L1_L2_L3
57503e5d38eSCan Guo  *
57603e5d38eSCan Guo  * Returns calculated TAdapt value in term of Unit Intervals (UI)
57703e5d38eSCan Guo  */
57803e5d38eSCan Guo static inline u64 adapt_cap_to_t_adapt_l0l1l2l3(u32 adapt_cap)
57903e5d38eSCan Guo {
58003e5d38eSCan Guo 	u64 tadapt;
58103e5d38eSCan Guo 	u8 adapt_length = adapt_cap & ADAPT_LENGTH_MASK;
58203e5d38eSCan Guo 
58303e5d38eSCan Guo 	if (!IS_ADAPT_RANGE_COARSE(adapt_cap))
58403e5d38eSCan Guo 		tadapt = TADAPT_L0L1L2L3_FACTOR * (adapt_length + 1);
58503e5d38eSCan Guo 	else
58603e5d38eSCan Guo 		tadapt = TADAPT_L0L1L2L3_FACTOR * (1 << adapt_length);
58703e5d38eSCan Guo 
58803e5d38eSCan Guo 	return tadapt;
58903e5d38eSCan Guo }
59003e5d38eSCan Guo 
59103e5d38eSCan Guo /**
59203e5d38eSCan Guo  * ufshcd_setup_tx_eqtr_adapt_length - Setup TX adapt length for EQTR
59303e5d38eSCan Guo  * @hba: per adapter instance
59403e5d38eSCan Guo  * @params: TX EQ parameters data structure
59503e5d38eSCan Guo  * @gear: target gear for EQTR
59603e5d38eSCan Guo  *
59703e5d38eSCan Guo  * This function determines and configures the proper TX adapt length (TAdapt)
59803e5d38eSCan Guo  * for the TX EQTR procedure based on the target gear and RX adapt capabilities
59903e5d38eSCan Guo  * of both host and device.
60003e5d38eSCan Guo  *
60103e5d38eSCan Guo  * Guidelines from MIPI UniPro v3.0 spec - select the minimum Adapt Length for
60203e5d38eSCan Guo  * the Equalization Training procedure based on the following conditions:
60303e5d38eSCan Guo  *
60403e5d38eSCan Guo  * If the target High-Speed Gear n is HS-G4 or HS-G5:
60503e5d38eSCan Guo  *  PA_TxAdaptLength_EQTR[7:0] >= Max (10us, RX_HS_Gn_ADAPT_INITIAL_Capability,
60603e5d38eSCan Guo  *					PA_PeerRxHsGnAdaptInitial)
60703e5d38eSCan Guo  *  PA_TxAdaptLength_EQTR[7:0] shall be shorter than PACP_REQUEST_TIMER (10ms)
60803e5d38eSCan Guo  *  PA_TxAdaptLength_EQTR[15:8] is not relevant for HS-G4 and HS-G5. This field
60903e5d38eSCan Guo  *  is set to 255 (reserved value).
61003e5d38eSCan Guo  *
61103e5d38eSCan Guo  * If the target High-Speed Gear n is HS-G6:
61203e5d38eSCan Guo  *  PA_TxAdapthLength_EQTR >= 10us
61303e5d38eSCan Guo  *  PA_TxAdapthLength_EQTR[7:0] >= Max (RX_HS_G6_ADAPT_INITIAL_Capability,
61403e5d38eSCan Guo  *					PA_PeerRxHsG6AdaptInitialL0L3)
61503e5d38eSCan Guo  *  PA_TxAdapthLength_EQTR[15:8] >= Max (RX_HS_G6_ADAPT_INITIAL_L0_L1_L2_L3_Capability,
61603e5d38eSCan Guo  *					PA_PeerRxHsG6AdaptInitialL0L1L2L3)
61703e5d38eSCan Guo  * PA_TxAdaptLength_EQTR shall be shorter than PACP_REQUEST_TIMER value of 10ms.
61803e5d38eSCan Guo  *
61903e5d38eSCan Guo  * Since adapt capabilities encode both range (fine/coarse) and length values,
62003e5d38eSCan Guo  * direct comparison is not possible. This function converts adapt capabilities
62103e5d38eSCan Guo  * to actual time durations in Unit Intervals (UI) using the Adapt time
62203e5d38eSCan Guo  * calculation formular in M-PHY v6.0 spec (Table 8), then selects the maximum
62303e5d38eSCan Guo  * to ensure both host and device use adequate TX adapt length.
62403e5d38eSCan Guo  *
62503e5d38eSCan Guo  * Returns 0 on success, negative error code otherwise
62603e5d38eSCan Guo  */
62703e5d38eSCan Guo static int ufshcd_setup_tx_eqtr_adapt_length(struct ufs_hba *hba,
62803e5d38eSCan Guo 					     struct ufshcd_tx_eq_params *params,
62903e5d38eSCan Guo 					     u32 gear)
63003e5d38eSCan Guo {
631adbabdcfSCan Guo 	struct ufshcd_tx_eqtr_record *rec = params->eqtr_record;
63203e5d38eSCan Guo 	u32 adapt_eqtr;
63303e5d38eSCan Guo 	int ret;
63403e5d38eSCan Guo 
635adbabdcfSCan Guo 	if (rec && rec->saved_adapt_eqtr) {
636adbabdcfSCan Guo 		adapt_eqtr = rec->saved_adapt_eqtr;
637adbabdcfSCan Guo 		goto set_adapt_eqtr;
638adbabdcfSCan Guo 	}
639adbabdcfSCan Guo 
64003e5d38eSCan Guo 	if (gear == UFS_HS_G4 || gear == UFS_HS_G5) {
64103e5d38eSCan Guo 		u64 t_adapt, t_adapt_local, t_adapt_peer;
64203e5d38eSCan Guo 		u32 adapt_cap_local, adapt_cap_peer, adapt_length;
64303e5d38eSCan Guo 
64403e5d38eSCan Guo 		ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(rx_adapt_initial_cap[gear - 1],
64503e5d38eSCan Guo 				     UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)),
64603e5d38eSCan Guo 				     &adapt_cap_local);
64703e5d38eSCan Guo 		if (ret)
64803e5d38eSCan Guo 			return ret;
64903e5d38eSCan Guo 
65003e5d38eSCan Guo 		if (adapt_cap_local > ADAPT_LENGTH_MAX) {
65103e5d38eSCan Guo 			dev_err(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_CAP (0x%x) exceeds MAX\n",
65203e5d38eSCan Guo 				gear, adapt_cap_local);
65303e5d38eSCan Guo 			return -EINVAL;
65403e5d38eSCan Guo 		}
65503e5d38eSCan Guo 
65603e5d38eSCan Guo 		ret = ufshcd_dme_get(hba, UIC_ARG_MIB(pa_peer_rx_adapt_initial[gear - 1]),
65703e5d38eSCan Guo 				     &adapt_cap_peer);
65803e5d38eSCan Guo 		if (ret)
65903e5d38eSCan Guo 			return ret;
66003e5d38eSCan Guo 
66103e5d38eSCan Guo 		if (adapt_cap_peer > ADAPT_LENGTH_MAX) {
66203e5d38eSCan Guo 			dev_err(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_CAP (0x%x) exceeds MAX\n",
66303e5d38eSCan Guo 				gear, adapt_cap_peer);
66403e5d38eSCan Guo 			return -EINVAL;
66503e5d38eSCan Guo 		}
66603e5d38eSCan Guo 
66703e5d38eSCan Guo 		t_adapt_local = adapt_cap_to_t_adapt(adapt_cap_local);
66803e5d38eSCan Guo 		t_adapt_peer = adapt_cap_to_t_adapt(adapt_cap_peer);
66903e5d38eSCan Guo 		t_adapt = max(t_adapt_local, t_adapt_peer);
67003e5d38eSCan Guo 
67103e5d38eSCan Guo 		dev_dbg(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_CAP = 0x%x\n",
67203e5d38eSCan Guo 			gear, adapt_cap_local);
67303e5d38eSCan Guo 		dev_dbg(hba->dev, "peer RX_HS_G%u_ADAPT_INITIAL_CAP = 0x%x\n",
67403e5d38eSCan Guo 			gear, adapt_cap_peer);
67503e5d38eSCan Guo 		dev_dbg(hba->dev, "t_adapt_local = %llu UI, t_adapt_peer = %llu UI\n",
67603e5d38eSCan Guo 			t_adapt_local, t_adapt_peer);
67703e5d38eSCan Guo 		dev_dbg(hba->dev, "TAdapt %llu UI selected for TX EQTR\n",
67803e5d38eSCan Guo 			t_adapt);
67903e5d38eSCan Guo 
68003e5d38eSCan Guo 		adapt_length = (t_adapt_local >= t_adapt_peer) ?
68103e5d38eSCan Guo 			       adapt_cap_local : adapt_cap_peer;
68203e5d38eSCan Guo 
68303e5d38eSCan Guo 		if (gear == UFS_HS_G4 && t_adapt < TX_EQTR_HS_G4_MIN_T_ADAPT) {
68403e5d38eSCan Guo 			dev_dbg(hba->dev, "TAdapt %llu UI is too short for TX EQTR for HS-G%u, use default Adapt 0x%x\n",
68503e5d38eSCan Guo 				t_adapt, gear, TX_EQTR_HS_G4_ADAPT_DEFAULT);
68603e5d38eSCan Guo 			adapt_length = TX_EQTR_HS_G4_ADAPT_DEFAULT;
68703e5d38eSCan Guo 		} else if (gear == UFS_HS_G5 && t_adapt < TX_EQTR_HS_G5_MIN_T_ADAPT) {
68803e5d38eSCan Guo 			dev_dbg(hba->dev, "TAdapt %llu UI is too short for TX EQTR for HS-G%u, use default Adapt 0x%x\n",
68903e5d38eSCan Guo 				t_adapt, gear, TX_EQTR_HS_G5_ADAPT_DEFAULT);
69003e5d38eSCan Guo 			adapt_length = TX_EQTR_HS_G5_ADAPT_DEFAULT;
69103e5d38eSCan Guo 		}
69203e5d38eSCan Guo 
69303e5d38eSCan Guo 		adapt_eqtr = adapt_length |
69403e5d38eSCan Guo 			     (TX_EQTR_ADAPT_RESERVED << TX_EQTR_ADAPT_LENGTH_L0L1L2L3_SHIFT);
69503e5d38eSCan Guo 	} else if (gear == UFS_HS_G6) {
69603e5d38eSCan Guo 		u64 t_adapt, t_adapt_l0l3, t_adapt_l0l3_local, t_adapt_l0l3_peer;
69703e5d38eSCan Guo 		u64 t_adapt_l0l1l2l3, t_adapt_l0l1l2l3_local, t_adapt_l0l1l2l3_peer;
69803e5d38eSCan Guo 		u32 adapt_l0l3_cap_local, adapt_l0l3_cap_peer, adapt_length_l0l3;
69903e5d38eSCan Guo 		u32 adapt_l0l1l2l3_cap_local, adapt_l0l1l2l3_cap_peer, adapt_length_l0l1l2l3;
70003e5d38eSCan Guo 
70103e5d38eSCan Guo 		ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(rx_adapt_initial_cap[gear - 1],
70203e5d38eSCan Guo 				     UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)),
70303e5d38eSCan Guo 				     &adapt_l0l3_cap_local);
70403e5d38eSCan Guo 		if (ret)
70503e5d38eSCan Guo 			return ret;
70603e5d38eSCan Guo 
70703e5d38eSCan Guo 		if (adapt_l0l3_cap_local > ADAPT_L0L3_LENGTH_MAX) {
70803e5d38eSCan Guo 			dev_err(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_CAP (0x%x) exceeds MAX\n",
70903e5d38eSCan Guo 				gear, adapt_l0l3_cap_local);
71003e5d38eSCan Guo 			return -EINVAL;
71103e5d38eSCan Guo 		}
71203e5d38eSCan Guo 
71303e5d38eSCan Guo 		ret = ufshcd_dme_get(hba, UIC_ARG_MIB(pa_peer_rx_adapt_initial[gear - 1]),
71403e5d38eSCan Guo 				     &adapt_l0l3_cap_peer);
71503e5d38eSCan Guo 		if (ret)
71603e5d38eSCan Guo 			return ret;
71703e5d38eSCan Guo 
71803e5d38eSCan Guo 		if (adapt_l0l3_cap_peer > ADAPT_L0L3_LENGTH_MAX) {
71903e5d38eSCan Guo 			dev_err(hba->dev, "peer RX_HS_G%u_ADAPT_INITIAL_CAP (0x%x) exceeds MAX\n",
72003e5d38eSCan Guo 				gear, adapt_l0l3_cap_peer);
72103e5d38eSCan Guo 			return -EINVAL;
72203e5d38eSCan Guo 		}
72303e5d38eSCan Guo 
72403e5d38eSCan Guo 		t_adapt_l0l3_local = adapt_cap_to_t_adapt_l0l3(adapt_l0l3_cap_local);
72503e5d38eSCan Guo 		t_adapt_l0l3_peer = adapt_cap_to_t_adapt_l0l3(adapt_l0l3_cap_peer);
72603e5d38eSCan Guo 
72703e5d38eSCan Guo 		dev_dbg(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_CAP = 0x%x\n",
72803e5d38eSCan Guo 			gear, adapt_l0l3_cap_local);
72903e5d38eSCan Guo 		dev_dbg(hba->dev, "peer RX_HS_G%u_ADAPT_INITIAL_CAP = 0x%x\n",
73003e5d38eSCan Guo 			gear, adapt_l0l3_cap_peer);
73103e5d38eSCan Guo 		dev_dbg(hba->dev, "t_adapt_l0l3_local = %llu UI, t_adapt_l0l3_peer = %llu UI\n",
73203e5d38eSCan Guo 			t_adapt_l0l3_local, t_adapt_l0l3_peer);
73303e5d38eSCan Guo 
73403e5d38eSCan Guo 		ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(RX_HS_G6_ADAPT_INITIAL_L0L1L2L3_CAP,
73503e5d38eSCan Guo 				     UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)),
73603e5d38eSCan Guo 				     &adapt_l0l1l2l3_cap_local);
73703e5d38eSCan Guo 		if (ret)
73803e5d38eSCan Guo 			return ret;
73903e5d38eSCan Guo 
74003e5d38eSCan Guo 		if (adapt_l0l1l2l3_cap_local > ADAPT_L0L1L2L3_LENGTH_MAX) {
74103e5d38eSCan Guo 			dev_err(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_L0L1L2L3_CAP (0x%x) exceeds MAX\n",
74203e5d38eSCan Guo 				gear, adapt_l0l1l2l3_cap_local);
74303e5d38eSCan Guo 			return -EINVAL;
74403e5d38eSCan Guo 		}
74503e5d38eSCan Guo 
74603e5d38eSCan Guo 		ret = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTINITIALL0L1L2L3),
74703e5d38eSCan Guo 				     &adapt_l0l1l2l3_cap_peer);
74803e5d38eSCan Guo 		if (ret)
74903e5d38eSCan Guo 			return ret;
75003e5d38eSCan Guo 
75103e5d38eSCan Guo 		if (adapt_l0l1l2l3_cap_peer > ADAPT_L0L1L2L3_LENGTH_MAX) {
75203e5d38eSCan Guo 			dev_err(hba->dev, "peer RX_HS_G%u_ADAPT_INITIAL_L0L1L2L3_CAP (0x%x) exceeds MAX\n",
75303e5d38eSCan Guo 				gear, adapt_l0l1l2l3_cap_peer);
75403e5d38eSCan Guo 			return -EINVAL;
75503e5d38eSCan Guo 		}
75603e5d38eSCan Guo 
75703e5d38eSCan Guo 		t_adapt_l0l1l2l3_local = adapt_cap_to_t_adapt_l0l1l2l3(adapt_l0l1l2l3_cap_local);
75803e5d38eSCan Guo 		t_adapt_l0l1l2l3_peer = adapt_cap_to_t_adapt_l0l1l2l3(adapt_l0l1l2l3_cap_peer);
75903e5d38eSCan Guo 
76003e5d38eSCan Guo 		dev_dbg(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_L0L1L2L3_CAP = 0x%x\n",
76103e5d38eSCan Guo 			gear, adapt_l0l1l2l3_cap_local);
76203e5d38eSCan Guo 		dev_dbg(hba->dev, "peer RX_HS_G%u_ADAPT_INITIAL_L0L1L2L3_CAP = 0x%x\n",
76303e5d38eSCan Guo 			gear, adapt_l0l1l2l3_cap_peer);
76403e5d38eSCan Guo 		dev_dbg(hba->dev, "t_adapt_l0l1l2l3_local = %llu UI, t_adapt_l0l1l2l3_peer = %llu UI\n",
76503e5d38eSCan Guo 			t_adapt_l0l1l2l3_local, t_adapt_l0l1l2l3_peer);
76603e5d38eSCan Guo 
76703e5d38eSCan Guo 		t_adapt_l0l1l2l3 = max(t_adapt_l0l1l2l3_local, t_adapt_l0l1l2l3_peer);
76803e5d38eSCan Guo 		t_adapt_l0l3 = max(t_adapt_l0l3_local, t_adapt_l0l3_peer);
76903e5d38eSCan Guo 		t_adapt = t_adapt_l0l3 + t_adapt_l0l1l2l3;
77003e5d38eSCan Guo 
77103e5d38eSCan Guo 		dev_dbg(hba->dev, "TAdapt %llu PAM-4 UI selected for TX EQTR\n",
77203e5d38eSCan Guo 			t_adapt);
77303e5d38eSCan Guo 
77403e5d38eSCan Guo 		adapt_length_l0l3 = (t_adapt_l0l3_local >= t_adapt_l0l3_peer) ?
77503e5d38eSCan Guo 				    adapt_l0l3_cap_local : adapt_l0l3_cap_peer;
77603e5d38eSCan Guo 		adapt_length_l0l1l2l3 = (t_adapt_l0l1l2l3_local >= t_adapt_l0l1l2l3_peer) ?
77703e5d38eSCan Guo 					adapt_l0l1l2l3_cap_local : adapt_l0l1l2l3_cap_peer;
77803e5d38eSCan Guo 
77903e5d38eSCan Guo 		if (t_adapt < TX_EQTR_HS_G6_MIN_T_ADAPT) {
78003e5d38eSCan Guo 			dev_dbg(hba->dev, "TAdapt %llu UI is too short for TX EQTR for HS-G%u, use default Adapt 0x%x\n",
78103e5d38eSCan Guo 				t_adapt, gear, TX_EQTR_HS_G6_ADAPT_DEFAULT);
78203e5d38eSCan Guo 			adapt_length_l0l3 = TX_EQTR_HS_G6_ADAPT_DEFAULT;
78303e5d38eSCan Guo 		}
78403e5d38eSCan Guo 
78503e5d38eSCan Guo 		adapt_eqtr = adapt_length_l0l3 |
78603e5d38eSCan Guo 			     (adapt_length_l0l1l2l3 << TX_EQTR_ADAPT_LENGTH_L0L1L2L3_SHIFT);
78703e5d38eSCan Guo 	} else {
78803e5d38eSCan Guo 		return -EINVAL;
78903e5d38eSCan Guo 	}
79003e5d38eSCan Guo 
791adbabdcfSCan Guo 	if (rec)
792adbabdcfSCan Guo 		rec->saved_adapt_eqtr = (u16)adapt_eqtr;
793adbabdcfSCan Guo 
794adbabdcfSCan Guo set_adapt_eqtr:
79503e5d38eSCan Guo 	ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXADAPTLENGTH_EQTR), adapt_eqtr);
79603e5d38eSCan Guo 	if (ret)
79703e5d38eSCan Guo 		dev_err(hba->dev, "Failed to set adapt length for TX EQTR: %d\n", ret);
79803e5d38eSCan Guo 	else
79903e5d38eSCan Guo 		dev_dbg(hba->dev, "PA_TXADAPTLENGTH_EQTR configured to 0x%08x\n", adapt_eqtr);
80003e5d38eSCan Guo 
80103e5d38eSCan Guo 	return ret;
80203e5d38eSCan Guo }
80303e5d38eSCan Guo 
80403e5d38eSCan Guo /**
80503e5d38eSCan Guo  * ufshcd_compose_tx_eqtr_setting - Compose TX EQTR setting
80603e5d38eSCan Guo  * @iter: TX EQTR iterator data structure
80703e5d38eSCan Guo  * @num_lanes: number of active lanes
80803e5d38eSCan Guo  *
80903e5d38eSCan Guo  * Returns composed TX EQTR setting, same setting is used for all active lanes
81003e5d38eSCan Guo  */
81103e5d38eSCan Guo static inline u32 ufshcd_compose_tx_eqtr_setting(struct tx_eqtr_iter *iter,
81203e5d38eSCan Guo 						 int num_lanes)
81303e5d38eSCan Guo {
81403e5d38eSCan Guo 	u32 setting = 0;
81503e5d38eSCan Guo 	int lane;
81603e5d38eSCan Guo 
81703e5d38eSCan Guo 	for (lane = 0; lane < num_lanes; lane++) {
81803e5d38eSCan Guo 		setting |= TX_HS_PRESHOOT_BITS(lane, iter->preshoot);
81903e5d38eSCan Guo 		setting |= TX_HS_DEEMPHASIS_BITS(lane, iter->deemphasis);
82003e5d38eSCan Guo 	}
82103e5d38eSCan Guo 
82203e5d38eSCan Guo 	return setting;
82303e5d38eSCan Guo }
82403e5d38eSCan Guo 
82503e5d38eSCan Guo /**
82603e5d38eSCan Guo  * ufshcd_apply_tx_eqtr_settings - Apply TX EQTR setting
82703e5d38eSCan Guo  * @hba: per adapter instance
82803e5d38eSCan Guo  * @pwr_mode: target power mode containing gear and rate information
82903e5d38eSCan Guo  * @h_iter: host TX EQTR iterator data structure
83003e5d38eSCan Guo  * @d_iter: device TX EQTR iterator data structure
83103e5d38eSCan Guo  *
83203e5d38eSCan Guo  * Returns 0 on success, negative error code otherwise
83303e5d38eSCan Guo  */
83403e5d38eSCan Guo static int ufshcd_apply_tx_eqtr_settings(struct ufs_hba *hba,
83503e5d38eSCan Guo 					 struct ufs_pa_layer_attr *pwr_mode,
83603e5d38eSCan Guo 					 struct tx_eqtr_iter *h_iter,
83703e5d38eSCan Guo 					 struct tx_eqtr_iter *d_iter)
83803e5d38eSCan Guo {
83903e5d38eSCan Guo 	u32 setting;
84003e5d38eSCan Guo 	int ret;
84103e5d38eSCan Guo 
84203e5d38eSCan Guo 	setting = ufshcd_compose_tx_eqtr_setting(h_iter, pwr_mode->lane_tx);
84303e5d38eSCan Guo 	ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXEQTRSETTING), setting);
84403e5d38eSCan Guo 	if (ret)
84503e5d38eSCan Guo 		return ret;
84603e5d38eSCan Guo 
84703e5d38eSCan Guo 	setting = ufshcd_compose_tx_eqtr_setting(d_iter, pwr_mode->lane_rx);
84803e5d38eSCan Guo 	ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PEERTXEQTRSETTING), setting);
84903e5d38eSCan Guo 	if (ret)
85003e5d38eSCan Guo 		return ret;
85103e5d38eSCan Guo 
85203e5d38eSCan Guo 	ret = ufshcd_vops_apply_tx_eqtr_settings(hba, pwr_mode, h_iter, d_iter);
85303e5d38eSCan Guo 
85403e5d38eSCan Guo 	return ret;
85503e5d38eSCan Guo }
85603e5d38eSCan Guo 
85703e5d38eSCan Guo /**
85803e5d38eSCan Guo  * ufshcd_update_tx_eq_params - Update TX Equalization params
85903e5d38eSCan Guo  * @params: TX EQ parameters data structure
860adbabdcfSCan Guo  * @pwr_mode: target power mode containing gear and rate
86103e5d38eSCan Guo  * @eqtr_data: TX EQTR data structure
86203e5d38eSCan Guo  *
863adbabdcfSCan Guo  * Update TX Equalization params using results from TX EQTR data. Check also
864adbabdcfSCan Guo  * the TX EQTR FOM value for each TX lane in the TX EQTR data. If a TX lane got
865adbabdcfSCan Guo  * a FOM value of 0, restore the TX Equalization settings from the last known
866adbabdcfSCan Guo  * valid TX Equalization params for that specific TX lane.
86703e5d38eSCan Guo  */
86803e5d38eSCan Guo static inline void
86903e5d38eSCan Guo ufshcd_update_tx_eq_params(struct ufshcd_tx_eq_params *params,
870adbabdcfSCan Guo 			   struct ufs_pa_layer_attr *pwr_mode,
87103e5d38eSCan Guo 			   struct ufshcd_tx_eqtr_data *eqtr_data)
87203e5d38eSCan Guo {
87303e5d38eSCan Guo 	struct ufshcd_tx_eqtr_record *rec = params->eqtr_record;
87403e5d38eSCan Guo 
875adbabdcfSCan Guo 	if (params->is_valid) {
876adbabdcfSCan Guo 		int lane;
877adbabdcfSCan Guo 
878adbabdcfSCan Guo 		for (lane = 0; lane < pwr_mode->lane_tx; lane++)
879adbabdcfSCan Guo 			if (eqtr_data->host[lane].fom_val == 0)
880adbabdcfSCan Guo 				eqtr_data->host[lane] = params->host[lane];
881adbabdcfSCan Guo 
882adbabdcfSCan Guo 		for (lane = 0; lane < pwr_mode->lane_rx; lane++)
883adbabdcfSCan Guo 			if (eqtr_data->device[lane].fom_val == 0)
884adbabdcfSCan Guo 				eqtr_data->device[lane] = params->device[lane];
885adbabdcfSCan Guo 	}
886adbabdcfSCan Guo 
88703e5d38eSCan Guo 	memcpy(params->host, eqtr_data->host, sizeof(params->host));
88803e5d38eSCan Guo 	memcpy(params->device, eqtr_data->device, sizeof(params->device));
88903e5d38eSCan Guo 
89003e5d38eSCan Guo 	if (!rec)
89103e5d38eSCan Guo 		return;
89203e5d38eSCan Guo 
89303e5d38eSCan Guo 	memcpy(rec->host_fom, eqtr_data->host_fom, sizeof(rec->host_fom));
89403e5d38eSCan Guo 	memcpy(rec->device_fom, eqtr_data->device_fom, sizeof(rec->device_fom));
89503e5d38eSCan Guo 	rec->last_record_ts = ktime_get();
89603e5d38eSCan Guo 	rec->last_record_index++;
89703e5d38eSCan Guo }
89803e5d38eSCan Guo 
89903e5d38eSCan Guo /**
90003e5d38eSCan Guo  * __ufshcd_tx_eqtr - TX Equalization Training (EQTR) procedure
90103e5d38eSCan Guo  * @hba: per adapter instance
90203e5d38eSCan Guo  * @params: TX EQ parameters data structure
90303e5d38eSCan Guo  * @pwr_mode: target power mode containing gear and rate information
90403e5d38eSCan Guo  *
90503e5d38eSCan Guo  * This function implements the complete TX EQTR procedure as defined in UFSHCI
90603e5d38eSCan Guo  * v5.0 specification. It iterates through all possible combinations of PreShoot
90703e5d38eSCan Guo  * and DeEmphasis settings to find the optimal TX Equalization settings for all
90803e5d38eSCan Guo  * active lanes.
90903e5d38eSCan Guo  *
91003e5d38eSCan Guo  * Returns 0 on success, negative error code otherwise
91103e5d38eSCan Guo  */
91203e5d38eSCan Guo static int __ufshcd_tx_eqtr(struct ufs_hba *hba,
91303e5d38eSCan Guo 			    struct ufshcd_tx_eq_params *params,
91403e5d38eSCan Guo 			    struct ufs_pa_layer_attr *pwr_mode)
91503e5d38eSCan Guo {
91603e5d38eSCan Guo 	struct ufshcd_tx_eqtr_data *eqtr_data  __free(kfree) =
91703e5d38eSCan Guo 		kzalloc(sizeof(*eqtr_data), GFP_KERNEL);
91803e5d38eSCan Guo 	struct tx_eqtr_iter h_iter = {};
91903e5d38eSCan Guo 	struct tx_eqtr_iter d_iter = {};
92003e5d38eSCan Guo 	u32 gear = pwr_mode->gear_tx;
92103e5d38eSCan Guo 	u8 preshoot, deemphasis;
92203e5d38eSCan Guo 	ktime_t start;
92303e5d38eSCan Guo 	int ret;
92403e5d38eSCan Guo 
92503e5d38eSCan Guo 	if (!eqtr_data)
92603e5d38eSCan Guo 		return -ENOMEM;
92703e5d38eSCan Guo 
92803e5d38eSCan Guo 	dev_info(hba->dev, "Start TX EQTR procedure for HS-G%u, Rate-%s, RX Lanes: %u, TX Lanes: %u\n",
92903e5d38eSCan Guo 		 gear, ufs_hs_rate_to_str(pwr_mode->hs_rate),
93003e5d38eSCan Guo 		 pwr_mode->lane_rx, pwr_mode->lane_tx);
93103e5d38eSCan Guo 
93203e5d38eSCan Guo 	start = ktime_get();
93303e5d38eSCan Guo 
93403e5d38eSCan Guo 	/* Step 1 - Determine the TX Adapt Length for EQTR */
93503e5d38eSCan Guo 	ret = ufshcd_setup_tx_eqtr_adapt_length(hba, params, gear);
93603e5d38eSCan Guo 	if (ret) {
93703e5d38eSCan Guo 		dev_err(hba->dev, "Failed to setup TX EQTR Adaptation length: %d\n", ret);
93803e5d38eSCan Guo 		return ret;
93903e5d38eSCan Guo 	}
94003e5d38eSCan Guo 
94103e5d38eSCan Guo 	/* Step 2 - Determine TX Equalization setting capabilities */
94203e5d38eSCan Guo 	ret = ufshcd_tx_eqtr_iter_init(hba, &h_iter, &d_iter);
94303e5d38eSCan Guo 	if (ret) {
94403e5d38eSCan Guo 		dev_err(hba->dev, "Failed to init TX EQTR data: %d\n", ret);
94503e5d38eSCan Guo 		return ret;
94603e5d38eSCan Guo 	}
94703e5d38eSCan Guo 
94803e5d38eSCan Guo 	/* TX EQTR main loop */
94903e5d38eSCan Guo 	for (preshoot = 0; preshoot < TX_HS_NUM_PRESHOOT; preshoot++) {
95003e5d38eSCan Guo 		for (deemphasis = 0; deemphasis < TX_HS_NUM_DEEMPHASIS; deemphasis++) {
95103e5d38eSCan Guo 			if (!tx_eqtr_iter_update(preshoot, deemphasis, &h_iter, &d_iter))
95203e5d38eSCan Guo 				continue;
95303e5d38eSCan Guo 
95403e5d38eSCan Guo 			/* Step 3 - Apply TX EQTR settings */
95503e5d38eSCan Guo 			ret = ufshcd_apply_tx_eqtr_settings(hba, pwr_mode, &h_iter, &d_iter);
95603e5d38eSCan Guo 			if (ret) {
95703e5d38eSCan Guo 				dev_err(hba->dev, "Failed to apply TX EQTR settings (PreShoot %u, DeEmphasis %u): %d\n",
95803e5d38eSCan Guo 					preshoot, deemphasis, ret);
95903e5d38eSCan Guo 				return ret;
96003e5d38eSCan Guo 			}
96103e5d38eSCan Guo 
96203e5d38eSCan Guo 			/* Step 4 - Trigger UIC TX EQTR */
96303e5d38eSCan Guo 			ret = ufshcd_uic_tx_eqtr(hba, gear);
96403e5d38eSCan Guo 			if (ret) {
96503e5d38eSCan Guo 				dev_err(hba->dev, "Failed to trigger UIC TX EQTR for target gear %u: %d\n",
96603e5d38eSCan Guo 					gear, ret);
96703e5d38eSCan Guo 				return ret;
96803e5d38eSCan Guo 			}
96903e5d38eSCan Guo 
97003e5d38eSCan Guo 			/* Step 5 - Get FOM */
97103e5d38eSCan Guo 			ret = ufshcd_get_rx_fom(hba, pwr_mode, &h_iter, &d_iter);
97203e5d38eSCan Guo 			if (ret) {
97303e5d38eSCan Guo 				dev_err(hba->dev, "Failed to get RX_FOM: %d\n",
97403e5d38eSCan Guo 					ret);
97503e5d38eSCan Guo 				return ret;
97603e5d38eSCan Guo 			}
97703e5d38eSCan Guo 
97803e5d38eSCan Guo 			ufshcd_evaluate_tx_eqtr_fom(hba, pwr_mode, eqtr_data, &h_iter, &d_iter);
97903e5d38eSCan Guo 		}
98003e5d38eSCan Guo 	}
98103e5d38eSCan Guo 
98203e5d38eSCan Guo 	dev_info(hba->dev, "TX EQTR procedure completed! Time elapsed: %llu ms\n",
98303e5d38eSCan Guo 		 ktime_to_ms(ktime_sub(ktime_get(), start)));
98403e5d38eSCan Guo 
985adbabdcfSCan Guo 	ufshcd_update_tx_eq_params(params, pwr_mode, eqtr_data);
98603e5d38eSCan Guo 
98703e5d38eSCan Guo 	return ret;
98803e5d38eSCan Guo }
98903e5d38eSCan Guo 
99003e5d38eSCan Guo /**
99103e5d38eSCan Guo  * ufshcd_tx_eqtr_prepare - Prepare UFS link for TX EQTR procedure
99203e5d38eSCan Guo  * @hba: per adapter instance
99303e5d38eSCan Guo  * @pwr_mode: target power mode containing gear and rate
99403e5d38eSCan Guo  *
99503e5d38eSCan Guo  * This function prepares the UFS link for TX Equalization Training (EQTR) by
99603e5d38eSCan Guo  * establishing the proper initial conditions required by the EQTR procedure.
99703e5d38eSCan Guo  * It ensures that EQTR starts from the most reliable Power Mode (HS-G1) with
99803e5d38eSCan Guo  * all connected lanes activated and sets host TX HS Adapt Type to INITIAL.
99903e5d38eSCan Guo  *
100003e5d38eSCan Guo  * Returns 0 on successful preparation, negative error code on failure
100103e5d38eSCan Guo  */
100203e5d38eSCan Guo static int ufshcd_tx_eqtr_prepare(struct ufs_hba *hba,
100303e5d38eSCan Guo 				  struct ufs_pa_layer_attr *pwr_mode)
100403e5d38eSCan Guo {
100503e5d38eSCan Guo 	struct ufs_pa_layer_attr pwr_mode_hs_g1 = {
100603e5d38eSCan Guo 		/* TX EQTR shall be initiated from the most reliable HS-G1 */
100703e5d38eSCan Guo 		.gear_rx = UFS_HS_G1,
100803e5d38eSCan Guo 		.gear_tx = UFS_HS_G1,
100903e5d38eSCan Guo 		.lane_rx = pwr_mode->lane_rx,
101003e5d38eSCan Guo 		.lane_tx = pwr_mode->lane_tx,
101103e5d38eSCan Guo 		.pwr_rx = FAST_MODE,
101203e5d38eSCan Guo 		.pwr_tx = FAST_MODE,
101303e5d38eSCan Guo 		/* Use the target power mode's HS rate */
101403e5d38eSCan Guo 		.hs_rate = pwr_mode->hs_rate,
101503e5d38eSCan Guo 	};
101603e5d38eSCan Guo 	u32 rate = pwr_mode->hs_rate;
101703e5d38eSCan Guo 	int ret;
101803e5d38eSCan Guo 
101903e5d38eSCan Guo 	/* Change power mode to HS-G1, activate all connected lanes. */
102003e5d38eSCan Guo 	ret = ufshcd_change_power_mode(hba, &pwr_mode_hs_g1,
102103e5d38eSCan Guo 				       UFSHCD_PMC_POLICY_DONT_FORCE);
102203e5d38eSCan Guo 	if (ret) {
102303e5d38eSCan Guo 		dev_err(hba->dev, "TX EQTR: Failed to change power mode to HS-G1, Rate-%s: %d\n",
102403e5d38eSCan Guo 			ufs_hs_rate_to_str(rate), ret);
102503e5d38eSCan Guo 		return ret;
102603e5d38eSCan Guo 	}
102703e5d38eSCan Guo 
102803e5d38eSCan Guo 	ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TXHSADAPTTYPE),
102903e5d38eSCan Guo 			     PA_INITIAL_ADAPT);
103003e5d38eSCan Guo 	if (ret)
103103e5d38eSCan Guo 		dev_err(hba->dev, "TX EQTR: Failed to set Host Adapt type to INITIAL: %d\n",
103203e5d38eSCan Guo 			ret);
103303e5d38eSCan Guo 
103403e5d38eSCan Guo 	return ret;
103503e5d38eSCan Guo }
103603e5d38eSCan Guo 
103703e5d38eSCan Guo static void ufshcd_tx_eqtr_unprepare(struct ufs_hba *hba,
103803e5d38eSCan Guo 				     struct ufs_pa_layer_attr *pwr_mode)
103903e5d38eSCan Guo {
104003e5d38eSCan Guo 	int err;
104103e5d38eSCan Guo 
104203e5d38eSCan Guo 	if (pwr_mode->pwr_rx == SLOWAUTO_MODE || pwr_mode->hs_rate == 0)
104303e5d38eSCan Guo 		return;
104403e5d38eSCan Guo 
104503e5d38eSCan Guo 	err = ufshcd_change_power_mode(hba, pwr_mode,
104603e5d38eSCan Guo 				       UFSHCD_PMC_POLICY_DONT_FORCE);
104703e5d38eSCan Guo 	if (err)
104803e5d38eSCan Guo 		dev_err(hba->dev, "%s: Failed to restore Power Mode: %d\n",
104903e5d38eSCan Guo 			__func__, err);
105003e5d38eSCan Guo }
105103e5d38eSCan Guo 
105203e5d38eSCan Guo /**
105303e5d38eSCan Guo  * ufshcd_tx_eqtr - Perform TX EQTR procedures with vops callbacks
105403e5d38eSCan Guo  * @hba: per adapter instance
105503e5d38eSCan Guo  * @params: TX EQ parameters data structure to populate
105603e5d38eSCan Guo  * @pwr_mode: target power mode containing gear and rate information
105703e5d38eSCan Guo  *
105803e5d38eSCan Guo  * This is the main entry point for performing TX Equalization Training (EQTR)
105903e5d38eSCan Guo  * procedure as defined in UFSCHI v5.0 specification. It serves as a wrapper
106003e5d38eSCan Guo  * around __ufshcd_tx_eqtr() to provide vops support through the variant
106103e5d38eSCan Guo  * operations framework.
106203e5d38eSCan Guo  *
106303e5d38eSCan Guo  * Returns 0 on success, negative error code on failure
106403e5d38eSCan Guo  */
106503e5d38eSCan Guo static int ufshcd_tx_eqtr(struct ufs_hba *hba,
106603e5d38eSCan Guo 			  struct ufshcd_tx_eq_params *params,
106703e5d38eSCan Guo 			  struct ufs_pa_layer_attr *pwr_mode)
106803e5d38eSCan Guo {
106903e5d38eSCan Guo 	struct ufs_pa_layer_attr old_pwr_info;
107003e5d38eSCan Guo 	int ret;
107103e5d38eSCan Guo 
107203e5d38eSCan Guo 	if (!params->eqtr_record) {
107303e5d38eSCan Guo 		params->eqtr_record = devm_kzalloc(hba->dev,
107403e5d38eSCan Guo 						   sizeof(*params->eqtr_record),
107503e5d38eSCan Guo 						   GFP_KERNEL);
107603e5d38eSCan Guo 		if (!params->eqtr_record)
107703e5d38eSCan Guo 			return -ENOMEM;
107803e5d38eSCan Guo 	}
107903e5d38eSCan Guo 
108003e5d38eSCan Guo 	memcpy(&old_pwr_info, &hba->pwr_info, sizeof(struct ufs_pa_layer_attr));
108103e5d38eSCan Guo 
108203e5d38eSCan Guo 	ret = ufshcd_tx_eqtr_prepare(hba, pwr_mode);
108303e5d38eSCan Guo 	if (ret) {
108403e5d38eSCan Guo 		dev_err(hba->dev, "Failed to prepare TX EQTR: %d\n", ret);
108503e5d38eSCan Guo 		goto out;
108603e5d38eSCan Guo 	}
108703e5d38eSCan Guo 
108803e5d38eSCan Guo 	ret = ufshcd_vops_tx_eqtr_notify(hba, PRE_CHANGE, pwr_mode);
108903e5d38eSCan Guo 	if (ret)
109003e5d38eSCan Guo 		goto out;
109103e5d38eSCan Guo 
109203e5d38eSCan Guo 	ret = __ufshcd_tx_eqtr(hba, params, pwr_mode);
109303e5d38eSCan Guo 	if (ret)
109403e5d38eSCan Guo 		goto out;
109503e5d38eSCan Guo 
109603e5d38eSCan Guo 	ret = ufshcd_vops_tx_eqtr_notify(hba, POST_CHANGE, pwr_mode);
109703e5d38eSCan Guo 
109803e5d38eSCan Guo out:
109903e5d38eSCan Guo 	if (ret)
110003e5d38eSCan Guo 		ufshcd_tx_eqtr_unprepare(hba, &old_pwr_info);
110103e5d38eSCan Guo 
110203e5d38eSCan Guo 	return ret;
110303e5d38eSCan Guo }
110403e5d38eSCan Guo 
110503e5d38eSCan Guo /**
110603e5d38eSCan Guo  * ufshcd_config_tx_eq_settings - Configure TX Equalization settings
110703e5d38eSCan Guo  * @hba: per adapter instance
110803e5d38eSCan Guo  * @pwr_mode: target power mode containing gear and rate information
1109adbabdcfSCan Guo  * @force_tx_eqtr: execute the TX EQTR procedure
111003e5d38eSCan Guo  *
111103e5d38eSCan Guo  * This function finds and sets the TX Equalization settings for the given
111203e5d38eSCan Guo  * target power mode.
111303e5d38eSCan Guo  *
111403e5d38eSCan Guo  * Returns 0 on success, error code otherwise
111503e5d38eSCan Guo  */
111603e5d38eSCan Guo int ufshcd_config_tx_eq_settings(struct ufs_hba *hba,
1117adbabdcfSCan Guo 				 struct ufs_pa_layer_attr *pwr_mode,
1118adbabdcfSCan Guo 				 bool force_tx_eqtr)
111903e5d38eSCan Guo {
112003e5d38eSCan Guo 	struct ufshcd_tx_eq_params *params;
112103e5d38eSCan Guo 	u32 gear, rate;
112203e5d38eSCan Guo 
112303e5d38eSCan Guo 	if (!ufshcd_is_tx_eq_supported(hba) || !use_adaptive_txeq)
112403e5d38eSCan Guo 		return 0;
112503e5d38eSCan Guo 
112603e5d38eSCan Guo 	if (!hba->max_pwr_info.is_valid) {
112703e5d38eSCan Guo 		dev_err(hba->dev, "Max power info is invalid\n");
112803e5d38eSCan Guo 		return -EINVAL;
112903e5d38eSCan Guo 	}
113003e5d38eSCan Guo 
113103e5d38eSCan Guo 	if (!pwr_mode) {
113203e5d38eSCan Guo 		dev_err(hba->dev, "Target power mode is NULL\n");
113303e5d38eSCan Guo 		return -EINVAL;
113403e5d38eSCan Guo 	}
113503e5d38eSCan Guo 
113603e5d38eSCan Guo 	gear = pwr_mode->gear_tx;
113703e5d38eSCan Guo 	rate = pwr_mode->hs_rate;
113803e5d38eSCan Guo 
113903e5d38eSCan Guo 	if (gear < UFS_HS_G1 || gear > UFS_HS_GEAR_MAX) {
114003e5d38eSCan Guo 		dev_err(hba->dev, "Invalid HS-Gear (%u) for TX Equalization\n",
114103e5d38eSCan Guo 			gear);
114203e5d38eSCan Guo 		return -EINVAL;
114303e5d38eSCan Guo 	} else if (gear < max_t(u32, adaptive_txeq_gear, UFS_HS_G4)) {
114403e5d38eSCan Guo 		/* TX EQTR is supported for HS-G4 and higher Gears */
114503e5d38eSCan Guo 		return 0;
114603e5d38eSCan Guo 	}
114703e5d38eSCan Guo 
114803e5d38eSCan Guo 	if (rate != PA_HS_MODE_A && rate != PA_HS_MODE_B) {
114903e5d38eSCan Guo 		dev_err(hba->dev, "Invalid HS-Rate (%u) for TX Equalization\n",
115003e5d38eSCan Guo 			rate);
115103e5d38eSCan Guo 		return -EINVAL;
115203e5d38eSCan Guo 	}
115303e5d38eSCan Guo 
115403e5d38eSCan Guo 	params = &hba->tx_eq_params[gear - 1];
1155adbabdcfSCan Guo 	if (!params->is_valid || force_tx_eqtr) {
115603e5d38eSCan Guo 		int ret;
115703e5d38eSCan Guo 
115803e5d38eSCan Guo 		ret = ufshcd_tx_eqtr(hba, params, pwr_mode);
115903e5d38eSCan Guo 		if (ret) {
116003e5d38eSCan Guo 			dev_err(hba->dev, "Failed to train TX Equalization for HS-G%u, Rate-%s: %d\n",
116103e5d38eSCan Guo 				gear, ufs_hs_rate_to_str(rate), ret);
116203e5d38eSCan Guo 			return ret;
116303e5d38eSCan Guo 		}
116403e5d38eSCan Guo 
116503e5d38eSCan Guo 		/* Mark TX Equalization settings as valid */
116603e5d38eSCan Guo 		params->is_valid = true;
116703e5d38eSCan Guo 		params->is_applied = false;
116803e5d38eSCan Guo 	}
116903e5d38eSCan Guo 
117003e5d38eSCan Guo 	if (params->is_valid && !params->is_applied) {
117103e5d38eSCan Guo 		int ret;
117203e5d38eSCan Guo 
117303e5d38eSCan Guo 		ret = ufshcd_apply_tx_eq_settings(hba, params, gear);
117403e5d38eSCan Guo 		if (ret) {
117503e5d38eSCan Guo 			dev_err(hba->dev, "Failed to apply TX Equalization settings for HS-G%u, Rate-%s: %d\n",
117603e5d38eSCan Guo 				gear, ufs_hs_rate_to_str(rate), ret);
117703e5d38eSCan Guo 			return ret;
117803e5d38eSCan Guo 		}
117903e5d38eSCan Guo 
118003e5d38eSCan Guo 		params->is_applied = true;
118103e5d38eSCan Guo 	}
118203e5d38eSCan Guo 
118303e5d38eSCan Guo 	return 0;
118403e5d38eSCan Guo }
118503e5d38eSCan Guo 
118603e5d38eSCan Guo /**
118703e5d38eSCan Guo  * ufshcd_apply_valid_tx_eq_settings - Apply valid TX Equalization settings
118803e5d38eSCan Guo  * @hba: per-adapter instance
118903e5d38eSCan Guo  *
119003e5d38eSCan Guo  * This function iterates through all supported High-Speed (HS) gears and
119103e5d38eSCan Guo  * applies valid TX Equalization settings to both Host and Device.
119203e5d38eSCan Guo  */
119303e5d38eSCan Guo void ufshcd_apply_valid_tx_eq_settings(struct ufs_hba *hba)
119403e5d38eSCan Guo {
119503e5d38eSCan Guo 	struct ufshcd_tx_eq_params *params;
119603e5d38eSCan Guo 	int gear, err;
119703e5d38eSCan Guo 
119803e5d38eSCan Guo 	if (!ufshcd_is_tx_eq_supported(hba))
119903e5d38eSCan Guo 		return;
120003e5d38eSCan Guo 
120103e5d38eSCan Guo 	if (!hba->max_pwr_info.is_valid) {
120203e5d38eSCan Guo 		dev_err(hba->dev, "Max power info is invalid, cannot apply TX Equalization settings\n");
120303e5d38eSCan Guo 		return;
120403e5d38eSCan Guo 	}
120503e5d38eSCan Guo 
120603e5d38eSCan Guo 	for (gear = UFS_HS_G1; gear <= UFS_HS_GEAR_MAX; gear++) {
120703e5d38eSCan Guo 		params = &hba->tx_eq_params[gear - 1];
120803e5d38eSCan Guo 
120903e5d38eSCan Guo 		if (params->is_valid) {
121003e5d38eSCan Guo 			err = ufshcd_apply_tx_eq_settings(hba, params, gear);
121103e5d38eSCan Guo 			if (err) {
121203e5d38eSCan Guo 				params->is_applied = false;
121303e5d38eSCan Guo 				dev_err(hba->dev, "Failed to apply TX Equalization settings for HS-G%u: %d\n",
121403e5d38eSCan Guo 					gear, err);
121503e5d38eSCan Guo 			} else {
121603e5d38eSCan Guo 				params->is_applied = true;
121703e5d38eSCan Guo 			}
121803e5d38eSCan Guo 		}
121903e5d38eSCan Guo 	}
122003e5d38eSCan Guo }
1221adbabdcfSCan Guo 
1222adbabdcfSCan Guo /**
1223adbabdcfSCan Guo  * ufshcd_retrain_tx_eq - Retrain TX Equalization and apply new settings
1224adbabdcfSCan Guo  * @hba: per-adapter instance
1225adbabdcfSCan Guo  * @gear: target High-Speed (HS) gear for retraining
1226adbabdcfSCan Guo  *
1227adbabdcfSCan Guo  * This function initiates a refresh of the TX Equalization settings for a
1228adbabdcfSCan Guo  * specific HS gear. It scales the clocks to maximum frequency, negotiates the
1229adbabdcfSCan Guo  * power mode with the device, retrains TX EQ and applies new TX EQ settings
1230adbabdcfSCan Guo  * by conducting a Power Mode change.
1231adbabdcfSCan Guo  *
1232adbabdcfSCan Guo  * Returns 0 on success, non-zero error code otherwise
1233adbabdcfSCan Guo  */
1234adbabdcfSCan Guo int ufshcd_retrain_tx_eq(struct ufs_hba *hba, u32 gear)
1235adbabdcfSCan Guo {
1236adbabdcfSCan Guo 	struct ufs_pa_layer_attr new_pwr_info, final_params = {};
1237adbabdcfSCan Guo 	int ret;
1238adbabdcfSCan Guo 
1239adbabdcfSCan Guo 	if (!ufshcd_is_tx_eq_supported(hba) || !use_adaptive_txeq)
1240adbabdcfSCan Guo 		return -EOPNOTSUPP;
1241adbabdcfSCan Guo 
1242adbabdcfSCan Guo 	if (gear < adaptive_txeq_gear)
1243adbabdcfSCan Guo 		return -ERANGE;
1244adbabdcfSCan Guo 
1245adbabdcfSCan Guo 	ufshcd_hold(hba);
1246adbabdcfSCan Guo 
1247adbabdcfSCan Guo 	ret = ufshcd_pause_command_processing(hba, 1 * USEC_PER_SEC);
1248adbabdcfSCan Guo 	if (ret) {
1249adbabdcfSCan Guo 		ufshcd_release(hba);
1250adbabdcfSCan Guo 		return ret;
1251adbabdcfSCan Guo 	}
1252adbabdcfSCan Guo 
1253adbabdcfSCan Guo 	/* scale up clocks to max frequency before TX EQTR */
1254adbabdcfSCan Guo 	if (ufshcd_is_clkscaling_supported(hba))
1255adbabdcfSCan Guo 		ufshcd_scale_clks(hba, ULONG_MAX, true);
1256adbabdcfSCan Guo 
1257adbabdcfSCan Guo 	new_pwr_info = hba->pwr_info;
1258adbabdcfSCan Guo 	new_pwr_info.gear_tx = gear;
1259adbabdcfSCan Guo 	new_pwr_info.gear_rx = gear;
1260adbabdcfSCan Guo 
1261adbabdcfSCan Guo 	ret = ufshcd_vops_negotiate_pwr_mode(hba, &new_pwr_info, &final_params);
1262adbabdcfSCan Guo 	if (ret)
1263adbabdcfSCan Guo 		memcpy(&final_params, &new_pwr_info, sizeof(final_params));
1264adbabdcfSCan Guo 
1265adbabdcfSCan Guo 	if (final_params.gear_tx != gear) {
1266adbabdcfSCan Guo 		dev_err(hba->dev, "Negotiated Gear (%u) does not match target Gear (%u)\n",
1267adbabdcfSCan Guo 			final_params.gear_tx, gear);
1268adbabdcfSCan Guo 		ret = -EINVAL;
1269adbabdcfSCan Guo 		goto out;
1270adbabdcfSCan Guo 	}
1271adbabdcfSCan Guo 
1272adbabdcfSCan Guo 	ret = ufshcd_config_tx_eq_settings(hba, &final_params, true);
1273adbabdcfSCan Guo 	if (ret) {
1274adbabdcfSCan Guo 		dev_err(hba->dev, "Failed to config TX Equalization for HS-G%u, Rate-%s: %d\n",
1275adbabdcfSCan Guo 			final_params.gear_tx,
1276adbabdcfSCan Guo 			ufs_hs_rate_to_str(final_params.hs_rate), ret);
1277adbabdcfSCan Guo 		goto out;
1278adbabdcfSCan Guo 	}
1279adbabdcfSCan Guo 
1280adbabdcfSCan Guo 	/* Change Power Mode to apply the new TX EQ settings */
1281adbabdcfSCan Guo 	ret = ufshcd_change_power_mode(hba, &final_params,
1282adbabdcfSCan Guo 				       UFSHCD_PMC_POLICY_FORCE);
1283adbabdcfSCan Guo 	if (ret)
1284adbabdcfSCan Guo 		dev_err(hba->dev, "%s: Failed to change Power Mode to HS-G%u, Rate-%s: %d\n",
1285adbabdcfSCan Guo 			__func__, final_params.gear_tx,
1286adbabdcfSCan Guo 			ufs_hs_rate_to_str(final_params.hs_rate), ret);
1287adbabdcfSCan Guo 
1288adbabdcfSCan Guo out:
1289adbabdcfSCan Guo 	ufshcd_resume_command_processing(hba);
1290adbabdcfSCan Guo 	ufshcd_release(hba);
1291adbabdcfSCan Guo 
1292adbabdcfSCan Guo 	return ret;
1293adbabdcfSCan Guo }
1294