Hi Casey,

Thanks for your feedback.

Have addressed all your comments in this respin: https://lore.kernel.org/u-boot/[email protected]/. Request to check if this version is okay.

Please find reply to your comments below.

Thanks,

Balaji

On 11/26/2025 8:16 PM, Casey Connolly wrote:
Hi Balaji,

In general this looks great! I'm very excited to have SuperSpeed up and
running in U-Boot at long last :D

I'll try and give this a spin soon on my boards.

On 24/11/2025 16:54, Balaji Selvanathan wrote:
Add support for the Qualcomm QMP USB3-DP Combo PHY found on
SC7280 and QCM6490 platforms. This driver currently implements
USB3 super-speed functionality of the combo PHY.

The QMP Combo PHY is a dual-mode PHY
that can operate in either USB3 mode or DisplayPort mode. This
initial implementation focuses on USB3 mode to enable Super-Speed
USB support.

This is a port of the upstream Linux files to U-Boot:
https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/phy/qualcomm/phy-qcom-qmp-combo.c?id=3d25d46a255a83f94d7d4d4216f38aafc8e116b0
For future reference, the best format for this is something like:

Taken from Linux commit 3d25d46a255a ("pmdomain: qcom: rpmhpd: Add
rpmhpd support for SM8750")

You can get this with "git top --pretty="%h (\"%s\")" COMMIT_SHA", you
can stick an alias for this in your .gitconfig.

Few more comments below.
Have corrected this.

Enabled and tested the driver on Qualcomm RB3 Gen2 (QCS6490) board.

Signed-off-by: Balaji Selvanathan <[email protected]>
---
v2:
- Added pipe clock disable in qmp_combo_power_off sequence
- Added all required clocks except pipe clock in qmp_combo_phy_clk_l
- All clocks except pipe clock are enabled and disabled seperate from pipe clock
- Added support for regulator power supplies
- Added a minimal xlate to only return the USB3 phy
---
  drivers/phy/qcom/Kconfig                   |   8 +
  drivers/phy/qcom/Makefile                  |   1 +
  drivers/phy/qcom/phy-qcom-qmp-combo.c      | 642 +++++++++++++++++++++
  drivers/phy/qcom/phy-qcom-qmp-common.h     |  62 ++
  drivers/phy/qcom/phy-qcom-qmp-dp-com-v3.h  |  18 +
  drivers/phy/qcom/phy-qcom-qmp-pcs-usb-v4.h |  34 ++
  drivers/phy/qcom/phy-qcom-qmp.h            |  17 +
  7 files changed, 782 insertions(+)
  create mode 100644 drivers/phy/qcom/phy-qcom-qmp-combo.c
  create mode 100644 drivers/phy/qcom/phy-qcom-qmp-common.h
  create mode 100644 drivers/phy/qcom/phy-qcom-qmp-dp-com-v3.h
  create mode 100644 drivers/phy/qcom/phy-qcom-qmp-pcs-usb-v4.h
[snip]

diff --git a/drivers/phy/qcom/phy-qcom-qmp-combo.c 
b/drivers/phy/qcom/phy-qcom-qmp-combo.c
new file mode 100644
index 00000000000..f5596aa5e50
--- /dev/null
+++ b/drivers/phy/qcom/phy-qcom-qmp-combo.c
@@ -0,0 +1,642 @@
[snip]

+/* list of clocks required by phy */
+static const char * const qmp_combo_phy_clk_l[] = {
+       "aux", "com_aux",
+};
+
+/* list of regulators */
+static const char * const qmp_phy_vreg_l[] = {
+       "vdda-phy",
+       "vdda-pll",
On U-Boot it's necessary to use the full DT property name:
"vdda-phy-supply".
Updated to full DT name.

+};

[..]

+static int qmp_combo_power_off(struct phy *phy)
+{
+       struct qmp_combo *qmp = dev_get_priv(phy->dev);
+       void __iomem *com = qmp->com;
+
+       clk_disable(qmp->pipe_clk);
+
+       /* PHY reset */
+       qphy_setbits(qmp->pcs, QPHY_V4_PCS_SW_RESET, SW_RESET);
+
+       /* Stop SerDes and Phy-Coding-Sublayer */
+       qphy_clrbits(qmp->pcs, QPHY_V4_PCS_START_CONTROL,
+                    SERDES_START | PCS_START);
+
+       /* Put PHY into POWER DOWN state: active low */
+       qphy_clrbits(qmp->pcs, QPHY_V4_PCS_POWER_DOWN_CONTROL, SW_PWRDN);
+
+       /* Power down common block */
+       qphy_clrbits(com, QPHY_V3_DP_COM_POWER_DOWN_CTRL, SW_PWRDN);
Should we also turn off the regulators here?
Turning off regulators is happening in qmp_combo_com_exit.

+
+       return qmp_combo_com_exit(qmp);
+}
[...]

+static int qmp_combo_vreg_init(struct qmp_combo *qmp)
+{
+       const struct qmp_phy_cfg *cfg = qmp->cfg;
+       struct udevice *dev = qmp->dev;
+       int num = cfg->num_vregs;
+       int i, ret;
+
+       if (!num)
+               return 0;
+
+       qmp->vregs = devm_kcalloc(dev, num, sizeof(*qmp->vregs), GFP_KERNEL);
+       if (!qmp->vregs)
+               return -ENOMEM;
+
+       for (i = 0; i < num; i++) {
+               ret = device_get_supply_regulator(dev, cfg->vreg_list[i],
+                                                 &qmp->vregs[i]);
+               if (ret) {
+                       dev_dbg(dev, "regulator %s not found (optional)\n",
+                               cfg->vreg_list[i]);
Are these really optional?
You are right. They shouldnt be optional. Made them mandatory now, else probe fails.

+                       qmp->num_vregs = 0;
+                       return 0;
+               }
+       }
+
+       qmp->num_vregs = num;
+       dev_dbg(dev, "found %d regulators\n", num);
Please drop this debug print.
Dropped.

Kind regards,

+       return 0;
+}
+
+static int qmp_combo_parse_dt(struct qmp_combo *qmp)
+{
+       const struct qmp_phy_cfg *cfg = qmp->cfg;
+       const struct qmp_combo_offsets *offs = cfg->offsets;
+       struct udevice *dev = qmp->dev;
+       void __iomem *base;
+       int ret;
+
+       if (!offs)
+               return -EINVAL;
+
+       base = (void __iomem *)dev_read_addr(dev);
+       if (IS_ERR(base))
+               return PTR_ERR(base);
+
+       qmp->com = base + offs->com;
+       qmp->serdes = base + offs->usb3_serdes;
+       qmp->tx = base + offs->txa;
+       qmp->rx = base + offs->rxa;
+       qmp->tx2 = base + offs->txb;
+       qmp->rx2 = base + offs->rxb;
+       qmp->pcs = base + offs->usb3_pcs;
+       qmp->pcs_usb = base + offs->usb3_pcs_usb;
+       qmp->pcs_misc = base + offs->usb3_pcs_misc;
+
+       ret = qmp_combo_clk_init(qmp);
+       if (ret)
+               return ret;
+
+       qmp->pipe_clk = devm_clk_get(dev, "usb3_pipe");
+       if (IS_ERR(qmp->pipe_clk)) {
+               dev_err(dev, "failed to get pipe clock (%ld)\n",
+                       PTR_ERR(qmp->pipe_clk));
+               return ret;
+       }
+
+       ret = qmp_combo_reset_init(qmp);
+       if (ret)
+               return ret;
+
+       ret = qmp_combo_vreg_init(qmp);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static int qmp_combo_probe(struct udevice *dev)
+{
+       struct qmp_combo *qmp = dev_get_priv(dev);
+       int ret;
+
+       qmp->dev = dev;
+       qmp->cfg = (const struct qmp_phy_cfg *)dev_get_driver_data(dev);
+       if (!qmp->cfg) {
+               printf("Failed to get PHY configuration\n");
+               return -EINVAL;
+       }
+
+       ret = qmp_combo_parse_dt(qmp);
+
+       return ret;
+}
+
+static const struct qmp_phy_cfg sc7280_usb3dpphy_cfg = {
+       .offsets                = &qmp_combo_offsets_v3,
+       .serdes_tbl             = sm8150_usb3_serdes_tbl,
+       .serdes_tbl_num         = ARRAY_SIZE(sm8150_usb3_serdes_tbl),
+       .tx_tbl                 = sm8250_usb3_tx_tbl,
+       .tx_tbl_num             = ARRAY_SIZE(sm8250_usb3_tx_tbl),
+       .rx_tbl                 = sm8250_usb3_rx_tbl,
+       .rx_tbl_num             = ARRAY_SIZE(sm8250_usb3_rx_tbl),
+       .pcs_tbl                = sm8250_usb3_pcs_tbl,
+       .pcs_tbl_num            = ARRAY_SIZE(sm8250_usb3_pcs_tbl),
+       .pcs_usb_tbl            = sm8250_usb3_pcs_usb_tbl,
+       .pcs_usb_tbl_num        = ARRAY_SIZE(sm8250_usb3_pcs_usb_tbl),
+       .vreg_list              = qmp_phy_vreg_l,
+       .num_vregs              = ARRAY_SIZE(qmp_phy_vreg_l),
+
+       .has_pwrdn_delay        = true,
+};
+
+static int qmp_combo_xlate(struct phy *phy, struct ofnode_phandle_args *args)
+{
+       if (args->args_count != 1) {
+               debug("Invalid args_count: %d\n", args->args_count);
+               return -EINVAL;
+       }
+
+       /* We only support the USB3 phy at slot 0 */
+       if (args->args[0] == QMP_USB43DP_DP_PHY)
+               return -EINVAL;
+
+       phy->id = QMP_USB43DP_USB3_PHY;
+
+       return 0;
+}
+
+static struct phy_ops qmp_combo_ops = {
+       .init = qmp_combo_power_on,
+       .exit = qmp_combo_power_off,
+       .of_xlate = qmp_combo_xlate,
+};
+
+static const struct udevice_id qmp_combo_ids[] = {
+       {
+               .compatible = "qcom,sc7280-qmp-usb3-dp-phy",
+               .data = (ulong)&sc7280_usb3dpphy_cfg,
+       },
+       { }
+};
+
+U_BOOT_DRIVER(qmp_combo) = {
+       .name = "qcom-qmp-usb3-dp-phy",
+       .id = UCLASS_PHY,
+       .of_match = qmp_combo_ids,
+       .ops = &qmp_combo_ops,
+       .probe = qmp_combo_probe,
+       .priv_auto = sizeof(struct qmp_combo),
+};
diff --git a/drivers/phy/qcom/phy-qcom-qmp-common.h 
b/drivers/phy/qcom/phy-qcom-qmp-common.h
new file mode 100644
index 00000000000..71356fb7dd0
--- /dev/null
+++ b/drivers/phy/qcom/phy-qcom-qmp-common.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef QCOM_PHY_QMP_COMMON_H_
+#define QCOM_PHY_QMP_COMMON_H_
+
+struct qmp_phy_init_tbl {
+       unsigned int offset;
+       unsigned int val;
+       char *name;
+       /*
+        * mask of lanes for which this register is written
+        * for cases when second lane needs different values
+        */
+       u8 lane_mask;
+};
+
+#define QMP_PHY_INIT_CFG(o, v)         \
+       {                               \
+               .offset = o,            \
+               .val = v,               \
+               .name = #o,             \
+               .lane_mask = 0xff,      \
+       }
+
+#define QMP_PHY_INIT_CFG_LANE(o, v, l) \
+       {                               \
+               .offset = o,            \
+               .val = v,               \
+               .name = #o,             \
+               .lane_mask = l,         \
+       }
+
+static inline void qmp_configure_lane(struct udevice *dev, void __iomem *base,
+                                     const struct qmp_phy_init_tbl tbl[],
+                                     int num, u8 lane_mask)
+{
+       int i;
+       const struct qmp_phy_init_tbl *t = tbl;
+
+       if (!t)
+               return;
+
+       for (i = 0; i < num; i++, t++) {
+               if (!(t->lane_mask & lane_mask))
+                       continue;
+
+               dev_dbg(dev, "Writing Reg: %s Offset: 0x%04x Val: 0x%02x\n",
+                       t->name, t->offset, t->val);
+               writel(t->val, base + t->offset);
+       }
+}
+
+static inline void qmp_configure(struct udevice *dev, void __iomem *base,
+                                const struct qmp_phy_init_tbl tbl[], int num)
+{
+       qmp_configure_lane(dev, base, tbl, num, 0xff);
+}
+
+#endif
diff --git a/drivers/phy/qcom/phy-qcom-qmp-dp-com-v3.h 
b/drivers/phy/qcom/phy-qcom-qmp-dp-com-v3.h
new file mode 100644
index 00000000000..396179ef38b
--- /dev/null
+++ b/drivers/phy/qcom/phy-qcom-qmp-dp-com-v3.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef QCOM_PHY_QMP_DP_COM_V3_H_
+#define QCOM_PHY_QMP_DP_COM_V3_H_
+
+/* Only for QMP V3 & V4 PHY - DP COM registers */
+#define QPHY_V3_DP_COM_PHY_MODE_CTRL                   0x00
+#define QPHY_V3_DP_COM_SW_RESET                                0x04
+#define QPHY_V3_DP_COM_POWER_DOWN_CTRL                 0x08
+#define QPHY_V3_DP_COM_SWI_CTRL                                0x0c
+#define QPHY_V3_DP_COM_TYPEC_CTRL                      0x10
+#define QPHY_V3_DP_COM_TYPEC_PWRDN_CTRL                        0x14
+#define QPHY_V3_DP_COM_RESET_OVRD_CTRL                 0x1c
+
+#endif
diff --git a/drivers/phy/qcom/phy-qcom-qmp-pcs-usb-v4.h 
b/drivers/phy/qcom/phy-qcom-qmp-pcs-usb-v4.h
new file mode 100644
index 00000000000..d7fd4ac0fc5
--- /dev/null
+++ b/drivers/phy/qcom/phy-qcom-qmp-pcs-usb-v4.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef QCOM_PHY_QMP_PCS_USB_V4_H_
+#define QCOM_PHY_QMP_PCS_USB_V4_H_
+
+/* Only for QMP V4 PHY - USB3 PCS registers */
+#define QPHY_V4_PCS_USB3_POWER_STATE_CONFIG1           0x000
+#define QPHY_V4_PCS_USB3_AUTONOMOUS_MODE_STATUS                0x004
+#define QPHY_V4_PCS_USB3_AUTONOMOUS_MODE_CTRL          0x008
+#define QPHY_V4_PCS_USB3_AUTONOMOUS_MODE_CTRL2         0x00c
+#define QPHY_V4_PCS_USB3_LFPS_RXTERM_IRQ_SOURCE_STATUS 0x010
+#define QPHY_V4_PCS_USB3_LFPS_RXTERM_IRQ_CLEAR         0x014
+#define QPHY_V4_PCS_USB3_LFPS_DET_HIGH_COUNT_VAL       0x018
+#define QPHY_V4_PCS_USB3_LFPS_TX_ECSTART               0x01c
+#define QPHY_V4_PCS_USB3_LFPS_PER_TIMER_VAL            0x020
+#define QPHY_V4_PCS_USB3_LFPS_TX_END_CNT_U3_START      0x024
+#define QPHY_V4_PCS_USB3_RXEQTRAINING_LOCK_TIME                0x028
+#define QPHY_V4_PCS_USB3_RXEQTRAINING_WAIT_TIME                0x02c
+#define QPHY_V4_PCS_USB3_RXEQTRAINING_CTLE_TIME                0x030
+#define QPHY_V4_PCS_USB3_RXEQTRAINING_WAIT_TIME_S2     0x034
+#define QPHY_V4_PCS_USB3_RXEQTRAINING_DFE_TIME_S2      0x038
+#define QPHY_V4_PCS_USB3_RCVR_DTCT_DLY_U3_L            0x03c
+#define QPHY_V4_PCS_USB3_RCVR_DTCT_DLY_U3_H            0x040
+#define QPHY_V4_PCS_USB3_ARCVR_DTCT_EN_PERIOD          0x044
+#define QPHY_V4_PCS_USB3_ARCVR_DTCT_CM_DLY             0x048
+#define QPHY_V4_PCS_USB3_TXONESZEROS_RUN_LENGTH                0x04c
+#define QPHY_V4_PCS_USB3_ALFPS_DEGLITCH_VAL            0x050
+#define QPHY_V4_PCS_USB3_SIGDET_STARTUP_TIMER_VAL      0x054
+#define QPHY_V4_PCS_USB3_TEST_CONTROL                  0x058
+
+#endif
diff --git a/drivers/phy/qcom/phy-qcom-qmp.h b/drivers/phy/qcom/phy-qcom-qmp.h
index 99f4d447caf..06dac21ddc4 100644
--- a/drivers/phy/qcom/phy-qcom-qmp.h
+++ b/drivers/phy/qcom/phy-qcom-qmp.h
@@ -12,12 +12,17 @@
  #include "phy-qcom-qmp-qserdes-com-v3.h"
  #include "phy-qcom-qmp-qserdes-txrx-v3.h"
+#include "phy-qcom-qmp-qserdes-com-v4.h"
+#include "phy-qcom-qmp-qserdes-txrx-v4.h"
+
  #include "phy-qcom-qmp-qserdes-pll.h"
#include "phy-qcom-qmp-pcs-v2.h" #include "phy-qcom-qmp-pcs-v3.h" +#include "phy-qcom-qmp-pcs-v4.h"
+
  /* Only for QMP V3 & V4 PHY - DP COM registers */
  #define QPHY_V3_DP_COM_PHY_MODE_CTRL                  0x00
  #define QPHY_V3_DP_COM_SW_RESET                               0x04
@@ -112,4 +117,16 @@
  #define QSERDES_V6_DP_PHY_AUX_INTERRUPT_STATUS                0x0e0
  #define QSERDES_V6_DP_PHY_STATUS                      0x0e4
+/* QPHY_SW_RESET bit */
+#define SW_RESET                                BIT(0)
+/* QPHY_POWER_DOWN_CONTROL */
+#define SW_PWRDN                                BIT(0)
+
+/* QPHY_START_CONTROL bits */
+#define SERDES_START                            BIT(0)
+#define PCS_START                               BIT(1)
+
+/* QPHY_PCS_STATUS bit */
+#define PHYSTATUS                               BIT(6)
+
  #endif

Reply via email to