[PATCH v5 2/2] phy: qcom: qmp-usb: Add support for SA8255P

Neil Armstrong neil.armstrong at linaro.org
Thu Sep 24 01:13:54 PDT 2026


On 9/3/26 09:44, Mattijs Korpershoek wrote:
> From: Sriram Dash <quic_sriramd at quicinc.com>
> 
> The Qualcomm automotive SA8255p SoC relies on firmware to configure
> platform resources, including clocks, interconnects and TLMM.
> The driver requests resources operations over SCMI using power protocols.
> 
> The SCMI power protocol enables or disables resources like clocks,
> interconnect paths, and TLMM (GPIOs) using runtime PM framework APIs,
> such as resume/suspend, to control power on/off.
> 
> Power domain "core" controls the GDSC, resets, and TLMM (GPIOs)
> whereas power domain "transfer" controls clocks and interconnects.
> 
> In this variant, two disjoint sets of resources are modeled against
> the power domain suppliers which can be turned on/off to achieve modeled
> device states. Two modeled low power states d1 and d3 are supported.
> 
> transition: d0 <--> d3  :: both core and transfer suppliers
> transition: d0 <--> d1  :: only transfer supplier
> 
> Signed-off-by: Sriram Dash <quic_sriramd at quicinc.com>
> Signed-off-by: Shazad Hussain <quic_shazhuss at quicinc.com>
> Signed-off-by: Mattijs Korpershoek <mkorpershoek at kernel.org>
> ---
>   drivers/phy/qualcomm/phy-qcom-qmp-usb.c | 182 ++++++++++++++++++++++++++++----
>   1 file changed, 160 insertions(+), 22 deletions(-)
> 
> diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-usb.c b/drivers/phy/qualcomm/phy-qcom-qmp-usb.c
> index b0790bcf0bc8..c18680b60803 100644
> --- a/drivers/phy/qualcomm/phy-qcom-qmp-usb.c
> +++ b/drivers/phy/qualcomm/phy-qcom-qmp-usb.c
> @@ -15,6 +15,7 @@
>   #include <linux/of_address.h>
>   #include <linux/phy/phy.h>
>   #include <linux/platform_device.h>
> +#include <linux/pm_domain.h>
>   #include <linux/regulator/consumer.h>
>   #include <linux/reset.h>
>   #include <linux/slab.h>
> @@ -1411,6 +1412,9 @@ struct qmp_phy_cfg {
>   
>   	/* Offset from PCS to PCS_USB region */
>   	unsigned int pcs_usb_offset;
> +
> +	/* true, if the device resources are fw managed */
> +	bool fw_managed;
>   };
>   
>   struct qmp_usb {
> @@ -1439,6 +1443,8 @@ struct qmp_usb {
>   	struct phy *phy;
>   
>   	struct clk_fixed_rate pipe_clk_fixed;
> +
> +	struct dev_pm_domain_list *pd_list;
>   };
>   
>   static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val)
> @@ -1633,6 +1639,25 @@ static const struct qmp_phy_cfg qdu1000_usb3_uniphy_cfg = {
>   	.has_pwrdn_delay	= true,
>   };
>   
> +static const struct qmp_phy_cfg sa8255p_usb3_uniphy_cfg = {
> +	.offsets		= &qmp_usb_offsets_v5,
> +
> +	.serdes_tbl		= sc8280xp_usb3_uniphy_serdes_tbl,
> +	.serdes_tbl_num		= ARRAY_SIZE(sc8280xp_usb3_uniphy_serdes_tbl),
> +	.tx_tbl			= sc8280xp_usb3_uniphy_tx_tbl,
> +	.tx_tbl_num		= ARRAY_SIZE(sc8280xp_usb3_uniphy_tx_tbl),
> +	.rx_tbl			= sc8280xp_usb3_uniphy_rx_tbl,
> +	.rx_tbl_num		= ARRAY_SIZE(sc8280xp_usb3_uniphy_rx_tbl),
> +	.pcs_tbl		= sa8775p_usb3_uniphy_pcs_tbl,
> +	.pcs_tbl_num		= ARRAY_SIZE(sa8775p_usb3_uniphy_pcs_tbl),
> +	.pcs_usb_tbl		= sa8775p_usb3_uniphy_pcs_usb_tbl,
> +	.pcs_usb_tbl_num	= ARRAY_SIZE(sa8775p_usb3_uniphy_pcs_usb_tbl),
> +	.vreg_list		= NULL,
> +	.num_vregs		= 0, /* Regulators are handled by the firmware */
> +	.regs			= qmp_v5_usb3phy_regs_layout,
> +	.fw_managed		= true,
> +};
> +
>   static const struct qmp_phy_cfg sa8775p_usb3_uniphy_cfg = {
>   	.offsets		= &qmp_usb_offsets_v5,
>   
> @@ -1867,6 +1892,77 @@ static const struct qmp_phy_cfg glymur_usb3_uniphy_cfg = {
>   	.regs			= qmp_v7_usb3phy_regs_layout,
>   };
>   
> +static int qmp_usb_domain_attach(struct qmp_usb *qmp)
> +{
> +	struct dev_pm_domain_attach_data pd_data = {
> +		.pd_flags       = PD_FLAG_NO_DEV_LINK,
> +		.pd_names       = (const char *[]) { "core", "transfer" },
> +		.num_pd_names   = 2,
> +	};
> +	struct device *dev = qmp->dev;
> +	int ret;
> +
> +	ret = dev_pm_domain_attach_list(dev, &pd_data, &qmp->pd_list);
> +	if (ret < 0)
> +		return dev_err_probe(dev, ret, "domain attach failed\n");
> +
> +	return 0;
> +}
> +
> +static void qmp_usb_domain_detach(void *data)
> +{
> +	struct qmp_usb *qmp = data;
> +
> +	dev_pm_domain_detach_list(qmp->pd_list);
> +}
> +
> +/* d3_to_d0 transition by turning on all the suppliers */
> +static int qmp_usb_d3_to_d0(struct qmp_usb *qmp)
> +{
> +	int ret;
> +
> +	if (!qmp->pd_list)
> +		return 0;
> +
> +	ret = pm_runtime_resume_and_get(qmp->pd_list->pd_devs[0]);
> +	if (ret)
> +		return ret;
> +
> +	ret = pm_runtime_resume_and_get(qmp->pd_list->pd_devs[1]);
> +	if (ret)
> +		pm_runtime_put_sync(qmp->pd_list->pd_devs[0]);
> +
> +	return ret;
> +}
> +
> +/* d0_to_d3 transition by turning off all the suppliers */
> +static void qmp_usb_d0_to_d3(struct qmp_usb *qmp)
> +{
> +	if (!qmp->pd_list)
> +		return;
> +
> +	pm_runtime_put_sync(qmp->pd_list->pd_devs[1]);
> +	pm_runtime_put_sync(qmp->pd_list->pd_devs[0]);
> +}
> +
> +/* d1_to_d0 transition by turning on the 'transfer' supplier */
> +static int qmp_usb_d1_to_d0(struct qmp_usb *qmp)
> +{
> +	if (!qmp->pd_list)
> +		return 0;
> +
> +	return pm_runtime_resume_and_get(qmp->pd_list->pd_devs[1]);
> +}
> +
> +/* d0_to_d1 transition by turning off the 'transfer' supplier */
> +static void qmp_usb_d0_to_d1(struct qmp_usb *qmp)
> +{
> +	if (!qmp->pd_list)
> +		return;
> +
> +	pm_runtime_put_sync(qmp->pd_list->pd_devs[1]);
> +}
> +
>   static int qmp_usb_serdes_init(struct qmp_usb *qmp)
>   {
>   	const struct qmp_phy_cfg *cfg = qmp->cfg;
> @@ -1886,10 +1982,16 @@ static int qmp_usb_init(struct phy *phy)
>   	void __iomem *pcs = qmp->pcs;
>   	int ret;
>   
> +	ret = qmp_usb_d3_to_d0(qmp);
> +	if (ret < 0) {
> +		dev_err(qmp->dev, "Failed to transition to d0 state\n");
> +		return ret;
> +	}
> +
>   	ret = regulator_bulk_enable(cfg->num_vregs, qmp->vregs);
>   	if (ret) {
>   		dev_err(qmp->dev, "failed to enable regulators, err=%d\n", ret);
> -		return ret;
> +		goto err_transition_to_d3;
>   	}
>   
>   	ret = reset_control_bulk_assert(qmp->num_resets, qmp->resets);
> @@ -1916,6 +2018,8 @@ static int qmp_usb_init(struct phy *phy)
>   	reset_control_bulk_assert(qmp->num_resets, qmp->resets);
>   err_disable_regulators:
>   	regulator_bulk_disable(cfg->num_vregs, qmp->vregs);
> +err_transition_to_d3:
> +	qmp_usb_d0_to_d3(qmp);
>   
>   	return ret;
>   }
> @@ -1931,6 +2035,8 @@ static int qmp_usb_exit(struct phy *phy)
>   
>   	regulator_bulk_disable(cfg->num_vregs, qmp->vregs);
>   
> +	qmp_usb_d0_to_d3(qmp);
> +
>   	return 0;
>   }
>   
> @@ -2119,6 +2225,8 @@ static int __maybe_unused qmp_usb_runtime_suspend(struct device *dev)
>   	clk_disable_unprepare(qmp->pipe_clk);
>   	clk_bulk_disable_unprepare(qmp->num_clks, qmp->clks);
>   
> +	qmp_usb_d0_to_d1(qmp);
> +
>   	return 0;
>   }
>   
> @@ -2134,14 +2242,23 @@ static int __maybe_unused qmp_usb_runtime_resume(struct device *dev)
>   		return 0;
>   	}
>   
> +	ret = qmp_usb_d1_to_d0(qmp);
> +	if (ret < 0) {
> +		dev_err(qmp->dev, "Failed to transition to d0 state\n");
> +		return ret;
> +	}
> +
>   	ret = clk_bulk_prepare_enable(qmp->num_clks, qmp->clks);
> -	if (ret)
> +	if (ret) {
> +		qmp_usb_d0_to_d1(qmp);
>   		return ret;
> +	}
>   
>   	ret = clk_prepare_enable(qmp->pipe_clk);
>   	if (ret) {
>   		dev_err(dev, "pipe_clk enable failed, err=%d\n", ret);
>   		clk_bulk_disable_unprepare(qmp->num_clks, qmp->clks);
> +		qmp_usb_d0_to_d1(qmp);
>   		return ret;
>   	}
>   
> @@ -2321,10 +2438,12 @@ static int qmp_usb_parse_dt_legacy(struct qmp_usb *qmp, struct device_node *np)
>   		qmp->pcs_misc = NULL;
>   	}
>   
> -	qmp->pipe_clk = devm_get_clk_from_child(dev, np, NULL);
> -	if (IS_ERR(qmp->pipe_clk)) {
> -		return dev_err_probe(dev, PTR_ERR(qmp->pipe_clk),
> -				     "failed to get pipe clock\n");
> +	if (!qmp->cfg->fw_managed) {
> +		qmp->pipe_clk = devm_get_clk_from_child(dev, np, NULL);
> +		if (IS_ERR(qmp->pipe_clk)) {
> +			return dev_err_probe(dev, PTR_ERR(qmp->pipe_clk),
> +					     "failed to get pipe clock\n");
> +		}
>   	}
>   
>   	ret = devm_clk_bulk_get_all(qmp->dev, &qmp->clks);
> @@ -2333,10 +2452,12 @@ static int qmp_usb_parse_dt_legacy(struct qmp_usb *qmp, struct device_node *np)
>   
>   	qmp->num_clks = ret;
>   
> -	ret = qmp_usb_reset_init(qmp, usb3phy_legacy_reset_l,
> -				 ARRAY_SIZE(usb3phy_legacy_reset_l));
> -	if (ret)
> -		return ret;
> +	if (!qmp->cfg->fw_managed) {
> +		ret = qmp_usb_reset_init(qmp, usb3phy_legacy_reset_l,
> +					 ARRAY_SIZE(usb3phy_legacy_reset_l));
> +		if (ret)
> +			return ret;
> +	}
>   
>   	return 0;
>   }
> @@ -2370,17 +2491,19 @@ static int qmp_usb_parse_dt(struct qmp_usb *qmp)
>   	if (ret)
>   		return ret;
>   
> -	qmp->pipe_clk = devm_clk_get(dev, "pipe");
> -	if (IS_ERR(qmp->pipe_clk)) {
> -		return dev_err_probe(dev, PTR_ERR(qmp->pipe_clk),
> -				     "failed to get pipe clock\n");
> +	if (!qmp->cfg->fw_managed) {
> +		qmp->pipe_clk = devm_clk_get(dev, "pipe");
> +		if (IS_ERR(qmp->pipe_clk)) {
> +			return dev_err_probe(dev, PTR_ERR(qmp->pipe_clk),
> +					     "failed to get pipe clock\n");
> +		}
> +
> +		ret = qmp_usb_reset_init(qmp, usb3phy_reset_l,
> +					 ARRAY_SIZE(usb3phy_reset_l));
> +		if (ret)
> +			return ret;
>   	}
>   
> -	ret = qmp_usb_reset_init(qmp, usb3phy_reset_l,
> -				 ARRAY_SIZE(usb3phy_reset_l));
> -	if (ret)
> -		return ret;
> -
>   	return 0;
>   }
>   
> @@ -2403,6 +2526,16 @@ static int qmp_usb_probe(struct platform_device *pdev)
>   	if (!qmp->cfg)
>   		return -EINVAL;
>   
> +	if (qmp->cfg->fw_managed) {
> +		ret = qmp_usb_domain_attach(qmp);
> +		if (ret)
> +			return ret;
> +
> +		ret = devm_add_action_or_reset(dev, qmp_usb_domain_detach, qmp);
> +		if (ret)
> +			return ret;
> +	}
> +
>   	ret = devm_regulator_bulk_get_const(dev, qmp->cfg->num_vregs,
>   					qmp->cfg->vreg_list, &qmp->vregs);
>   	if (ret)
> @@ -2435,9 +2568,11 @@ static int qmp_usb_probe(struct platform_device *pdev)
>   	 */
>   	pm_runtime_forbid(dev);
>   
> -	ret = phy_pipe_clk_register(qmp, np);
> -	if (ret)
> -		goto err_pm_put;
> +	if (!qmp->cfg->fw_managed) {
> +		ret = phy_pipe_clk_register(qmp, np);
> +		if (ret)
> +			goto err_pm_put;
> +	}
>   
>   	qmp->phy = devm_phy_create(dev, np, &qmp_usb_phy_ops);
>   	if (IS_ERR(qmp->phy)) {
> @@ -2492,6 +2627,9 @@ static const struct of_device_id qmp_usb_of_match_table[] = {
>   	}, {
>   		.compatible = "qcom,qdu1000-qmp-usb3-uni-phy",
>   		.data = &qdu1000_usb3_uniphy_cfg,
> +	}, {
> +		.compatible = "qcom,sa8255p-qmp-usb3-uni-phy",
> +		.data = &sa8255p_usb3_uniphy_cfg,
>   	}, {
>   		.compatible = "qcom,sa8775p-qmp-usb3-uni-phy",
>   		.data = &sa8775p_usb3_uniphy_cfg,
> 

Reviewed-by: Neil Armstrong <neil.armstrong at linaro.org>

Thanks,
Neil



More information about the linux-phy mailing list