[PATCH v4 3/3] usb: dwc3: xilinx: Add support to program MMI USB TX deemphasis

From: Radhey Shyam Pandey

Date: Tue Oct 20 2026 - 07:38:20 EST


Introduces support for programming the 18-bit TX deemphasis value that
drives the pipe_TxDeemph signal, as defined in the PIPE4 specification.

The value is supplied by the glue driver through struct dwc3_properties,
which the flat model provides for core properties the glue knows at probe
time, and is programmed into LCSR_TX_DEEMPH during SoC bus configuration.

The configured value is recommended by Synopsys and is intended for
standard (non-compliance) operation. These Gen2 equalization settings
have been validated through both internal and external compliance
testing. By applying this setting, the stability of USB 3.2 enumeration
is improved and now SuperSpeedPlus devices are consistently recognized as
USB 3.2 Gen2 by the MMI USB Host controller.

Signed-off-by: Radhey Shyam Pandey <radhey.shyam.pandey@xxxxxxx>
---
Changes for v4:
- Pass the value through struct dwc3_properties instead of the
snps,lcsr-tx-deemph software node property; the flat model hands core
properties to dwc3_core_probe() directly.
- Drop the per-port DWC3_LCSR_TX_DEEMPH(n) macro and the loop over
num_usb3_ports in favour of a plain offset, as MMI has a single USB3
port.
- Only program the register on DWC31 and DWC32, where the LCSR space
exists.
- Add the UNSPECIFIED sentinel to DWC3_DEFAULT_PROPERTIES and spell out
both sentinels in fsl_ls1028_dwc3 to avoid -Woverride-init.

Changes for v3:
- Define DWC3_LCSR_TX_DEEMPH(n) and multiport handling. Thinh: Please
review on this offset calculation as MMI USB IP support single usb3
port.
- Default set the tx_deemph to the DWC3_LCSR_TX_DEEMPH_UNSPECIFIED.

Changes for v2:
- Don't use compatible check for deemphasis programming.
- Rename property "snps,lcsr_tx_deemph" to "snps,lcsr-tx-deemph"
(hyphens per kernel convention).
- Fix double space in LCSR_TX_DEEMPH register comment.
- Add blank line between register offset define and "Bit fields" section.
---
drivers/usb/dwc3/core.c | 12 ++++++++++++
drivers/usb/dwc3/core.h | 8 ++++++++
drivers/usb/dwc3/dwc3-generic-plat.c | 11 ++++++++++-
drivers/usb/dwc3/dwc3-xilinx.c | 4 ++++
drivers/usb/dwc3/glue.h | 4 ++++
5 files changed, 38 insertions(+), 1 deletion(-)

diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index fd5c2cd36c59..4e29056a76d9 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -642,6 +642,17 @@ static void dwc3_config_soc_bus(struct dwc3 *dwc)
reg |= DWC3_GSBUSCFG0_REQINFO(dwc->gsbuscfg0_reqinfo);
dwc3_writel(dwc, DWC3_GSBUSCFG0, reg);
}
+
+ /* The LCSR register space only exists on DWC31 and DWC32 cores. */
+ if (dwc->csr_tx_deemph_field_1 != DWC3_LCSR_TX_DEEMPH_UNSPECIFIED &&
+ (DWC3_IP_IS(DWC31) || DWC3_IP_IS(DWC32))) {
+ u32 reg;
+
+ reg = dwc3_readl(dwc, DWC3_LCSR_TX_DEEMPH);
+ reg &= ~DWC3_LCSR_TX_DEEMPH_MASK(~0);
+ reg |= DWC3_LCSR_TX_DEEMPH_MASK(dwc->csr_tx_deemph_field_1);
+ dwc3_writel(dwc, DWC3_LCSR_TX_DEEMPH, reg);
+ }
}

