Merge patch series "scsi: ufs: Add support for static TX Equalization settings"

Can Guo <can.guo@oss.qualcomm.com> says:

Hi,

This series adds support for board-specific static TX Equalization settings
provided through Device Tree.

This series is based on the earlier TX Equalization enablement work and
persistent storage/retrieval of optimal TX Equalization settings work:
https://lore.kernel.org/all/20260325152154.1604082-1-can.guo@oss.qualcomm.com
https://lore.kernel.org/all/20260424151420.111675-1-can.guo@oss.qualcomm.com

Background
==========

UFS v5.0/UFSHCI v5.0 adds HS-G6 support (46.6 Gbps/lane) via UniPro v3.0
and M-PHY v6.0. These specs define TX Equalization for all High-Speed
Gears (not only HS-G6) to compensate channel loss and improve signal
integrity at high speed.

For HS-G6, M-PHY uses PAM4 1b1b line coding. Pre-Coding may also be
required depending on channel characteristics.

This series adds vendor-neutral DT properties:
- patternProperties: txeq-preshoot-g[1-6], txeq-deemphasis-g[1-6]
- fixed property: tx-precode-enable-g6

All properties use per-lane Host/Device tuples and accept 2 or 4 values
for x1/x2 lane configurations:
- txeq-preshoot-g[1-6]: values 0..7
- txeq-deemphasis-g[1-6]: values 0..7
- tx-precode-enable-g6: values 0/1

These properties carry board-level SI characterization data used as static
TX Equalization settings for each High-Speed Gear.

Example DTS snippet
===================

The following x2-lane example shows the expected DT encoding:

	ufs@1d84000 {
		lanes-per-direction = <2>;

		txeq-preshoot-g6 = <1 2>, <3 4>;
		txeq-deemphasis-g6 = <0 1>, <2 3>;
		tx-precode-enable-g6 = <1 0>, <0 1>;
	};

Relationship with Adaptive TX Equalization
==========================================

Adaptive TX Equalization remains the primary path when enabled.

Static TX Equalization settings from DT are board-specific baseline values,
but when adaptive TX Equalization is used, static settings are not final:
- If valid settings are retrieved from qTxEQGnSettings/wTxEQGnSettingsExt,
  those retrieved settings override static DT settings.
- If retrieval is not available/valid, TX EQTR runs and trained settings
  override static DT settings.

So static DT settings are a fallback and are intended for cases where
adaptive TX Equalization is not enabled/used.

No behavior changes for platforms that do not provide these properties.

What this series adds
=====================

1. dt-bindings:
- Document txeq-preshoot-g[1-6], txeq-deemphasis-g[1-6], and
  tx-precode-enable-g6 in ufs-common.yaml.
- Define tuple encoding for host/device values per lane.
- Add per-property value validation ranges in schema.

2. UFS core/platform integration:
- Parse and validate per-gear DT TX EQ settings during platform init.
- Store parsed values into per-gear TX EQ params and track DT origin using
  the from_dt flag.
- Integrate static-state handling in TX EQ flow so DT-provided entries are
  fed through the adaptive TX Equalization path and then converted to
  normal runtime params.

Link: https://patch.msgid.link/20260616113348.1168248-1-can.guo@oss.qualcomm.com
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
This commit is contained in:
Martin K. Petersen 2026-07-16 22:16:57 -04:00
commit e700a7c94f
4 changed files with 228 additions and 1 deletions

View File

@ -105,6 +105,64 @@ properties:
Restricts the UFS controller to rate-a or rate-b for both TX and
RX directions.
tx-precode-enable-g6:
$ref: /schemas/types.yaml#/definitions/uint32-matrix
minItems: 1
items:
- items:
- description: Host_Lane0 precode
enum: [0, 1]
- description: Device_Lane0 precode
enum: [0, 1]
- items:
- description: Host_Lane1 precode
enum: [0, 1]
- description: Device_Lane1 precode
enum: [0, 1]
description:
Static TX Precode enable values for HS-G6 only.
patternProperties:
"^txeq-preshoot-g[1-6]$":
$ref: /schemas/types.yaml#/definitions/uint32-matrix
minItems: 1
items:
- items:
- description: Host_Lane0 Preshoot value
enum: [0, 1, 2, 3, 4, 5, 6, 7]
- description: Device_Lane0 Preshoot value
enum: [0, 1, 2, 3, 4, 5, 6, 7]
- items:
- description: Host_Lane1 Preshoot value
enum: [0, 1, 2, 3, 4, 5, 6, 7]
- description: Device_Lane1 Preshoot value
enum: [0, 1, 2, 3, 4, 5, 6, 7]
description: |
Static TX Equalization PreShoot settings for High Speed Gears. These
values are programmed to the corresponding UniPro PA layer attribute
PA_TxEQG[1-6]Setting. Each value selects a Pre-Shoot level as defined
by the MIPI M-PHY specification (TX_HS_PreShoot_Setting).
"^txeq-deemphasis-g[1-6]$":
$ref: /schemas/types.yaml#/definitions/uint32-matrix
minItems: 1
items:
- items:
- description: Host_Lane0 DeEmphasis value
enum: [0, 1, 2, 3, 4, 5, 6, 7]
- description: Device_Lane0 DeEmphasis value
enum: [0, 1, 2, 3, 4, 5, 6, 7]
- items:
- description: Host_Lane1 DeEmphasis value
enum: [0, 1, 2, 3, 4, 5, 6, 7]
- description: Device_Lane1 DeEmphasis value
enum: [0, 1, 2, 3, 4, 5, 6, 7]
description: |
Static TX Equalization DeEmphasis settings for High Speed Gears. These
values are programmed to the corresponding UniPro PA layer attribute
PA_TxEQG[1-6]Setting. Each value selects a De-Emphasis level as defined
by the MIPI M-PHY specification (TX_HS_DeEmphasis_Setting).
dependencies:
freq-table-hz: [ clocks ]
operating-points-v2: [ clocks, clock-names ]

View File

@ -1301,7 +1301,13 @@ int ufshcd_config_tx_eq_settings(struct ufs_hba *hba,
}
params = &hba->tx_eq_params[gear - 1];
if (!params->is_valid || force_tx_eqtr) {
/*
* TX EQTR must run for the following cases:
* 1. TX EQ settings are invalid.
* 2. TX EQ settings are from Device Tree.
* 3. TX EQTR procedure is forced.
*/
if (!params->is_valid || params->from_dt || force_tx_eqtr) {
int ret;
ret = ufshcd_tx_eqtr(hba, params, pwr_mode);
@ -1314,6 +1320,8 @@ int ufshcd_config_tx_eq_settings(struct ufs_hba *hba,
/* Mark TX Equalization settings as valid */
params->is_valid = true;
params->is_trained = true;
/* TX EQTR succeeds, clear from_dt flag */
params->from_dt = false;
params->is_applied = false;
}
@ -1499,6 +1507,11 @@ static void ufshcd_extract_tx_eq_settings_attrs(struct ufs_hba *hba, u8 gear)
}
params->is_valid = true;
/*
* Optimal TX EQ settings are retrieved from UFS device attributes,
* clear from_dt flag to avoid TX EQTR procedure.
*/
params->from_dt = false;
}
void ufshcd_retrieve_tx_eq_settings(struct ufs_hba *hba)

View File

