[PATCH v8 3/6] phy: fsl-imx8mq-usb: add runtime PM support
Xu Yang <[email protected]> Fri, 31 Jul 2026 16:11:21 +0800
| Newsgroups | dev.linux.lists.imx,org.infradead.lists.linux-arm-kernel,org.infradead.lists.linux-phy,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
From: Xu Yang <[email protected]> Add runtime PM support to ensure the PHY clocks are properly gated when the PHY is not in use, reducing power consumption. Clock management is moved from power_on()/power_off() callbacks into the runtime_resume()/runtime_suspend() callbacks respectively. The PHY subsystem core already holds a runtime PM reference around init() and power_on/off() calls, so no explicit clock handling is needed there. Use devm_clk_get_enabled() and devm_clk_get_optional_enabled() in probe() to keep clocks enabled initially. This ensures the PHY remains functional when CONFIG_PM is disabled, where runtime suspend/resume callbacks are never invoked. In tca_blk_typec_switch_set(), replace the manual clk_prepare_enable() / clk_disable_unprepare() pair with PM_RUNTIME_ACQUIRE_IF_ENABLED() to guard register access against a concurrently suspended PHY. Move devm_regulator_get() before pm_runtime_enable() to avoid having to clean up runtime PM state on regulator acquisition failure. In remove(), call pm_runtime_get_sync() before pm_runtime_disable() to ensure the PHY is resumed and clocks are enabled before the devres teardown disables them. Signed-off-by: Xu Yang <[email protected]> --- Changes in v8: - fix if condition in imx8mq_usb_phy_remove() Changes in v7: - replase PM_RUNTIME_ACQUIRE() with PM_RUNTIME_ACQUIRE_IF_ENABLED() - use non-devm PM runtime callback because it's hard to avoid double clock disable issue when probe fails or remove the driver - improve commit message Changes in v6: - use devm_pm_runtime_enable() to disable runtime PM when probe fails - simply pm_runtime_get_sync/disable/put_noidle() to pm_runtime_resume() Changes in v5: - use non-devm PM runtime callback to correctly enable/disable clocks when unbind the device Changes in v4: - replace guard() with PM_RUNTIME_ACQUIRE() Changes in v3: - new patch --- drivers/phy/freescale/phy-fsl-imx8mq-usb.c | 103 +++++++++++++++++++++-------- 1 file changed, 74 insertions(+), 29 deletions(-) diff --git a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c index 3a5788c609e1..42de2cff4d5f 100644 --- a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c +++ b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c @@ -9,6 +9,7 @@ #include <linux/of.h> #include <linux/phy/phy.h> #include <linux/platform_device.h> +#include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> #include <linux/usb/typec_mux.h> @@ -136,17 +137,15 @@ static int tca_blk_typec_switch_set(struct typec_switch_dev *sw, { struct imx8mq_usb_phy *imx_phy = typec_switch_get_drvdata(sw); struct tca_blk *tca = imx_phy->tca; - int ret; if (tca->orientation == orientation) return 0; - ret = clk_prepare_enable(imx_phy->clk); - if (ret) - return ret; + PM_RUNTIME_ACQUIRE_IF_ENABLED(&imx_phy->phy->dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; tca_blk_orientation_set(tca, orientation); - clk_disable_unprepare(imx_phy->clk); return 0; } @@ -620,16 +619,6 @@ static int imx8mq_phy_power_on(struct phy *phy) if (ret) return ret; - ret = clk_prepare_enable(imx_phy->clk); - if (ret) - return ret; - - ret = clk_prepare_enable(imx_phy->alt_clk); - if (ret) { - clk_disable_unprepare(imx_phy->clk); - return ret; - } - /* Disable rx term override */ value = readl(imx_phy->base + PHY_CTRL6); value &= ~PHY_CTRL6_RXTERM_OVERRIDE_SEL; @@ -648,8 +637,6 @@ static int imx8mq_phy_power_off(struct phy *phy) value |= PHY_CTRL6_RXTERM_OVERRIDE_SEL; writel(value, imx_phy->base + PHY_CTRL6); - clk_disable_unprepare(imx_phy->alt_clk); - clk_disable_unprepare(imx_phy->clk); regulator_disable(imx_phy->vbus); return 0; @@ -686,6 +673,7 @@ static int imx8mq_usb_phy_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct imx8mq_usb_phy *imx_phy; const struct phy_ops *phy_ops; + int ret; imx_phy = devm_kzalloc(dev, sizeof(*imx_phy), GFP_KERNEL); if (!imx_phy) @@ -693,13 +681,13 @@ static int imx8mq_usb_phy_probe(struct platform_device *pdev) platform_set_drvdata(pdev, imx_phy); - imx_phy->clk = devm_clk_get(dev, "phy"); + imx_phy->clk = devm_clk_get_enabled(dev, "phy"); if (IS_ERR(imx_phy->clk)) { dev_err(dev, "failed to get imx8mq usb phy clock\n"); return PTR_ERR(imx_phy->clk); } - imx_phy->alt_clk = devm_clk_get_optional(dev, "alt"); + imx_phy->alt_clk = devm_clk_get_optional_enabled(dev, "alt"); if (IS_ERR(imx_phy->alt_clk)) return dev_err_probe(dev, PTR_ERR(imx_phy->alt_clk), "Failed to get alt clk\n"); @@ -708,44 +696,101 @@ static int imx8mq_usb_phy_probe(struct platform_device *pdev) if (IS_ERR(imx_phy->base)) return PTR_ERR(imx_phy->base); + imx_phy->vbus = devm_regulator_get(dev, "vbus"); + if (IS_ERR(imx_phy->vbus)) + return dev_err_probe(dev, PTR_ERR(imx_phy->vbus), "failed to get vbus\n"); + phy_ops = of_device_get_match_data(dev); if (!phy_ops) return -EINVAL; - imx_phy->phy = devm_phy_create(dev, NULL, phy_ops); - if (IS_ERR(imx_phy->phy)) - return PTR_ERR(imx_phy->phy); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); - imx_phy->vbus = devm_regulator_get(dev, "vbus"); - if (IS_ERR(imx_phy->vbus)) - return dev_err_probe(dev, PTR_ERR(imx_phy->vbus), "failed to get vbus\n"); + imx_phy->phy = devm_phy_create(dev, NULL, phy_ops); + if (IS_ERR(imx_phy->phy)) { + ret = dev_err_probe(dev, PTR_ERR(imx_phy->phy), + "failed to create PHY\n"); + goto disable_rpm; + } phy_set_drvdata(imx_phy->phy, imx_phy); imx_phy->tca = imx95_usb_phy_get_tca(pdev, imx_phy); - if (IS_ERR(imx_phy->tca)) - return dev_err_probe(dev, PTR_ERR(imx_phy->tca), - "failed to get tca\n"); + if (IS_ERR(imx_phy->tca)) { + ret = dev_err_probe(dev, PTR_ERR(imx_phy->tca), + "failed to get tca\n"); + goto disable_rpm; + } imx8m_get_phy_tuning_data(imx_phy); device_set_wakeup_capable(dev, true); phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + ret = dev_err_probe(dev, PTR_ERR(phy_provider), + "failed to register phy provider\n"); + goto disable_rpm; + } - return PTR_ERR_OR_ZERO(phy_provider); + return 0; + +disable_rpm: + pm_runtime_disable(dev); + return ret; } static void imx8mq_usb_phy_remove(struct platform_device *pdev) { + struct device *dev = &pdev->dev; + int ret; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) + dev_warn(dev, "failed to resume on remove: %d\n", ret); + + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); +} + +static int imx8mq_usb_phy_runtime_suspend(struct device *dev) +{ + struct imx8mq_usb_phy *imx_phy = dev_get_drvdata(dev); + + clk_disable_unprepare(imx_phy->alt_clk); + clk_disable_unprepare(imx_phy->clk); + + return 0; +} + +static int imx8mq_usb_phy_runtime_resume(struct device *dev) +{ + struct imx8mq_usb_phy *imx_phy = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(imx_phy->clk); + if (ret) + return ret; + ret = clk_prepare_enable(imx_phy->alt_clk); + if (ret) { + clk_disable_unprepare(imx_phy->clk); + return ret; + } + + return 0; } +static DEFINE_RUNTIME_DEV_PM_OPS(imx8mq_usb_phy_pm_ops, imx8mq_usb_phy_runtime_suspend, + imx8mq_usb_phy_runtime_resume, NULL); + static struct platform_driver imx8mq_usb_phy_driver = { .probe = imx8mq_usb_phy_probe, .remove = imx8mq_usb_phy_remove, .driver = { .name = "imx8mq-usb-phy", .of_match_table = imx8mq_usb_phy_of_match, + .pm = pm_ptr(&imx8mq_usb_phy_pm_ops), .suppress_bind_attrs = true, } }; -- 2.34.1