aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorMartin K. Petersen <martin.petersen@oracle.com>2026-07-16 22:16:57 -0400
committerMartin K. Petersen <martin.petersen@oracle.com>2026-07-16 22:16:57 -0400
commite700a7c94fdc970b0cf112ae12119ee5f3d1c1bc (patch)
treef0739309b38b8488be2ba9ef49ea7ec3217eb107
parentscsi: ufs: sysfs: Add HS_GEAR6 string in power_info/gear sysfs output (diff)
parentscsi: ufs: core: Add support for static TX Equalization settings (diff)
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>
-rw-r--r--Documentation/devicetree/bindings/ufs/ufs-common.yaml58
-rw-r--r--drivers/ufs/core/ufs-txeq.c15
-rw-r--r--drivers/ufs/host/ufshcd-pltfrm.c154
-rw-r--r--include/ufs/ufshcd.h2
4 files changed, 228 insertions, 1 deletions
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 ]
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;
};
/**