* [PATCH v8 0/6] phy: fsl-imx8mq-usb: few improvements
@ 2026-07-31 8:11 Xu Yang
2026-07-31 8:11 ` [PATCH v8 1/6] phy: fsl-imx8mq-usb: fix typec switch leak on probe error path Xu Yang
` (5 more replies)
0 siblings, 6 replies; 12+ messages in thread
From: Xu Yang @ 2026-07-31 8:11 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Jun Li
Cc: linux-phy, imx, linux-arm-kernel, linux-kernel, Felix Gu, stable,
Xu Yang
Patch #1 fix Type-C switch resource leak if probe() fails.
Patch #3 add runtime PM support to avoid register access issue if the
USB controller enters into runtime suspended state, in this state
accessing USB PHY register may lack some resources. This will also
avoid regulator leak if power_on() fails.
Patch #4 add debug control register regmap
Patch #5 add imx8mq_usb_phy_drvdata drvdata for better extension
Patch #6 correct i.MX8MP USB runtime wakeup issue after introduce runtime
PM support.
---
Changes in v8:
- fix imx8mq_usb_phy_remove() if condition
- add "select REGMAP_MMIO" in Kconfig
- Link to v7: https://patch.msgid.link/20260729-imx8mp-usb-phy-improvement-v7-0-c385ee40ade6@nxp.com
Changes in v7:
- replase PM_RUNTIME_ACQUIRE() with PM_RUNTIME_ACQUIRE_IF_ENABLED()
- use non-devm PM runtime callback finally, because the devm version will still cause the clock
be disabled twice when probe() is failed or driver removed at device suspended state.
- Link to v6: https://patch.msgid.link/20260715-imx8mp-usb-phy-improvement-v6-0-00d95e270e4c@nxp.com
Changes in v6:
- use devm_pm_runtime_enable() to disable runtime PM if probe fails
- add imx8mq_usb_phy_drvdata drvdata and need_genpd_rpm_on flag for better extension
- Link to v5: https://patch.msgid.link/20260630-imx8mp-usb-phy-improvement-v5-0-25d616403844@nxp.com
Changes in v5:
- not use devm runtime callback to avoid clk enable/disable imblance
- Link to v4: https://patch.msgid.link/20260605-imx8mp-usb-phy-improvement-v4-0-b2ddf2f3862c@nxp.com
Changes in v4:
- add Rb tag
- replace guard() with PM_RUNTIME_ACQUIRE()
- Link to v3: https://patch.msgid.link/20260603-imx8mp-usb-phy-improvement-v3-0-7afb8f89abc6@nxp.com
Link to v2:
- https://lore.kernel.org/linux-phy/20260512101046.1498096-1-xu.yang_2@nxp.com/
- https://lore.kernel.org/linux-phy/20260512101212.1498223-1-xu.yang_2@nxp.com/
---
Felix Gu (1):
phy: fsl-imx8mq-usb: fix typec switch leak on probe error path
Xu Yang (5):
phy: fsl-imx8mq-usb: set usb phy to be wakeup capable
phy: fsl-imx8mq-usb: add runtime PM support
phy: fsl-imx8mq-usb: add control register regmap
phy: fsl-imx8mq-usb: introduce per-variant driver data structure
phy: fsl-imx8mq-usb: keep PHY power domain runtime always-on for i.MX8MP
drivers/phy/freescale/Kconfig | 1 +
drivers/phy/freescale/phy-fsl-imx8mq-usb.c | 186 +++++++++++++++++++++--------
2 files changed, 138 insertions(+), 49 deletions(-)
---
base-commit: 3652b49adac266a3d27cb41cdfdb7d8790fc3633
change-id: 20260602-imx8mp-usb-phy-improvement-4272d308d862
Best regards,
--
Xu Yang <xu.yang_2@nxp.com>
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v8 1/6] phy: fsl-imx8mq-usb: fix typec switch leak on probe error path
2026-07-31 8:11 [PATCH v8 0/6] phy: fsl-imx8mq-usb: few improvements Xu Yang
@ 2026-07-31 8:11 ` Xu Yang
2026-07-31 8:19 ` sashiko-bot
2026-07-31 8:11 ` [PATCH v8 2/6] phy: fsl-imx8mq-usb: set usb phy to be wakeup capable Xu Yang
` (4 subsequent siblings)
5 siblings, 1 reply; 12+ messages in thread
From: Xu Yang @ 2026-07-31 8:11 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Jun Li
Cc: linux-phy, imx, linux-arm-kernel, linux-kernel, Felix Gu, stable,
Xu Yang
From: Felix Gu <ustc.gu@gmail.com>
If probe fails after imx95_usb_phy_get_tca() succeeds, the typec
switch leaks because the only cleanup path was in .remove(), which
never runs on probe failure.
Use devm_add_action_or_reset() so the switch is cleaned up on both
probe failure and driver removal. The imx95_usb_phy_put_tca() is no
longer needed, it will be removed in .remove() too.
Fixes: b58f0f86fd61 ("phy: fsl-imx8mq-usb: add tca function driver for imx95")
Cc: stable@vger.kernel.org
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Reviewed-by: Xu Yang <xu.yang_2@nxp.com>
Signed-off-by: Felix Gu <ustc.gu@gmail.com>
Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
---
Changes in v8:
- no changes
Changes in v7:
- no changes
Changes in v6:
- delete .remove() is deleted words in commit message
Changes in v5:
- keep remove() callback as patch #3 needs it
Changes in v4:
- add my signed-off tag
Changes in v3:
- add R-b tag
- cc statble
- drop "sw = data" conversion
---
drivers/phy/freescale/phy-fsl-imx8mq-usb.c | 21 +++++++--------------
1 file changed, 7 insertions(+), 14 deletions(-)
diff --git a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
index b05d80e849a1..9a33c06d6fc3 100644
--- a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
+++ b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
@@ -173,9 +173,9 @@ static struct typec_switch_dev *tca_blk_get_typec_switch(struct platform_device
return sw;
}
-static void tca_blk_put_typec_switch(struct typec_switch_dev *sw)
+static void tca_blk_put_typec_switch(void *data)
{
- typec_switch_unregister(sw);
+ typec_switch_unregister(data);
}
static void tca_blk_orientation_set(struct tca_blk *tca,
@@ -248,6 +248,7 @@ static struct tca_blk *imx95_usb_phy_get_tca(struct platform_device *pdev,
struct device *dev = &pdev->dev;
struct resource *res;
struct tca_blk *tca;
+ int ret;
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (!res)
@@ -266,17 +267,11 @@ static struct tca_blk *imx95_usb_phy_get_tca(struct platform_device *pdev,
tca->orientation = TYPEC_ORIENTATION_NORMAL;
tca->sw = tca_blk_get_typec_switch(pdev, imx_phy);
- return tca;
-}
-
-static void imx95_usb_phy_put_tca(struct imx8mq_usb_phy *imx_phy)
-{
- struct tca_blk *tca = imx_phy->tca;
-
- if (!tca)
- return;
+ ret = devm_add_action_or_reset(&pdev->dev, tca_blk_put_typec_switch, tca->sw);
+ if (ret)
+ return ERR_PTR(ret);
- tca_blk_put_typec_switch(tca->sw);
+ return tca;
}
static u32 phy_tx_vref_tune_from_property(u32 percent)
@@ -741,9 +736,7 @@ static int imx8mq_usb_phy_probe(struct platform_device *pdev)
static void imx8mq_usb_phy_remove(struct platform_device *pdev)
{
- struct imx8mq_usb_phy *imx_phy = platform_get_drvdata(pdev);
- imx95_usb_phy_put_tca(imx_phy);
}
static struct platform_driver imx8mq_usb_phy_driver = {
--
2.34.1
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v8 2/6] phy: fsl-imx8mq-usb: set usb phy to be wakeup capable
2026-07-31 8:11 [PATCH v8 0/6] phy: fsl-imx8mq-usb: few improvements Xu Yang
2026-07-31 8:11 ` [PATCH v8 1/6] phy: fsl-imx8mq-usb: fix typec switch leak on probe error path Xu Yang
@ 2026-07-31 8:11 ` Xu Yang
2026-07-31 8:11 ` [PATCH v8 3/6] phy: fsl-imx8mq-usb: add runtime PM support Xu Yang
` (3 subsequent siblings)
5 siblings, 0 replies; 12+ messages in thread
From: Xu Yang @ 2026-07-31 8:11 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Jun Li
Cc: linux-phy, imx, linux-arm-kernel, linux-kernel, Xu Yang
From: Xu Yang <xu.yang_2@nxp.com>
Set PHY wakeup capable because this PHY supports remote wakeup function.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
---
Changes in v8:
- no changes
Changes in v7:
- no changes
Changes in v6:
- no changes
Changes in v5:
- no changes
Changes in v4:
- add Rb tag
Changes in v3:
- no changes
Changes in v2:
- no changes
---
drivers/phy/freescale/phy-fsl-imx8mq-usb.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
index 9a33c06d6fc3..3a5788c609e1 100644
--- a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
+++ b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
@@ -728,6 +728,7 @@ static int imx8mq_usb_phy_probe(struct platform_device *pdev)
"failed to get tca\n");
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);
--
2.34.1
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v8 3/6] phy: fsl-imx8mq-usb: add runtime PM support
2026-07-31 8:11 [PATCH v8 0/6] phy: fsl-imx8mq-usb: few improvements Xu Yang
2026-07-31 8:11 ` [PATCH v8 1/6] phy: fsl-imx8mq-usb: fix typec switch leak on probe error path Xu Yang
2026-07-31 8:11 ` [PATCH v8 2/6] phy: fsl-imx8mq-usb: set usb phy to be wakeup capable Xu Yang
@ 2026-07-31 8:11 ` Xu Yang
2026-07-31 8:21 ` sashiko-bot
2026-07-31 14:42 ` Frank Li
2026-07-31 8:11 ` [PATCH v8 4/6] phy: fsl-imx8mq-usb: add control register regmap Xu Yang
` (2 subsequent siblings)
5 siblings, 2 replies; 12+ messages in thread
From: Xu Yang @ 2026-07-31 8:11 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Jun Li
Cc: linux-phy, imx, linux-arm-kernel, linux-kernel, Xu Yang
From: Xu Yang <xu.yang_2@nxp.com>
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 <xu.yang_2@nxp.com>
---
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
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v8 4/6] phy: fsl-imx8mq-usb: add control register regmap
2026-07-31 8:11 [PATCH v8 0/6] phy: fsl-imx8mq-usb: few improvements Xu Yang
` (2 preceding siblings ...)
2026-07-31 8:11 ` [PATCH v8 3/6] phy: fsl-imx8mq-usb: add runtime PM support Xu Yang
@ 2026-07-31 8:11 ` Xu Yang
2026-07-31 8:27 ` sashiko-bot
2026-07-31 8:11 ` [PATCH v8 5/6] phy: fsl-imx8mq-usb: introduce per-variant driver data structure Xu Yang
2026-07-31 8:11 ` [PATCH v8 6/6] phy: fsl-imx8mq-usb: keep PHY power domain runtime always-on for i.MX8MP Xu Yang
5 siblings, 1 reply; 12+ messages in thread
From: Xu Yang @ 2026-07-31 8:11 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Jun Li
Cc: linux-phy, imx, linux-arm-kernel, linux-kernel, Xu Yang
From: Xu Yang <xu.yang_2@nxp.com>
The CR port is a simple 16-bit data/address parallel port that is
accessed through 32-bit MMIO registers for on-chip access to the
control registers inside the USB 3.0 femtoPHY. Add control register
regmap and export these registers by debugfs to help PHY's diagnostic.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
---
Changes in v8:
- add select REGMAP_MMIO in Kconfig
Changes in v7:
- no changes
Changes in v6:
- no changes
Changes in v5:
- add Rb tag
Changes in v4:
- improve commit message as Haibo's suggestion
Changes in v3:
- drop Frank's tag because it includes other changes
- new patch
---
drivers/phy/freescale/Kconfig | 1 +
drivers/phy/freescale/phy-fsl-imx8mq-usb.c | 27 ++++++++++++++++++++++++++-
2 files changed, 27 insertions(+), 1 deletion(-)
diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig
index 5bf3864fbe64..7e027755dd8e 100644
--- a/drivers/phy/freescale/Kconfig
+++ b/drivers/phy/freescale/Kconfig
@@ -7,6 +7,7 @@ config PHY_FSL_IMX8MQ_USB
depends on OF && HAS_IOMEM
depends on TYPEC || TYPEC=n
select GENERIC_PHY
+ select REGMAP_MMIO
default ARCH_MXC && ARM64
config PHY_MIXEL_LVDS_PHY
diff --git a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
index 42de2cff4d5f..e03f9dafd69e 100644
--- a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
+++ b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0+
-/* Copyright (c) 2017 NXP. */
+/* Copyright 2017-2026 NXP. */
#include <linux/bitfield.h>
#include <linux/clk.h>
@@ -11,6 +11,7 @@
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
+#include <linux/regmap.h>
#include <linux/usb/typec_mux.h>
#define PHY_CTRL0 0x0
@@ -56,6 +57,8 @@
#define PHY_CTRL6_ALT_CLK_EN BIT(1)
#define PHY_CTRL6_ALT_CLK_SEL BIT(0)
+#define PHY_CRCTL 0x30
+
#define PHY_TUNE_DEFAULT 0xffffffff
#define TCA_CLK_RST 0x00
@@ -119,6 +122,7 @@ struct imx8mq_usb_phy {
void __iomem *base;
struct regulator *vbus;
struct tca_blk *tca;
+ struct regmap *cr_regmap;
u32 pcs_tx_swing_full;
u32 pcs_tx_deemph_3p5db;
u32 tx_vref_tune;
@@ -667,6 +671,14 @@ static const struct of_device_id imx8mq_usb_phy_of_match[] = {
};
MODULE_DEVICE_TABLE(of, imx8mq_usb_phy_of_match);
+static const struct regmap_config imx_cr_regmap_config = {
+ .name = "cr",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0x7,
+};
+
static int imx8mq_usb_phy_probe(struct platform_device *pdev)
{
struct phy_provider *phy_provider;
@@ -696,6 +708,13 @@ static int imx8mq_usb_phy_probe(struct platform_device *pdev)
if (IS_ERR(imx_phy->base))
return PTR_ERR(imx_phy->base);
+ imx_phy->cr_regmap = devm_regmap_init_mmio(dev, imx_phy->base + PHY_CRCTL,
+ &imx_cr_regmap_config);
+ if (IS_ERR(imx_phy->cr_regmap)) {
+ dev_warn(dev, "Fail to init debug register regmap\n");
+ imx_phy->cr_regmap = NULL;
+ }
+
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");
@@ -757,6 +776,9 @@ static int imx8mq_usb_phy_runtime_suspend(struct device *dev)
{
struct imx8mq_usb_phy *imx_phy = dev_get_drvdata(dev);
+ if (imx_phy->cr_regmap)
+ regcache_cache_only(imx_phy->cr_regmap, true);
+
clk_disable_unprepare(imx_phy->alt_clk);
clk_disable_unprepare(imx_phy->clk);
@@ -778,6 +800,9 @@ static int imx8mq_usb_phy_runtime_resume(struct device *dev)
return ret;
}
+ if (imx_phy->cr_regmap)
+ regcache_cache_only(imx_phy->cr_regmap, false);
+
return 0;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v8 5/6] phy: fsl-imx8mq-usb: introduce per-variant driver data structure
2026-07-31 8:11 [PATCH v8 0/6] phy: fsl-imx8mq-usb: few improvements Xu Yang
` (3 preceding siblings ...)
2026-07-31 8:11 ` [PATCH v8 4/6] phy: fsl-imx8mq-usb: add control register regmap Xu Yang
@ 2026-07-31 8:11 ` Xu Yang
2026-07-31 8:11 ` [PATCH v8 6/6] phy: fsl-imx8mq-usb: keep PHY power domain runtime always-on for i.MX8MP Xu Yang
5 siblings, 0 replies; 12+ messages in thread
From: Xu Yang @ 2026-07-31 8:11 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Jun Li
Cc: linux-phy, imx, linux-arm-kernel, linux-kernel, Xu Yang
From: Xu Yang <xu.yang_2@nxp.com>
Replace direct use of phy_ops pointer in of_device_id .data with a
dedicated imx8mq_usb_phy_drvdata structure. This allows per-variant
driver data to be extended in the future without changing the match
table.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
---
Changes in v8:
- no changes
Changes in v7:
- no changes
Changes in v6:
- new patch
---
drivers/phy/freescale/phy-fsl-imx8mq-usb.c | 29 ++++++++++++++++++++++-------
1 file changed, 22 insertions(+), 7 deletions(-)
diff --git a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
index e03f9dafd69e..ba7603a008d8 100644
--- a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
+++ b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
@@ -132,6 +132,9 @@ struct imx8mq_usb_phy {
u32 comp_dis_tune;
};
+struct imx8mq_usb_phy_drvdata {
+ const struct phy_ops *ops;
+};
static void tca_blk_orientation_set(struct tca_blk *tca,
enum typec_orientation orientation);
@@ -660,13 +663,25 @@ static const struct phy_ops imx8mp_usb_phy_ops = {
.owner = THIS_MODULE,
};
+static const struct imx8mq_usb_phy_drvdata imx8mq_usb_phy_data = {
+ .ops = &imx8mq_usb_phy_ops,
+};
+
+static const struct imx8mq_usb_phy_drvdata imx8mp_usb_phy_data = {
+ .ops = &imx8mp_usb_phy_ops,
+};
+
+static const struct imx8mq_usb_phy_drvdata imx95_usb_phy_data = {
+ .ops = &imx8mp_usb_phy_ops,
+};
+
static const struct of_device_id imx8mq_usb_phy_of_match[] = {
{.compatible = "fsl,imx8mq-usb-phy",
- .data = &imx8mq_usb_phy_ops,},
+ .data = &imx8mq_usb_phy_data,},
{.compatible = "fsl,imx8mp-usb-phy",
- .data = &imx8mp_usb_phy_ops,},
+ .data = &imx8mp_usb_phy_data,},
{.compatible = "fsl,imx95-usb-phy",
- .data = &imx8mp_usb_phy_ops,},
+ .data = &imx95_usb_phy_data,},
{ }
};
MODULE_DEVICE_TABLE(of, imx8mq_usb_phy_of_match);
@@ -684,7 +699,7 @@ static int imx8mq_usb_phy_probe(struct platform_device *pdev)
struct phy_provider *phy_provider;
struct device *dev = &pdev->dev;
struct imx8mq_usb_phy *imx_phy;
- const struct phy_ops *phy_ops;
+ const struct imx8mq_usb_phy_drvdata *phy_data;
int ret;
imx_phy = devm_kzalloc(dev, sizeof(*imx_phy), GFP_KERNEL);
@@ -719,14 +734,14 @@ static int imx8mq_usb_phy_probe(struct platform_device *pdev)
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)
+ phy_data = of_device_get_match_data(dev);
+ if (!phy_data)
return -EINVAL;
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
- imx_phy->phy = devm_phy_create(dev, NULL, phy_ops);
+ imx_phy->phy = devm_phy_create(dev, NULL, phy_data->ops);
if (IS_ERR(imx_phy->phy)) {
ret = dev_err_probe(dev, PTR_ERR(imx_phy->phy),
"failed to create PHY\n");
--
2.34.1
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v8 6/6] phy: fsl-imx8mq-usb: keep PHY power domain runtime always-on for i.MX8MP
2026-07-31 8:11 [PATCH v8 0/6] phy: fsl-imx8mq-usb: few improvements Xu Yang
` (4 preceding siblings ...)
2026-07-31 8:11 ` [PATCH v8 5/6] phy: fsl-imx8mq-usb: introduce per-variant driver data structure Xu Yang
@ 2026-07-31 8:11 ` Xu Yang
2026-07-31 14:44 ` Frank Li
5 siblings, 1 reply; 12+ messages in thread
From: Xu Yang @ 2026-07-31 8:11 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Jun Li
Cc: linux-phy, imx, linux-arm-kernel, linux-kernel, Xu Yang
From: Xu Yang <xu.yang_2@nxp.com>
On i.MX8MP, the USB PHY has a dedicated power domain that was previously
never powered off at runtime. With the introduction of runtime PM support,
the power domain will be powered off if the device is runtime suspended,
which breaks USB wakeup functionality.
To preserve wakeup functionality, mark the PHY power domain as runtime
always-on for i.MX8MP platform. To limit the behavior to i.MX8MP, add a
need_genpd_rpm_on to imx8mq_usb_phy_drvdata and set it as true for i.MX8MP.
Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
---
Changes in v8:
- no changes
Changes in v7:
- replace dev_err_probe() with dev_warn()
Changes in v6:
- set need_genpd_rpm_on flag for i.MX8MP and call dev_pm_genpd_rpm_always_on()
if the flag is true
Changes in v5:
- no changes
Changes in v4:
- no changes
Changes in v3:
- new patch
---
drivers/phy/freescale/phy-fsl-imx8mq-usb.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
index ba7603a008d8..8859835541b1 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_domain.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/regmap.h>
@@ -134,6 +135,7 @@ struct imx8mq_usb_phy {
struct imx8mq_usb_phy_drvdata {
const struct phy_ops *ops;
+ bool need_genpd_rpm_on;
};
static void tca_blk_orientation_set(struct tca_blk *tca,
@@ -669,6 +671,7 @@ static const struct imx8mq_usb_phy_drvdata imx8mq_usb_phy_data = {
static const struct imx8mq_usb_phy_drvdata imx8mp_usb_phy_data = {
.ops = &imx8mp_usb_phy_ops,
+ .need_genpd_rpm_on = true,
};
static const struct imx8mq_usb_phy_drvdata imx95_usb_phy_data = {
@@ -738,6 +741,12 @@ static int imx8mq_usb_phy_probe(struct platform_device *pdev)
if (!phy_data)
return -EINVAL;
+ if (phy_data->need_genpd_rpm_on) {
+ ret = dev_pm_genpd_rpm_always_on(dev, true);
+ if (ret && ret != -EOPNOTSUPP)
+ dev_warn(dev, "failed to set genpd rpm always on\n");
+ }
+
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
--
2.34.1
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v8 1/6] phy: fsl-imx8mq-usb: fix typec switch leak on probe error path
2026-07-31 8:11 ` [PATCH v8 1/6] phy: fsl-imx8mq-usb: fix typec switch leak on probe error path Xu Yang
@ 2026-07-31 8:19 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-07-31 8:19 UTC (permalink / raw)
To: Xu Yang; +Cc: Frank.Li, linux-phy, vkoul, imx, neil.armstrong, olteanv
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Pre-existing race condition where `imx_phy->tca` is dereferenced by the Type-C switch's `.set()` callback before it is actually assigned in the probe path.
- [High] Lifetime mismatch between the devres-allocated provider data and the registered typec switch callback, leading to Use-After-Free.
--
--- Patch [1]: [PATCH v8 1/6] phy: fsl-imx8mq-usb: fix typec switch leak on probe error path ---
commit c464d7a13f05d9153fb1007a61d8f4290d1a196f
Author: Felix Gu <ustc.gu@gmail.com>
phy: fsl-imx8mq-usb: fix typec switch leak on probe error path
This commit fixes a typec switch leak when probe fails after
imx95_usb_phy_get_tca() succeeds. It uses devm_add_action_or_reset()
so the switch is cleaned up on both probe failure and driver removal.
> diff --git a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
> index b05d80e849a14..9a33c06d6fc39 100644
> --- a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
> +++ b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
[ ... ]
> @@ -266,17 +267,11 @@ static struct tca_blk *imx95_usb_phy_get_tca(struct platform_device *pdev,
> tca->orientation = TYPEC_ORIENTATION_NORMAL;
> tca->sw = tca_blk_get_typec_switch(pdev, imx_phy);
[Severity: High]
This is a pre-existing issue, but does registering the switch here expose it
to consumers before we are fully initialized?
By registering the switch in tca_blk_get_typec_switch(), the device becomes
immediately discoverable. If a consumer acquires it and invokes the callback:
tca_blk_typec_switch_set()
struct tca_blk *tca = imx_phy->tca;
...
if (tca->orientation == orientation)
Since imx_phy->tca is not assigned until imx8mq_usb_phy_probe() finishes
handling the return from imx95_usb_phy_get_tca(), won't this cause a NULL
pointer dereference if the callback is invoked during this window?
>
> - return tca;
> -}
> -
> -static void imx95_usb_phy_put_tca(struct imx8mq_usb_phy *imx_phy)
> -{
> - struct tca_blk *tca = imx_phy->tca;
> -
> - if (!tca)
> - return;
> + ret = devm_add_action_or_reset(&pdev->dev, tca_blk_put_typec_switch, tca->sw);
[Severity: High]
This is also a pre-existing issue, but does using devres to unregister the
switch introduce a potential use-after-free?
If a consumer driver (like TCPM) obtains a reference to the switch, and the
PHY driver is subsequently unbound or probe fails:
1. devres calls tca_blk_put_typec_switch() to unregister the switch.
2. typec_switch_unregister() removes the device from sysfs and drops the
provider's reference, but the consumer's reference keeps the sw_dev
alive.
3. devres then frees the provider data (imx_phy) which was allocated with
devm_kzalloc().
If the consumer later calls typec_switch_set(), the callback will access
imx_phy->tca, which has already been freed. Could this lead to memory
corruption?
> + if (ret)
> + return ERR_PTR(ret);
>
> - tca_blk_put_typec_switch(tca->sw);
> + return tca;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260731-imx8mp-usb-phy-improvement-v8-0-2ec8d6b3854d@nxp.com?part=1
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v8 3/6] phy: fsl-imx8mq-usb: add runtime PM support
2026-07-31 8:11 ` [PATCH v8 3/6] phy: fsl-imx8mq-usb: add runtime PM support Xu Yang
@ 2026-07-31 8:21 ` sashiko-bot
2026-07-31 14:42 ` Frank Li
1 sibling, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-07-31 8:21 UTC (permalink / raw)
To: Xu Yang; +Cc: vkoul, olteanv, imx, neil.armstrong, linux-phy, Frank.Li
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Medium] TOCTOU race in tca_blk_typec_switch_set() and tca_blk_init() causes hardware orientation desynchronization.
--
--- Patch [3]: [PATCH v8 3/6] phy: fsl-imx8mq-usb: add runtime PM support ---
commit 2932b088ad1c6fda6302f9ed3f5778ccb0da73b9
Author: Xu Yang <xu.yang_2@nxp.com>
phy: fsl-imx8mq-usb: add runtime PM support
This commit adds runtime PM support to ensure the PHY clocks are properly
gated when the PHY is not in use, reducing power consumption. Clock
management is shifted from the power_on and power_off callbacks into the
new runtime PM callbacks.
> diff --git a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
> index 3a5788c609e1b..42de2cff4d5f3 100644
> --- a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
> +++ b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
[ ... ]
> @@ -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;
[Severity: Medium]
This is a pre-existing issue, but I noticed a potential race condition here.
Does this lockless equality check risk short-circuiting the orientation
switch update if it races with a concurrent initialization?
For instance, if tca_blk_init() is called concurrently during driver probe
or resume, it evaluates tca->orientation locklessly and passes it by value:
tca_blk_init()
...
tca_blk_orientation_set(tca, tca->orientation);
Could tca_blk_typec_switch_set() return 0 here, returning success to the
Type-C core, while the hardware is simultaneously overwritten with a stale
state by tca_blk_init()? This might cause the hardware orientation to become
desynchronized from the Type-C subsystem's expected state, potentially
misrouting USB 3.0 data lines.
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260731-imx8mp-usb-phy-improvement-v8-0-2ec8d6b3854d@nxp.com?part=3
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v8 4/6] phy: fsl-imx8mq-usb: add control register regmap
2026-07-31 8:11 ` [PATCH v8 4/6] phy: fsl-imx8mq-usb: add control register regmap Xu Yang
@ 2026-07-31 8:27 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-07-31 8:27 UTC (permalink / raw)
To: Xu Yang; +Cc: Frank.Li, vkoul, olteanv, linux-phy, imx, neil.armstrong
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Missing PM runtime protection during probe allows mid-probe suspend and double clock disable on error path.
--
--- Patch [4]: [PATCH v8 4/6] phy: fsl-imx8mq-usb: add control register regmap ---
commit 0c8e2a6389c47dd36275e3aecba6491f3f4c70ac
Author: Xu Yang <xu.yang_2@nxp.com>
phy: fsl-imx8mq-usb: add control register regmap
The CR port is a simple 16-bit data/address parallel port that is
accessed through 32-bit MMIO registers for on-chip access to the
control registers inside the USB 3.0 femtoPHY. Add control register
regmap and export these registers by debugfs to help PHY's diagnostic.
[ ... ]
> diff --git a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
> index 42de2cff4d5f3..e03f9dafd69e0 100644
> --- a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
> +++ b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
[ ... ]
> @@ -696,6 +708,13 @@ static int imx8mq_usb_phy_probe(struct platform_device *pdev)
> if (IS_ERR(imx_phy->base))
> return PTR_ERR(imx_phy->base);
>
> + imx_phy->cr_regmap = devm_regmap_init_mmio(dev, imx_phy->base + PHY_CRCTL,
> + &imx_cr_regmap_config);
> + if (IS_ERR(imx_phy->cr_regmap)) {
> + dev_warn(dev, "Fail to init debug register regmap\n");
> + imx_phy->cr_regmap = NULL;
> + }
> +
> 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");
[Severity: High]
Does imx8mq_usb_phy_probe() need a PM runtime usage reference before enabling
runtime PM?
Later in this function, pm_runtime_enable(dev) is called without holding a
usage reference via pm_runtime_get_noresume(). After that, it registers a
Type-C switch.
If the Type-C subsystem (or TCPM) immediately configures this switch via
tca_blk_typec_switch_set(), it acquires and drops a PM reference. Dropping
the reference queues an idle request, causing the device to runtime suspend
and disable its clocks.
If a subsequent probe step like devm_of_phy_provider_register() fails, the
code jumps to disable_rpm:
disable_rpm:
pm_runtime_disable(dev);
return ret;
Because pm_runtime_disable() leaves the device in the suspended state if it
suspended mid-probe, the device remains suspended. The probe returns an
error, triggering devres cleanup, which calls clk_disable_unprepare() and
disables the clocks a second time.
For a shared clock, this drops the global enable count unexpectedly, turning
off the hardware clock while other consumers are actively using it. On SoCs
like i.MX8, accessing unclocked registers typically causes an AXI bus lockup.
Can we acquire a PM reference before enabling runtime PM to prevent the
device from suspending during probe?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260731-imx8mp-usb-phy-improvement-v8-0-2ec8d6b3854d@nxp.com?part=4
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v8 3/6] phy: fsl-imx8mq-usb: add runtime PM support
2026-07-31 8:11 ` [PATCH v8 3/6] phy: fsl-imx8mq-usb: add runtime PM support Xu Yang
2026-07-31 8:21 ` sashiko-bot
@ 2026-07-31 14:42 ` Frank Li
1 sibling, 0 replies; 12+ messages in thread
From: Frank Li @ 2026-07-31 14:42 UTC (permalink / raw)
To: Xu Yang
Cc: Vinod Koul, Neil Armstrong, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Jun Li, linux-phy, imx,
linux-arm-kernel, linux-kernel, Xu Yang
On Fri, Jul 31, 2026 at 04:11:21PM +0800, Xu Yang wrote:
> From: Xu Yang <xu.yang_2@nxp.com>
>
> 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 <xu.yang_2@nxp.com>
>
> ---
> 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);
does devm_pm_runtime_set_active_enabled() work?
Frank
>
> - 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
>
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v8 6/6] phy: fsl-imx8mq-usb: keep PHY power domain runtime always-on for i.MX8MP
2026-07-31 8:11 ` [PATCH v8 6/6] phy: fsl-imx8mq-usb: keep PHY power domain runtime always-on for i.MX8MP Xu Yang
@ 2026-07-31 14:44 ` Frank Li
0 siblings, 0 replies; 12+ messages in thread
From: Frank Li @ 2026-07-31 14:44 UTC (permalink / raw)
To: Xu Yang
Cc: Vinod Koul, Neil Armstrong, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Jun Li, linux-phy, imx,
linux-arm-kernel, linux-kernel, Xu Yang
On Fri, Jul 31, 2026 at 04:11:24PM +0800, Xu Yang wrote:
> From: Xu Yang <xu.yang_2@nxp.com>
>
> On i.MX8MP, the USB PHY has a dedicated power domain that was previously
> never powered off at runtime. With the introduction of runtime PM support,
> the power domain will be powered off if the device is runtime suspended,
> which breaks USB wakeup functionality.
>
> To preserve wakeup functionality, mark the PHY power domain as runtime
> always-on for i.MX8MP platform. To limit the behavior to i.MX8MP, add a
> need_genpd_rpm_on to imx8mq_usb_phy_drvdata and set it as true for i.MX8MP.
>
> Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
>
> ---
> Changes in v8:
> - no changes
> Changes in v7:
> - replace dev_err_probe() with dev_warn()
> Changes in v6:
> - set need_genpd_rpm_on flag for i.MX8MP and call dev_pm_genpd_rpm_always_on()
> if the flag is true
> Changes in v5:
> - no changes
> Changes in v4:
> - no changes
> Changes in v3:
> - new patch
> ---
> drivers/phy/freescale/phy-fsl-imx8mq-usb.c | 9 +++++++++
> 1 file changed, 9 insertions(+)
>
> diff --git a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
> index ba7603a008d8..8859835541b1 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_domain.h>
> #include <linux/pm_runtime.h>
> #include <linux/regulator/consumer.h>
> #include <linux/regmap.h>
> @@ -134,6 +135,7 @@ struct imx8mq_usb_phy {
>
> struct imx8mq_usb_phy_drvdata {
> const struct phy_ops *ops;
> + bool need_genpd_rpm_on;
> };
>
> static void tca_blk_orientation_set(struct tca_blk *tca,
> @@ -669,6 +671,7 @@ static const struct imx8mq_usb_phy_drvdata imx8mq_usb_phy_data = {
>
> static const struct imx8mq_usb_phy_drvdata imx8mp_usb_phy_data = {
> .ops = &imx8mp_usb_phy_ops,
> + .need_genpd_rpm_on = true,
> };
>
> static const struct imx8mq_usb_phy_drvdata imx95_usb_phy_data = {
> @@ -738,6 +741,12 @@ static int imx8mq_usb_phy_probe(struct platform_device *pdev)
> if (!phy_data)
> return -EINVAL;
>
> + if (phy_data->need_genpd_rpm_on) {
> + ret = dev_pm_genpd_rpm_always_on(dev, true);
> + if (ret && ret != -EOPNOTSUPP)
> + dev_warn(dev, "failed to set genpd rpm always on\n");
> + }
> +
> pm_runtime_set_active(dev);
> pm_runtime_enable(dev);
>
>
> --
> 2.34.1
>
>
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-07-31 14:44 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-31 8:11 [PATCH v8 0/6] phy: fsl-imx8mq-usb: few improvements Xu Yang
2026-07-31 8:11 ` [PATCH v8 1/6] phy: fsl-imx8mq-usb: fix typec switch leak on probe error path Xu Yang
2026-07-31 8:19 ` sashiko-bot
2026-07-31 8:11 ` [PATCH v8 2/6] phy: fsl-imx8mq-usb: set usb phy to be wakeup capable Xu Yang
2026-07-31 8:11 ` [PATCH v8 3/6] phy: fsl-imx8mq-usb: add runtime PM support Xu Yang
2026-07-31 8:21 ` sashiko-bot
2026-07-31 14:42 ` Frank Li
2026-07-31 8:11 ` [PATCH v8 4/6] phy: fsl-imx8mq-usb: add control register regmap Xu Yang
2026-07-31 8:27 ` sashiko-bot
2026-07-31 8:11 ` [PATCH v8 5/6] phy: fsl-imx8mq-usb: introduce per-variant driver data structure Xu Yang
2026-07-31 8:11 ` [PATCH v8 6/6] phy: fsl-imx8mq-usb: keep PHY power domain runtime always-on for i.MX8MP Xu Yang
2026-07-31 14:44 ` Frank Li
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox