mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 12:44:03 +02:00
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:
commit
e700a7c94f
|
|
@ -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 ]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user