Re: [PATCH v3 3/5] phy: qualcomm: qmp-combo: Add preliminary USB4 support

Manivannan Sadhasivam <[email protected]>
Newsgroups org.kernel.vger.linux-arm-msm,org.infradead.lists.linux-phy,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel
Message-ID <dgvdvug7zeoommfh6iszybwzpcm6f5vz3pwkpamtezjxenx4ua@ew5xgoc5reqd>
On Thu, Jul 30, 2026 at 04:19:15PM +0200, Konrad Dybcio wrote:
> From: Konrad Dybcio <[email protected]>
> 
> Some Combo PHYs (so far only on SC8280XP, X1E80100 and Glymur), come in
> a flavor called USB43DP, which as the name implies, features USB4, USB3
> and DP signal processing capabilities. In that architecture, USB3 and
> USB4 PHYs share the same USB_PLL while featuring separate logic spaces.
> The DP part is roughly the same as on the instances without USB4.
> 
> The USB4 and USB3/DP operation modes of the PHY are mutually exclusive.
> Only one USB protocol (and flavor of pipe clock) can be active at a
> given moment (not to be confused with USB3 not being able to be
> tunneled as USB4 packets - that of course remains possible).
> The DP PLL is still used for clocking tunneled DP links. It may be
> turned off to save power when no tunnels are active, but that's left as
> a TODO item for now.
> 
> Due to the nature of USB4, the Type-C handling happens entirely inside
> the Host Router, and as such the QMPPHY's mux_set() function is
> nullified for the period when USB4 PHY remains active. This is strictly
> necessary, as the Host Router driver is going to excercise manual
> control over the USB4 PHY's power state, which is needed by the suspend
> and resume flows. Failure to control that synchronously with other
> parts of the code results in a SoC crash by unlocked access.
> 
> Because of that, a new struct phy is spawned to expose the USB4 mode,
> along with a .set_mode callback to allow toggling between USB4 and TBT3
> submodes.
> 
> Thunderbolt 3, having a number of differences vs USB4, requires a
> couple specific overrides, pertaining to electrical characteristics,
> which are easily accommodated for.
> 
> Signed-off-by: Konrad Dybcio <[email protected]>
> ---
>  drivers/phy/qualcomm/phy-qcom-qmp-combo.c | 392 ++++++++++++++++++++++++------
>  1 file changed, 322 insertions(+), 70 deletions(-)
> 
> diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c
> index cdcfad2e86b1..b80eac9d7140 100644
> --- a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c
> +++ b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c
> @@ -22,6 +22,7 @@
>  #include <linux/usb/typec.h>
>  #include <linux/usb/typec_dp.h>
>  #include <linux/usb/typec_mux.h>
> +#include <linux/usb/typec_tbt.h>
>  
>  #include <drm/bridge/aux-bridge.h>
>  
> @@ -61,10 +62,14 @@
>  #define SW_USB3PHY_RESET			BIT(2)
>  /* mux to select USB3 PHY reset control, 0:HW control, 1: software reset */
>  #define SW_USB3PHY_RESET_MUX			BIT(3)
> +#define SW_USB4PHY_RESET			BIT(4)
> +#define SW_USB4PHY_RESET_MUX			BIT(5)
>  
>  /* QPHY_V3_DP_COM_PHY_MODE_CTRL register bits */
>  #define USB3_MODE				BIT(0) /* enables USB3 mode */
>  #define DP_MODE					BIT(1) /* enables DP mode */
> +#define USB4_MODE				BIT(2) /* mutually exclusive with the above */
> +#define DP_TUNNELING_CLOCK_GEN_EN		BIT(3)
>  
>  /* QPHY_V3_DP_COM_TYPEC_CTRL register bits */
>  #define SW_PORTSELECT_VAL			BIT(0)
> @@ -77,6 +82,8 @@ enum qmpphy_mode {
>  	QMPPHY_MODE_USB3DP = 0,
>  	QMPPHY_MODE_DP_ONLY,
>  	QMPPHY_MODE_USB3_ONLY,
> +	/* USB4 QMPPHY mode refers to both USB4 and TBT3 */
> +	QMPPHY_MODE_USB4,
>  };
>  
>  /* set of registers with offsets different per-PHY */
> @@ -89,6 +96,7 @@ enum qphy_reg_layout {
>  	QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR,
>  	QPHY_PCS_POWER_DOWN_CONTROL,
>  	QPHY_PCS_CLAMP_ENABLE,
> +	QPHY_PCS_USB4_CLAMP_ENABLE,
>  
>  	QPHY_COM_RESETSM_CNTRL,
>  	QPHY_COM_C_READY_STATUS,
> @@ -2198,6 +2206,8 @@ struct qmp_combo_offsets {
>  	u16 dp_txa;
>  	u16 dp_txb;
>  	u16 dp_dp_phy;
> +	u16 usb4_serdes;
> +	u16 usb4_pcs;
>  };
>  
>  struct qmp_phy_cfg {
> @@ -2245,6 +2255,18 @@ struct qmp_phy_cfg {
>  	int (*calibrate_dp_phy)(struct qmp_combo *qmp);
>  	void (*dp_aux_init)(struct qmp_combo *qmp);
>  
> +	/* USB4 specifics */
> +	const struct qmp_phy_init_tbl *usb4_serdes_tbl;
> +	int usb4_serdes_tbl_num;
> +	const struct qmp_phy_init_tbl *usb4_serdes_tb3_ovrd_tbl;
> +	int usb4_serdes_tb3_ovrd_num;
> +	const struct qmp_phy_init_tbl *usb4_tx_tbl;
> +	int usb4_tx_tbl_num;
> +	const struct qmp_phy_init_tbl *usb4_rx_tbl;
> +	int usb4_rx_tbl_num;
> +	const struct qmp_phy_init_tbl *usb4_pcs_tbl;
> +	int usb4_pcs_tbl_num;
> +
>  	/* resets to be requested */
>  	const char * const *reset_list;
>  	int num_resets;
> @@ -2286,8 +2308,12 @@ struct qmp_combo {
>  	void __iomem *dp_tx2;
>  	void __iomem *dp_dp_phy;
>  
> +	void __iomem *usb4_serdes;
> +	void __iomem *usb4_pcs;
> +
>  	struct clk *pipe_clk;
>  	struct clk_bulk_data *clks;
> +	struct clk *p2rr2p_pipe_clk;
>  	int num_clks;
>  	struct reset_control_bulk_data *resets;
>  	struct regulator_bulk_data *vregs;
> @@ -2306,6 +2332,10 @@ struct qmp_combo {
>  	unsigned int dp_init_count;
>  	bool dp_powered_on;
>  
> +	struct phy *usb4_phy;
> +	enum tbt_phy_submode usb4_phy_submode;
> +	unsigned int usb4phy_init_count;

There is already 'usb_init_count', so can this be named as 'usb4_init_count'?

> +
>  	struct clk_fixed_rate pipe_clk_fixed;
>  	struct clk_hw dp_link_hw;
>  	struct clk_hw dp_pixel_hw;

[...]

> +static int qmp_combo_reconfigure_phy(struct qmp_combo *qmp, enum qmpphy_mode new_mode)
> +{
> +	dev_dbg(qmp->dev, "qmp_combo_reconfigure_phy: switching from qmpphy mode %d to %d\n",
> +		qmp->qmpphy_mode, new_mode);
> +

Looks like a downstream debug leftover.

> +	if (qmp->usb_init_count || qmp->usb4phy_init_count)
> +		qmp_combo_usb_power_off(qmp->usb_phy);
> +
> +	if (qmp->dp_init_count)
> +		writel(DP_PHY_PD_CTL_PSR_PWRDN, qmp->dp_dp_phy + QSERDES_DP_PHY_PD_CTL);
> +
> +	qmp_combo_com_exit(qmp, true);
> +

No delay required between power off/on?

> +	qmp->qmpphy_mode = new_mode;
> +
> +	/* Now everything's powered down, power up the right PHYs */
> +	qmp_combo_com_init(qmp, true);
> +
> +	if ((qmp->usb_init_count || qmp->usb4phy_init_count) &&
> +	    new_mode != QMPPHY_MODE_DP_ONLY)
> +		qmp_combo_usb_power_on(qmp->usb_phy);
> +
> +	if ((new_mode == QMPPHY_MODE_USB3DP || new_mode == QMPPHY_MODE_DP_ONLY) &&
> +	    qmp->dp_init_count)
> +		qmp->cfg->dp_aux_init(qmp);
> +
> +	return 0;
> +}
> +
> +static int qmp_combo_usb4_init(struct phy *phy)
> +{
> +	struct qmp_combo *qmp = phy_get_drvdata(phy);
> +	int ret;
> +
> +	guard(mutex)(&qmp->phy_mutex);
> +
> +	if (!qmp->p2rr2p_pipe_clk) {
> +		dev_err(qmp->dev, "missing p2rr2p_pipe clock handle. Update your Device Tree.\n");
> +		return -EINVAL;
> +	}
> +
> +	ret = clk_prepare_enable(qmp->p2rr2p_pipe_clk);
> +	if (ret) {
> +		dev_err(qmp->dev, "p2rr2p_pipe enable failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = qmp_combo_com_init(qmp, false);
> +	if (ret)
> +		return ret;

Missing clk_disable_unprepare() here and below?

> +
> +	/* USB4 mode takes precedence to USB3(+DP), force reconfigure the PHY */
> +	ret = qmp_combo_reconfigure_phy(qmp, QMPPHY_MODE_USB4);
> +	if (ret)

Missing qmp_combo_com_exit().

> +		return ret;
> +
> +	ret = qmp_combo_usb_power_on(phy);
> +	if (ret) {
> +		qmp_combo_com_exit(qmp, false);
> +		return ret;
> +	}
> +
> +	/*
> +	 * Due to the SoC design, the PHY only has a single valid consumer and
> +	 * preventing it from having sole ownership of the PHY's power state
> +	 * makes suspending/resuming the router impossible.
> +	 */
> +	WARN_ON(qmp->usb4phy_init_count++);
> +
> +	return 0;
> +}
> +
> +static int qmp_combo_usb4_exit(struct phy *phy)
> +{
> +	struct qmp_combo *qmp = phy_get_drvdata(phy);
> +	int ret;
> +
> +	guard(mutex)(&qmp->phy_mutex);
> +
> +	ret = qmp_combo_usb_power_off(qmp->usb_phy);
> +	if (ret)
> +		return ret;
> +
> +	ret = qmp_combo_com_exit(qmp, false);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * Mark the USB4 PHY uninitialized and wait for a mux_set event to determine the correct
> +	 * setting. This will always be possible because USB4 requires Type-C.
> +	 */
> +	qmp->usb4phy_init_count--;
> +
> +	clk_disable_unprepare(qmp->p2rr2p_pipe_clk);
> +
> +	return 0;
> +}
> +
> +static int qmp_combo_usb4_set_mode(struct phy *phy, enum phy_mode mode, int submode)
> +{
> +	struct qmp_combo *qmp = phy_get_drvdata(phy);
> +
> +	if (mode != PHY_MODE_TBT)
> +		return -EINVAL;
> +
> +	if (submode == PHY_SUBMODE_USB4 || submode == PHY_SUBMODE_TBT3) {
> +		qmp->usb4_phy_submode = submode;
> +		return 0;
> +	}

Idiomatic style is to error out if the condition is not met and return success
otherwise:

	if (submode != PHY_SUBMODE_USB4 && submode != PHY_SUBMODE_TBT3)
		return -EINVAL;

	qmp->usb4_phy_submode = submode;

	return 0;

- Mani

-- 
மணிவண்ணன் சதாசிவம்
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.