static int dwc3_core_ulpi_init(struct dwc3 *dwc)
@@ -1695,6 +1706,7 @@ static void dwc3_get_software_properties(struct dwc3 *dwc,
dwc->needs_full_reinit = true;

dwc->gsbuscfg0_reqinfo = DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED;
+ dwc->csr_tx_deemph_field_1 = properties->csr_tx_deemph_field_1;

if (properties->gsbuscfg0_reqinfo !=
DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED) {
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index f3dabb37f806..032f801954fa 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -182,6 +182,8 @@

#define DWC3_LLUCTL(n) (0xd024 + ((n) * 0x80))

+#define DWC3_LCSR_TX_DEEMPH 0xd060
+
/* Bit fields */

/* Global SoC Bus Configuration INCRx Register 0 */
@@ -199,6 +201,10 @@
#define DWC3_GSBUSCFG0_REQINFO(n) (((n) & 0xffff) << 16)
#define DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED 0xffffffff

+/* LCSR_TX_DEEMPH Register: setting TX deemphasis used in normal operation in gen2 */
+#define DWC3_LCSR_TX_DEEMPH_MASK(n) ((n) & 0x3ffff)
+#define DWC3_LCSR_TX_DEEMPH_UNSPECIFIED 0xffffffff
+
/* Global Debug LSP MUX Select */
#define DWC3_GDBGLSPMUX_ENDBC BIT(15) /* Host only */
#define DWC3_GDBGLSPMUX_HOSTSELECT(n) ((n) & 0x3fff)
@@ -1195,6 +1201,7 @@ struct dwc3_glue_ops {
* @gsbuscfg0_reqinfo: store GSBUSCFG0.DATRDREQINFO, DESRDREQINFO,
* DATWRREQINFO, and DESWRREQINFO value passed from
* glue driver.
+ * @csr_tx_deemph_field_1: stores TX deemphasis used in Gen2 operation.
* @wakeup_pending_funcs: Indicates whether any interface has requested for
* function wakeup in bitmap format where bit position
* represents interface_id.
@@ -1447,6 +1454,7 @@ struct dwc3 {
int num_ep_resized;
struct dentry *debug_root;
u32 gsbuscfg0_reqinfo;
+ u32 csr_tx_deemph_field_1;
u32 wakeup_pending_funcs;
};

diff --git a/drivers/usb/dwc3/dwc3-generic-plat.c b/drivers/usb/dwc3/dwc3-generic-plat.c
index ca69ac0eb07c..4c2eba2a0a3f 100644
--- a/drivers/usb/dwc3/dwc3-generic-plat.c
+++ b/drivers/usb/dwc3/dwc3-generic-plat.c
@@ -223,7 +223,16 @@ static const struct dwc3_generic_config spacemit_k1_dwc3 = {
};

static const struct dwc3_generic_config fsl_ls1028_dwc3 = {
- .properties.gsbuscfg0_reqinfo = 0x2222,
+ /*
+ * Spell out every unspecified sentinel. A designated initializer
+ * cannot override a single member of DWC3_DEFAULT_PROPERTIES without
+ * tripping -Woverride-init, and a partially initialized struct would
+ * request a value of zero for the members left out.
+ */
+ .properties = {
+ .gsbuscfg0_reqinfo = 0x2222,
+ .csr_tx_deemph_field_1 = DWC3_LCSR_TX_DEEMPH_UNSPECIFIED,
+ },
};

static const struct dwc3_generic_config eic7700_dwc3 = {
diff --git a/drivers/usb/dwc3/dwc3-xilinx.c b/drivers/usb/dwc3/dwc3-xilinx.c
index 5c7431d711bf..70f28ed16faa 100644
--- a/drivers/usb/dwc3/dwc3-xilinx.c
+++ b/drivers/usb/dwc3/dwc3-xilinx.c
@@ -45,6 +45,8 @@
#define XLNX_USB_FPD_POWER_PRSNT 0x80
#define FPD_POWER_PRSNT_OPTION BIT(0)

+#define XLNX_MMI_USB_TX_DEEMPH_DEF 0x8c45
+
struct dwc3_xlnx;

struct dwc3_xlnx_platdata {
@@ -517,6 +519,8 @@ static int dwc3_xlnx_probe_flat(struct platform_device *pdev,
if (of_dma_is_coherent(dev->of_node))
probe_data.properties.gsbuscfg0_reqinfo = 0xf0f0;

+ probe_data.properties.csr_tx_deemph_field_1 = XLNX_MMI_USB_TX_DEEMPH_DEF;
+
ret = dwc3_core_probe(&probe_data);
if (ret)
goto err_clk_put;
diff --git a/drivers/usb/dwc3/glue.h b/drivers/usb/dwc3/glue.h
index d738e1739ae0..8fa7f0f696a8 100644
--- a/drivers/usb/dwc3/glue.h
+++ b/drivers/usb/dwc3/glue.h
@@ -12,16 +12,20 @@
/**
* dwc3_properties: DWC3 core properties
* @gsbuscfg0_reqinfo: Value to be programmed in the GSBUSCFG0.REQINFO field
+ * @csr_tx_deemph_field_1: Value to be programmed in the LCSR_TX_DEEMPH
+ * register, setting the TX deemphasis used in Gen2
* @needs_full_reinit: indicate the controller may not remain power during system
* pm and need full initialization
*/
struct dwc3_properties {
u32 gsbuscfg0_reqinfo;
+ u32 csr_tx_deemph_field_1;
unsigned needs_full_reinit:1;
};

#define DWC3_DEFAULT_PROPERTIES ((struct dwc3_properties){ \
.gsbuscfg0_reqinfo = DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED, \
+ .csr_tx_deemph_field_1 = DWC3_LCSR_TX_DEEMPH_UNSPECIFIED, \
})

/**
--
2.43.0