@ -210,6 +210,158 @@ static void ufshcd_init_lanes_per_dir(struct ufs_hba *hba)
}
}
static int ufshcd_parse_tx_precode_enable(struct ufs_hba *hba,
bool host_precode_en[UFS_MAX_LANES],
bool device_precode_en[UFS_MAX_LANES])
{
const char *prop_name = "tx-precode-enable-g6";
u32 num_elems = 2 * hba->lanes_per_direction;
const u32 lpd = hba->lanes_per_direction;
u32 precode[UFS_MAX_LANES * 2];
struct device *dev = hba->dev;
int count, err, i;
count = of_property_count_u32_elems(dev->of_node, prop_name);
if (count == -EINVAL || count == -ENODATA)
return 0;
if (count < 0)
return count;
if (count != num_elems) {
dev_err(dev, "Property %s has invalid count (%d), expecting %u\n",
prop_name, count, num_elems);
return -EINVAL;
}
err = of_property_read_u32_array(dev->of_node, prop_name, precode, num_elems);
if (err) {
dev_err(dev, "Failed to read %s property, %d\n", prop_name, err);
return err;
}
for (i = 0; i < num_elems; i++) {
if (precode[i] > 1) {
dev_err(dev, "Invalid TX precode value (%u) in %s property\n",
precode[i], prop_name);
return -EINVAL;
}
}
for (i = 0; i < lpd; i++) {
host_precode_en[i] = precode[i * 2];
device_precode_en[i] = precode[i * 2 + 1];
}
return 0;
}
static int ufshcd_parse_tx_eq_value_array(struct ufs_hba *hba,
const char *prop_name,
const u32 max_value,
u32 values[UFS_MAX_LANES * 2])
{
u32 num_elems = 2 * hba->lanes_per_direction;
struct device *dev = hba->dev;
int count, err, i;
count = of_property_count_u32_elems(dev->of_node, prop_name);
if (count < 0)
return count;
if (count != num_elems) {
dev_err(dev, "Property %s has invalid count (%d), expecting %u\n",
prop_name, count, num_elems);
return -EINVAL;
}
err = of_property_read_u32_array(dev->of_node, prop_name, values, num_elems);
if (err) {
dev_err(dev, "Failed to read %s property, %d\n", prop_name, err);
return err;
}
for (i = 0; i < num_elems; i++) {
if (values[i] >= max_value) {
dev_err(dev, "Invalid TX EQ value (%u) in %s property\n",
values[i], prop_name);
return -EINVAL;
}
}
return 0;
}
/**
* ufshcd_parse_tx_eq_settings_for_gear - Parse static TX EQ DT settings for one gear
* @hba: per adapter instance
* @gear: target HS gear
*
* Reads the txeq-preshoot-gN, txeq-deemphasis-gN, and (for G6)
* tx-precode-enable-g6 device-tree properties.
* If all present values are valid, stores them as static TX Equalization
* settings for the given gear.
*/
static void ufshcd_parse_tx_eq_settings_for_gear(struct ufs_hba *hba, int gear)
{
bool device_precode_en[UFS_MAX_LANES] = { false };
bool host_precode_en[UFS_MAX_LANES] = { false };
const u32 lpd = hba->lanes_per_direction;
struct ufshcd_tx_eq_params *params;
u32 deemphasis[UFS_MAX_LANES * 2];
u32 preshoot[UFS_MAX_LANES * 2];
char prop_name[MAX_PROP_SIZE];
int err, lane;
snprintf(prop_name, MAX_PROP_SIZE, "txeq-preshoot-g%d", gear);
err = ufshcd_parse_tx_eq_value_array(hba, prop_name, TX_HS_NUM_PRESHOOT, preshoot);
if (err)
return;
snprintf(prop_name, MAX_PROP_SIZE, "txeq-deemphasis-g%d", gear);
err = ufshcd_parse_tx_eq_value_array(hba, prop_name, TX_HS_NUM_DEEMPHASIS, deemphasis);
if (err)
return;
if (gear == UFS_HS_G6) {
err = ufshcd_parse_tx_precode_enable(hba, host_precode_en, device_precode_en);
if (err)
return;
}
params = &hba->tx_eq_params[gear - 1];
for (lane = 0; lane < lpd; lane++) {
params->host[lane].preshoot = preshoot[lane * 2];
params->host[lane].deemphasis = deemphasis[lane * 2];
params->host[lane].precode_en = host_precode_en[lane];
params->device[lane].preshoot = preshoot[lane * 2 + 1];
params->device[lane].deemphasis = deemphasis[lane * 2 + 1];
params->device[lane].precode_en = device_precode_en[lane];
}
params->is_valid = true;
params->from_dt = true;
}
static void ufshcd_parse_static_tx_eq_settings(struct ufs_hba *hba)
{
const u32 lpd = hba->lanes_per_direction;
int gear;
if (!lpd)
return;
if (lpd > UFS_MAX_LANES) {
dev_warn(hba->dev, "lanes_per_direction (%u) exceeds UFS_MAX_LANES (%u)\n",
lpd, UFS_MAX_LANES);
return;
}
for (gear = UFS_HS_G1; gear <= UFS_HS_GEAR_MAX; gear++)
ufshcd_parse_tx_eq_settings_for_gear(hba, gear);
}
/**
* ufshcd_parse_clock_min_max_freq - Parse MIN and MAX clocks freq
* @hba: per adapter instance
@ -528,6 +680,8 @@ int ufshcd_pltfrm_init(struct platform_device *pdev,
ufshcd_init_lanes_per_dir(hba);
ufshcd_parse_static_tx_eq_settings(hba);
err = ufshcd_parse_operating_points(hba);
if (err) {
dev_err(dev, "%s: OPP parse failed %d\n", __func__, err);

View File

@ -359,6 +359,7 @@ struct ufshcd_tx_eqtr_record {
* @is_valid: True if parameter contains valid TX Equalization settings
* @is_applied: True if settings have been applied to UniPro of both sides
* @is_trained: True if parameters obtained from TX EQTR procedure
* @from_dt: True if settings are from Device Tree
*/
struct ufshcd_tx_eq_params {
struct ufshcd_tx_eq_settings host[UFS_MAX_LANES];
@ -367,6 +368,7 @@ struct ufshcd_tx_eq_params {
bool is_valid;
bool is_applied;
bool is_trained;
bool from_dt;
};
/**