From: Tom Hsieh <[email protected]>

The driver defines PHY_CTRL3_TXVREF_TUNE_MASK,
PHY_CTRL3_TXRISE_TUNE_MASK, PHY_CTRL3_TXPREEMP_TUNE_MASK,
PHY_CTRL3_COMPDISTUNE_MASK, PHY_CTRL3_TX_VBOOST_LEVEL_MASK,
PHY_CTRL4_PCS_TX_DEEMPH_3P5DB_MASK and
PHY_CTRL5_PCS_TX_SWING_FULL_MASK, but uses none of them and reads
none of the fsl,phy-* properties that feed them. The PHY is brought
out of reset and left at its power-on defaults.

Several device trees in dts/upstream already carry those properties:
imx95-19x19-evk.dts, imx95-15x15-evk.dts, imx95-15x15-frdm.dts and
imx943-evk.dts each set five of them. Linux applies them on the same
hardware; U-Boot silently ignores them.

Port the tuning from Linux, keeping its structure so the two stay
comparable. The code is taken from
drivers/phy/freescale/phy-fsl-imx8mq-usb.c:

 - imx8m_phy_tune(), the property parsing and the generic converters
   from commit 63c85ad0cd81 ("phy: fsl-imx8mp-usb: add support for
   phy tuning")

 - the three i.MX95 converters from commit e75d564f6466
   ("phy: fsl-imx8mq-usb: add i.MX95 tuning support")

The i.MX95 field encodings differ from the i.MX8MQ/8MP ones rather
than being a coarser version of them, so they need their own
converters. For fsl,phy-tx-vref-tune-percent = 100:

  generic (i.MX8MQ/8MP): clamp 94..124, (100 - 94) / 2 = 3
  i.MX95:                clamp 90..108, piecewise, 100 - 92 = 8

One deliberate difference from Linux: the i.MX95 comp-dis converter
uses clamp(percent, 94U, 104U) where Linux has clamp(percent, 94, 104).
U-Boot's clamp() in include/linux/kernel.h is the older type-checking
form, and the unsuffixed literals make it warn about comparison of
distinct pointer types. The other three converters already use U
suffixes in Linux.

As in Linux, the tuning is applied from imx8mp_usb_phy_init() only.
A board that sets none of the properties keeps the current
behaviour: every field stays at PHY_TUNE_DEFAULT and
imx8m_phy_tune() returns without writing anything.

This fills a gap against Linux. It is not a fix for any failure
observed in U-Boot.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Tom Hsieh <[email protected]>
---
This is a new feature rather than a fix, so it targets the next merge
window (v2027.01) rather than v2026.10. It applies cleanly on both
master and u-boot/next; neither file it touches has been modified in
next.

Verified on i.MX95 (Advantech AOM-2521), which sets the same five
properties as imx95-19x19-evk.dts. Reading the PHY registers after
`usb start`, with the patch applied:

  PHY_CTRL3 0x4c1f004c = 0xaa14e6eb
  PHY_CTRL4 0x4c1f0050 = 0x04289e09
  PHY_CTRL5 0x4c1f0054 = 0x0000007f

  TX_VBOOST_LEVEL     [31:29] = 5     (1156 uV)
  TXVREF_TUNE         [25:22] = 8     (100 %)
  TXPREEMP_TUNE       [16:15] = 1     (600 uA)
  PCS_TX_DEEMPH_3P5DB [20:15] = 17    (17 dB)
  PCS_TX_SWING_FULL     [6:0] = 127   (100 %)

Each matches what the converters produce for the properties in the
device tree. TXRISE_TUNE and COMPDISTUNE are not set there and keep
their reset values, as intended.

TXVREF_TUNE is the field that shows the i.MX95 conversion is the one
being applied: for fsl,phy-tx-vref-tune-percent = 100 the generic
i.MX8MQ/8MP converter yields 3, the i.MX95 one yields 8.

Test base: the tree also carried a local fix for the DWC3 reference
clock lookup, without which `usb start` does not get far enough on
i.MX95 to be useful. That fix is on the list separately from Stefan
Eichenberger as "usb: dwc3-generic: fix ref clock lookup indexing
wrong clk_bulk array". This patch does not depend on it to build or
to apply.

Tested on i.MX95 only. The i.MX8MQ and i.MX8MP paths are unchanged in
behaviour for a device tree that sets none of these properties, but
were not tested on hardware.

Build-tested with imx8mp_evk_defconfig and aquila-imx95_defconfig.
Thirteen defconfigs enable both CONFIG_PHY_IMX8MQ_USB and
CONFIG_USB_DWC3_GENERIC.
 drivers/phy/phy-imx8mq-usb.c | 283 +++++++++++++++++++++++++++++++++++
 1 file changed, 283 insertions(+)

diff --git a/drivers/phy/phy-imx8mq-usb.c b/drivers/phy/phy-imx8mq-usb.c
index 387db843c50..3308a3a8b58 100644
--- a/drivers/phy/phy-imx8mq-usb.c
+++ b/drivers/phy/phy-imx8mq-usb.c
@@ -118,6 +118,9 @@
 
 #define TCA_INFO                       0xfc
 
+/* Tuning fields left at this value are not programmed. */
+#define PHY_TUNE_DEFAULT               0xffffffff
+
 enum imx8mpq_phy_type {
        IMX8MQ_PHY,
        IMX8MP_PHY,
@@ -130,6 +133,13 @@ struct imx8mq_usb_phy {
        enum imx8mpq_phy_type type;
        struct udevice *vbus_supply;
        void __iomem *tca_base;
+       u32 pcs_tx_swing_full;
+       u32 pcs_tx_deemph_3p5db;
+       u32 tx_vref_tune;
+       u32 tx_rise_tune;
+       u32 tx_preemp_amp_tune;
+       u32 tx_vboost_level;
+       u32 comp_dis_tune;
 };
 
 static const struct udevice_id imx8mq_usb_phy_of_match[] = {
@@ -201,6 +211,275 @@ static int imx8mq_usb_phy_init(struct phy *usb_phy)
        return 0;
 }
 
+/*
+ * Tuning helpers, ported from the Linux driver
+ * (drivers/phy/freescale/phy-fsl-imx8mq-usb.c). Without them the PHY keeps
+ * its reset defaults and the fsl,phy-* properties in the device tree have no
+ * effect.
+ *
+ * i.MX95 encodes three of these fields differently from i.MX8MQ/8MP, so it
+ * needs its own converters -- feeding the generic ones a percentage would
+ * program a different register value, not merely a less precise one.
+ */
+static u32 phy_tx_vref_tune_from_property(u32 percent)
+{
+       percent = clamp(percent, 94U, 124U);
+
+       return DIV_ROUND_CLOSEST(percent - 94U, 2);
+}
+
+static u32 imx95_phy_tx_vref_tune_from_property(u32 percent)
+{
+       percent = clamp(percent, 90U, 108U);
+
+       switch (percent) {
+       case 90 ... 91:
+               percent = 0;
+               break;
+       case 92 ... 96:
+               percent -= 91;
+               break;
+       case 97 ... 104:
+               percent -= 92;
+               break;
+       case 105 ... 108:
+               percent -= 93;
+               break;
+       }
+
+       return percent;
+}
+
+static u32 phy_tx_rise_tune_from_property(u32 percent)
+{
+       switch (percent) {
+       case 0 ... 98:
+               return 3;
+       case 99:
+               return 2;
+       case 100 ... 101:
+               return 1;
+       default:
+               return 0;
+       }
+}
+
+static u32 imx95_phy_tx_rise_tune_from_property(u32 percent)
+{
+       percent = clamp(percent, 90U, 120U);
+
+       switch (percent) {
+       case 90 ... 99:
+               return 3;
+       case 101 ... 115:
+               return 1;
+       case 116 ... 120:
+               return 0;
+       default:
+               return 2;
+       }
+}
+
+static u32 phy_tx_preemp_amp_tune_from_property(u32 microamp)
+{
+       microamp = min(microamp, 1800U);
+
+       return microamp / 600;
+}
+
+static u32 phy_tx_vboost_level_from_property(u32 microvolt)
+{
+       switch (microvolt) {
+       case 1156:
+               return 5;
+       case 844:
+               return 3;
+       default:
+               return 4;
+       }
+}
+
+static u32 phy_comp_dis_tune_from_property(u32 percent)
+{
+       switch (percent) {
+       case 0 ... 92:
+               return 0;
+       case 93 ... 95:
+               return 1;
+       case 96 ... 97:
+               return 2;
+       case 98 ... 102:
+               return 3;
+       case 103 ... 105:
+               return 4;
+       case 106 ... 109:
+               return 5;
+       case 110 ... 113:
+               return 6;
+       default:
+               return 7;
+       }
+}
+
+static u32 imx95_phy_comp_dis_tune_from_property(u32 percent)
+{
+       percent = clamp(percent, 94U, 104U);
+
+       switch (percent) {
+       case 94 ... 95:
+               percent = 0;
+               break;
+       case 96 ... 98:
+               percent -= 95;
+               break;
+       case 99 ... 102:
+               percent -= 96;
+               break;
+       case 103 ... 104:
+               percent -= 97;
+               break;
+       }
+
+       return percent;
+}
+
+static u32 phy_pcs_tx_deemph_3p5db_from_property(u32 decibel)
+{
+       return min(decibel, 36U);
+}
+
+static u32 phy_pcs_tx_swing_full_from_property(u32 percent)
+{
+       percent = min(percent, 100U);
+
+       return (percent * 127) / 100;
+}
+
+static void imx8m_get_phy_tuning_data(struct udevice *dev)
+{
+       struct imx8mq_usb_phy *imx_phy = dev_get_priv(dev);
+       bool is_imx95 = imx_phy->type == IMX95_PHY;
+
+       if (dev_read_u32(dev, "fsl,phy-tx-vref-tune-percent",
+                        &imx_phy->tx_vref_tune))
+               imx_phy->tx_vref_tune = PHY_TUNE_DEFAULT;
+       else if (is_imx95)
+               imx_phy->tx_vref_tune =
+                       
imx95_phy_tx_vref_tune_from_property(imx_phy->tx_vref_tune);
+       else
+               imx_phy->tx_vref_tune =
+                       phy_tx_vref_tune_from_property(imx_phy->tx_vref_tune);
+
+       if (dev_read_u32(dev, "fsl,phy-tx-rise-tune-percent",
+                        &imx_phy->tx_rise_tune))
+               imx_phy->tx_rise_tune = PHY_TUNE_DEFAULT;
+       else if (is_imx95)
+               imx_phy->tx_rise_tune =
+                       
imx95_phy_tx_rise_tune_from_property(imx_phy->tx_rise_tune);
+       else
+               imx_phy->tx_rise_tune =
+                       phy_tx_rise_tune_from_property(imx_phy->tx_rise_tune);
+
+       if (dev_read_u32(dev, "fsl,phy-tx-preemp-amp-tune-microamp",
+                        &imx_phy->tx_preemp_amp_tune))
+               imx_phy->tx_preemp_amp_tune = PHY_TUNE_DEFAULT;
+       else
+               imx_phy->tx_preemp_amp_tune =
+                       
phy_tx_preemp_amp_tune_from_property(imx_phy->tx_preemp_amp_tune);
+
+       if (dev_read_u32(dev, "fsl,phy-tx-vboost-level-microvolt",
+                        &imx_phy->tx_vboost_level))
+               imx_phy->tx_vboost_level = PHY_TUNE_DEFAULT;
+       else
+               imx_phy->tx_vboost_level =
+                       
phy_tx_vboost_level_from_property(imx_phy->tx_vboost_level);
+
+       if (dev_read_u32(dev, "fsl,phy-comp-dis-tune-percent",
+                        &imx_phy->comp_dis_tune))
+               imx_phy->comp_dis_tune = PHY_TUNE_DEFAULT;
+       else if (is_imx95)
+               imx_phy->comp_dis_tune =
+                       
imx95_phy_comp_dis_tune_from_property(imx_phy->comp_dis_tune);
+       else
+               imx_phy->comp_dis_tune =
+                       phy_comp_dis_tune_from_property(imx_phy->comp_dis_tune);
+
+       if (dev_read_u32(dev, "fsl,phy-pcs-tx-deemph-3p5db-attenuation-db",
+                        &imx_phy->pcs_tx_deemph_3p5db))
+               imx_phy->pcs_tx_deemph_3p5db = PHY_TUNE_DEFAULT;
+       else
+               imx_phy->pcs_tx_deemph_3p5db =
+                       
phy_pcs_tx_deemph_3p5db_from_property(imx_phy->pcs_tx_deemph_3p5db);
+
+       if (dev_read_u32(dev, "fsl,phy-pcs-tx-swing-full-percent",
+                        &imx_phy->pcs_tx_swing_full))
+               imx_phy->pcs_tx_swing_full = PHY_TUNE_DEFAULT;
+       else
+               imx_phy->pcs_tx_swing_full =
+                       
phy_pcs_tx_swing_full_from_property(imx_phy->pcs_tx_swing_full);
+}
+
+static void imx8m_phy_tune(struct imx8mq_usb_phy *imx_phy)
+{
+       u32 value;
+
+       if (imx_phy->pcs_tx_deemph_3p5db != PHY_TUNE_DEFAULT) {
+               value = readl(imx_phy->base + PHY_CTRL4);
+               value &= ~PHY_CTRL4_PCS_TX_DEEMPH_3P5DB_MASK;
+               value |= FIELD_PREP(PHY_CTRL4_PCS_TX_DEEMPH_3P5DB_MASK,
+                                   imx_phy->pcs_tx_deemph_3p5db);
+               writel(value, imx_phy->base + PHY_CTRL4);
+       }
+
+       if (imx_phy->pcs_tx_swing_full != PHY_TUNE_DEFAULT) {
+               value = readl(imx_phy->base + PHY_CTRL5);
+               value &= ~PHY_CTRL5_PCS_TX_SWING_FULL_MASK;
+               value |= FIELD_PREP(PHY_CTRL5_PCS_TX_SWING_FULL_MASK,
+                                   imx_phy->pcs_tx_swing_full);
+               writel(value, imx_phy->base + PHY_CTRL5);
+       }
+
+       if ((imx_phy->tx_vref_tune & imx_phy->tx_rise_tune &
+            imx_phy->tx_preemp_amp_tune & imx_phy->comp_dis_tune &
+            imx_phy->tx_vboost_level) == PHY_TUNE_DEFAULT)
+               /* If all are the default values, no need update. */
+               return;
+
+       value = readl(imx_phy->base + PHY_CTRL3);
+
+       if (imx_phy->tx_vref_tune != PHY_TUNE_DEFAULT) {
+               value &= ~PHY_CTRL3_TXVREF_TUNE_MASK;
+               value |= FIELD_PREP(PHY_CTRL3_TXVREF_TUNE_MASK,
+                                   imx_phy->tx_vref_tune);
+       }
+
+       if (imx_phy->tx_rise_tune != PHY_TUNE_DEFAULT) {
+               value &= ~PHY_CTRL3_TXRISE_TUNE_MASK;
+               value |= FIELD_PREP(PHY_CTRL3_TXRISE_TUNE_MASK,
+                                   imx_phy->tx_rise_tune);
+       }
+
+       if (imx_phy->tx_preemp_amp_tune != PHY_TUNE_DEFAULT) {
+               value &= ~PHY_CTRL3_TXPREEMP_TUNE_MASK;
+               value |= FIELD_PREP(PHY_CTRL3_TXPREEMP_TUNE_MASK,
+                                   imx_phy->tx_preemp_amp_tune);
+       }
+
+       if (imx_phy->comp_dis_tune != PHY_TUNE_DEFAULT) {
+               value &= ~PHY_CTRL3_COMPDISTUNE_MASK;
+               value |= FIELD_PREP(PHY_CTRL3_COMPDISTUNE_MASK,
+                                   imx_phy->comp_dis_tune);
+       }
+
+       if (imx_phy->tx_vboost_level != PHY_TUNE_DEFAULT) {
+               value &= ~PHY_CTRL3_TX_VBOOST_LEVEL_MASK;
+               value |= FIELD_PREP(PHY_CTRL3_TX_VBOOST_LEVEL_MASK,
+                                   imx_phy->tx_vboost_level);
+       }
+
+       writel(value, imx_phy->base + PHY_CTRL3);
+}
+
 static int imx8mp_usb_phy_init(struct phy *usb_phy)
 {
        struct udevice *dev = usb_phy->dev;
@@ -237,6 +516,8 @@ static int imx8mp_usb_phy_init(struct phy *usb_phy)
        value &= ~(PHY_CTRL1_RESET | PHY_CTRL1_ATERESET);
        writel(value, imx_phy->base + PHY_CTRL1);
 
+       imx8m_phy_tune(imx_phy);
+
        if (imx_phy->tca_base)
                tca_blk_init(usb_phy);
 
@@ -353,6 +634,8 @@ int imx8mq_usb_phy_probe(struct udevice *dev)
        if (priv->type == IMX95_PHY)
                priv->tca_base = dev_read_addr_index_ptr(dev, 1);
 
+       imx8m_get_phy_tuning_data(dev);
+
        return 0;
 }
 
-- 
2.43.0

Reply via email to