[PATCH 2/2] phy: snps-eusb2: Add support for SA8797P
From: Hiren Gohel
Date: Mon Oct 26 2026 - 05:48:22 EST
The Qualcomm automotive SA8797p 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: Hiren Gohel <hiren.gohel@xxxxxxxxxxxxxxxx>
---
drivers/phy/phy-snps-eusb2.c | 183 +++++++++++++++++++++++++++--------
1 file changed, 140 insertions(+), 43 deletions(-)
diff --git a/drivers/phy/phy-snps-eusb2.c b/drivers/phy/phy-snps-eusb2.c
index af4fa17ac6cb..b246ab52b409 100644
--- a/drivers/phy/phy-snps-eusb2.c
+++ b/drivers/phy/phy-snps-eusb2.c
@@ -9,6 +9,7 @@
#include <linux/iopoll.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>
@@ -157,9 +158,11 @@ struct snps_eusb2_phy_drvdata {
int (*phy_init)(struct phy *p);
const char * const *clk_names;
int num_clks;
+ bool fw_managed;
};
struct snps_eusb2_hsphy {
+ struct device *dev;
struct phy *phy;
void __iomem *base;
@@ -174,8 +177,62 @@ struct snps_eusb2_hsphy {
struct phy *repeater;
const struct snps_eusb2_phy_drvdata *data;
+ struct dev_pm_domain_list *pd_list;
};
+static void snps_eusb2_hsphy_domain_detach(void *data)
+{
+ struct snps_eusb2_hsphy *phy = data;
+
+ dev_pm_domain_detach_list(phy->pd_list);
+}
+
+static int snps_eusb2_hsphy_domain_attach(struct snps_eusb2_hsphy *phy)
+{
+ struct dev_pm_domain_attach_data pd_data = {
+ .pd_flags = PD_FLAG_NO_DEV_LINK,
+ .pd_names = (const char * const []) { "core", "transfer" },
+ .num_pd_names = 2,
+ };
+ struct device *dev = phy->dev;
+ int ret;
+
+ ret = dev_pm_domain_attach_list(dev, &pd_data, &phy->pd_list);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "domain attach failed\n");
+
+ return 0;
+}
+
+/* d3_to_d0 transition by turning on all the suppliers */
+static int snps_eusb2_hsphy_d3_to_d0(struct snps_eusb2_hsphy *phy)
+{
+ int ret;
+
+ if (!phy->pd_list)
+ return 0;
+
+ ret = pm_runtime_resume_and_get(phy->pd_list->pd_devs[0]);
+ if (ret)
+ return ret;
+
+ ret = pm_runtime_resume_and_get(phy->pd_list->pd_devs[1]);
+ if (ret)
+ pm_runtime_put_sync(phy->pd_list->pd_devs[0]);
+
+ return ret;
+}
+
+/* d0_to_d3 transition by turning off all the suppliers */
+static void snps_eusb2_hsphy_d0_to_d3(struct snps_eusb2_hsphy *phy)
+{
+ if (!phy->pd_list)
+ return;
+
+ pm_runtime_put_sync(phy->pd_list->pd_devs[1]);
+ pm_runtime_put_sync(phy->pd_list->pd_devs[0]);
+}
+
static int snps_eusb2_hsphy_set_mode(struct phy *p, enum phy_mode mode, int submode)
{
struct snps_eusb2_hsphy *phy = phy_get_drvdata(p);
@@ -282,7 +339,12 @@ static const struct snps_eusb2_ref_clk qcom_eusb2_ref_clk[] = {
static int qcom_eusb2_ref_clk_init(struct snps_eusb2_hsphy *phy)
{
const struct snps_eusb2_ref_clk *config = NULL;
- unsigned long ref_clk_freq = clk_get_rate(phy->ref_clk);
+ unsigned long ref_clk_freq;
+
+ if (phy->data->fw_managed)
+ return 0;
+
+ ref_clk_freq = clk_get_rate(phy->ref_clk);
for (int i = 0; i < ARRAY_SIZE(qcom_eusb2_ref_clk); i++) {
if (qcom_eusb2_ref_clk[i].freq == ref_clk_freq) {
@@ -452,19 +514,32 @@ static const struct snps_eusb2_phy_drvdata sm8550_snps_eusb2_phy = {
.num_clks = ARRAY_SIZE(qcom_eusb2_hsphy_clock_names),
};
+static const struct snps_eusb2_phy_drvdata sa8797p_snps_eusb2_phy = {
+ .phy_init = qcom_snps_eusb2_hsphy_init,
+ .fw_managed = true,
+};
+
static int snps_eusb2_hsphy_init(struct phy *p)
{
struct snps_eusb2_hsphy *phy = phy_get_drvdata(p);
int ret;
- ret = regulator_bulk_enable(ARRAY_SIZE(phy->vregs), phy->vregs);
- if (ret)
- return ret;
+ if (phy->data->fw_managed) {
+ ret = snps_eusb2_hsphy_d3_to_d0(phy);
+ if (ret < 0) {
+ dev_err(phy->dev, "Failed to transition to d0 state\n");
+ return ret;
+ }
+ } else {
+ ret = regulator_bulk_enable(ARRAY_SIZE(phy->vregs), phy->vregs);
+ if (ret)
+ return ret;
+ }
ret = phy_init(phy->repeater);
if (ret) {
dev_err(&p->dev, "repeater init failed: %d\n", ret);
- goto disable_vreg;
+ goto power_off;
}
ret = clk_bulk_prepare_enable(phy->data->num_clks, phy->clks);
@@ -497,8 +572,11 @@ static int snps_eusb2_hsphy_init(struct phy *p)
clk_bulk_disable_unprepare(phy->data->num_clks, phy->clks);
exit_repeater:
phy_exit(phy->repeater);
-disable_vreg:
- regulator_bulk_disable(ARRAY_SIZE(phy->vregs), phy->vregs);
+power_off:
+ if (phy->data->fw_managed)
+ snps_eusb2_hsphy_d0_to_d3(phy);
+ else
+ regulator_bulk_disable(ARRAY_SIZE(phy->vregs), phy->vregs);
return ret;
}
@@ -509,10 +587,14 @@ static int snps_eusb2_hsphy_exit(struct phy *p)
clk_bulk_disable_unprepare(phy->data->num_clks, phy->clks);
- regulator_bulk_disable(ARRAY_SIZE(phy->vregs), phy->vregs);
+ if (!phy->data->fw_managed)
+ regulator_bulk_disable(ARRAY_SIZE(phy->vregs), phy->vregs);
phy_exit(phy->repeater);
+ if (phy->data->fw_managed)
+ snps_eusb2_hsphy_d0_to_d3(phy);
+
return 0;
}
@@ -537,6 +619,7 @@ static int snps_eusb2_hsphy_probe(struct platform_device *pdev)
if (!phy)
return -ENOMEM;
+ phy->dev = dev;
phy->data = device_get_match_data(dev);
if (!phy->data)
return -EINVAL;
@@ -545,45 +628,56 @@ static int snps_eusb2_hsphy_probe(struct platform_device *pdev)
if (IS_ERR(phy->base))
return PTR_ERR(phy->base);
- phy->phy_reset = devm_reset_control_get_optional_exclusive(dev, NULL);
- if (IS_ERR(phy->phy_reset))
- return PTR_ERR(phy->phy_reset);
-
- phy->clks = devm_kcalloc(dev, phy->data->num_clks, sizeof(*phy->clks),
- GFP_KERNEL);
- if (!phy->clks)
- return -ENOMEM;
-
- for (i = 0; i < phy->data->num_clks; ++i)
- phy->clks[i].id = phy->data->clk_names[i];
-
- ret = devm_clk_bulk_get(dev, phy->data->num_clks, phy->clks);
- if (ret)
- return dev_err_probe(dev, ret,
- "failed to get phy clock(s)\n");
-
- phy->ref_clk = NULL;
- for (i = 0; i < phy->data->num_clks; ++i) {
- if (!strcmp(phy->clks[i].id, "ref")) {
- phy->ref_clk = phy->clks[i].clk;
- break;
+ if (phy->data->fw_managed) {
+ ret = snps_eusb2_hsphy_domain_attach(phy);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev,
+ snps_eusb2_hsphy_domain_detach,
+ phy);
+ if (ret)
+ return ret;
+ } else {
+ phy->phy_reset = devm_reset_control_get_optional_exclusive(dev, NULL);
+ if (IS_ERR(phy->phy_reset))
+ return PTR_ERR(phy->phy_reset);
+
+ phy->clks = devm_kcalloc(dev, phy->data->num_clks,
+ sizeof(*phy->clks), GFP_KERNEL);
+ if (!phy->clks)
+ return -ENOMEM;
+
+ for (i = 0; i < phy->data->num_clks; ++i)
+ phy->clks[i].id = phy->data->clk_names[i];
+
+ ret = devm_clk_bulk_get(dev, phy->data->num_clks, phy->clks);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to get phy clock(s)\n");
+
+ for (i = 0; i < phy->data->num_clks; ++i) {
+ if (!strcmp(phy->clks[i].id, "ref")) {
+ phy->ref_clk = phy->clks[i].clk;
+ break;
+ }
}
- }
- if (IS_ERR_OR_NULL(phy->ref_clk)) {
- ret = phy->ref_clk ? PTR_ERR(phy->ref_clk) : -ENOENT;
- return dev_err_probe(dev, ret,
- "failed to get ref clk\n");
- }
+ if (IS_ERR_OR_NULL(phy->ref_clk)) {
+ ret = phy->ref_clk ? PTR_ERR(phy->ref_clk) : -ENOENT;
+ return dev_err_probe(dev, ret,
+ "failed to get ref clk\n");
+ }
- num = ARRAY_SIZE(phy->vregs);
- for (i = 0; i < num; i++)
- phy->vregs[i].supply = eusb2_hsphy_vreg_names[i];
+ num = ARRAY_SIZE(phy->vregs);
+ for (i = 0; i < num; i++)
+ phy->vregs[i].supply = eusb2_hsphy_vreg_names[i];
- ret = devm_regulator_bulk_get(dev, num, phy->vregs);
- if (ret)
- return dev_err_probe(dev, ret,
- "failed to get regulator supplies\n");
+ ret = devm_regulator_bulk_get(dev, num, phy->vregs);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to get regulator supplies\n");
+ }
phy->repeater = devm_of_phy_optional_get(dev, np, NULL);
if (IS_ERR(phy->repeater))
@@ -608,6 +702,9 @@ static int snps_eusb2_hsphy_probe(struct platform_device *pdev)
static const struct of_device_id snps_eusb2_hsphy_of_match_table[] = {
{
+ .compatible = "qcom,sa8797p-snps-eusb2-phy",
+ .data = &sa8797p_snps_eusb2_phy,
+ }, {
.compatible = "qcom,sm8550-snps-eusb2-phy",
.data = &sm8550_snps_eusb2_phy,
}, {
--
2.34.1