From ce36dbeb2951607304cc5451a33ddcf06880d726 Mon Sep 17 00:00:00 2001 From: Can Guo Date: Tue, 16 Jun 2026 04:33:47 -0700 Subject: [PATCH 1/2] scsi: ufs: dt-bindings: Document static TX Equalization settings properties 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. Document vendor-neutral properties in ufs-common.yaml: - txeq-preshoot-g[1-6] - txeq-deemphasis-g[1-6] - tx-precode-enable-g6 Values are per-lane Host/Device tuples (2 values for x1, 4 values for x2). PreShoot/DeEmphasis range from 0..7, and Precode is 0/1. These are board-specific signal-integrity tuning values. They depend on channel SI/PHY characterization and validation (host PHY, device PHY, package, and board routing), and are determined by HW/PHY designers. Although UFSHCI v5.0 supports TX Equalization Training via UniPro v3.0, which allows host software to determine optimal TX Equalization at runtime, static board-specific TX Equalization settings in the Device Tree are still necessary because: - TX Equalization Training is not supported for HS-G3 and below - TX Equalization Training is disabled on some platforms Reviewed-by: Krzysztof Kozlowski Reviewed-by: Manivannan Sadhasivam Reviewed-by: Peter Wang Signed-off-by: Can Guo Link: https://patch.msgid.link/20260616113348.1168248-2-can.guo@oss.qualcomm.com Signed-off-by: Martin K. Petersen --- .../devicetree/bindings/ufs/ufs-common.yaml | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/Documentation/devicetree/bindings/ufs/ufs-common.yaml b/Documentation/devicetree/bindings/ufs/ufs-common.yaml index ed97f5682509..cc32e1189d50 100644 --- a/Documentation/devicetree/bindings/ufs/ufs-common.yaml +++ b/Documentation/devicetree/bindings/ufs/ufs-common.yaml @@ -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 ] From ecb8d6da1356abc3168e530fc328b3d461107d0f Mon Sep 17 00:00:00 2001 From: Can Guo Date: Tue, 16 Jun 2026 04:33:48 -0700 Subject: [PATCH 2/2] scsi: ufs: core: Add support for static TX Equalization settings Parse board-specific static TX Equalization settings from Device Tree for each HS gear and store them in hba->tx_eq_params. Parse txeq-preshoot-g[1-6] and txeq-deemphasis-g[1-6] as per-lane tuples: , []. For HS-G6, parse optional tx-precode-enable-g6 using the same per-lane Host/Device tuple format. If provided, it must contain values for all active lanes, and each value must be 0 or 1. Introduce from_dt in struct ufshcd_tx_eq_params to track whether TX EQ values came from static Device Tree data. When adaptive TX Equalization is used, these static settings are not final: - If valid settings are retrieved from qTxEQGnSettings/wTxEQGnSettingsExt, those retrieved settings override static Device Tree settings. - If retrieval is not available/valid, TX EQTR runs and trained settings override static Device Tree settings. So static Device Tree settings are a fallback for cases where adaptive TX Equalization is not enabled or not used. Adaptive TX Equalization remains the primary path when enabled. No behavior changes for platforms that do not provide these properties. Reviewed-by: Manivannan Sadhasivam Reviewed-by: Peter Wang Signed-off-by: Can Guo Reviewed-by: Bart Van Assche Link: https://patch.msgid.link/20260616113348.1168248-3-can.guo@oss.qualcomm.com Signed-off-by: Martin K. Petersen --- drivers/ufs/core/ufs-txeq.c | 15 ++- drivers/ufs/host/ufshcd-pltfrm.c | 154 +++++++++++++++++++++++++++++++ include/ufs/ufshcd.h | 2 + 3 files changed, 170 insertions(+), 1 deletion(-) diff --git a/drivers/ufs/core/ufs-txeq.c b/drivers/ufs/core/ufs-txeq.c index aa64f2bf4f1e..f06dc3c3492d 100644 --- a/drivers/ufs/core/ufs-txeq.c +++ b/drivers/ufs/core/ufs-txeq.c @@ -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) diff --git a/drivers/ufs/host/ufshcd-pltfrm.c b/drivers/ufs/host/ufshcd-pltfrm.c index c2dafb583cf5..5ac7afe75934 100644 --- a/drivers/ufs/host/ufshcd-pltfrm.c +++ b/drivers/ufs/host/ufshcd-pltfrm.c @@ -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); diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h index 248d0a5bef40..f96a97fbd314 100644 --- a/include/ufs/ufshcd.h +++ b/include/ufs/ufshcd.h @@ -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; }; /**