* [PATCH v2 1/2] phy: qcom: qmp-combo: hold a runtime PM reference in the typec callbacks
2026-09-16 12:56 [PATCH v2 0/2] phy: qcom: qmp-combo: keep the Type-C callbacks off unclocked registers Oleg Keri
@ 2026-09-16 12:56 ` Oleg Keri
2026-09-16 12:56 ` [PATCH v2 2/2] phy: qcom: qmp-combo: check qmp_combo_com_init() " Oleg Keri
1 sibling, 0 replies; 3+ messages in thread
From: Oleg Keri @ 2026-09-16 12:56 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Manivannan Sadhasivam, Johan Hovold,
Bjorn Andersson
Cc: linux-arm-msm, linux-phy, linux-kernel, Konrad Dybcio
qmp_combo_typec_switch_set() and qmp_combo_typec_mux_set() tear the common
block down and bring it straight back up, calling qmp_combo_com_exit() and
qmp_combo_com_init() with force=true. Both release and re-acquire the PHY
clocks.
Unlike the PHY operations, which the PHY core always invokes with a
runtime PM reference held - phy_pm_runtime_get_sync() in phy_init(),
phy_exit() and phy_power_on() - these two are typec_switch and typec_mux
callbacks and hold no such reference. Releasing the clocks is then enough
to drop the device's last reference, so clk_core_unprepare() runs
pm_runtime_idle() and re-enters the driver through
qmp_combo_runtime_suspend(), which disables the very clocks that are being
torn down:
gcc_usb3_prim_phy_pipe_clk already disabled
WARNING: drivers/clk/clk.c:1259 at clk_core_disable+0x298/0x300
Workqueue: events_freezable pmic_glink_altmode_worker
clk_disable
qmp_combo_runtime_suspend
pm_generic_runtime_suspend
genpd_runtime_suspend
rpm_suspend
rpm_idle
__pm_runtime_idle
clk_core_unprepare
clk_core_unprepare
clk_core_unprepare
qmp_combo_runtime_suspend() only checks init_count, which is still
non-zero at that point, so it proceeds and the clock enable and prepare
counts underflow.
Runtime PM is forbidden at probe, so this only becomes reachable once
userspace opts in through power/control - which is exactly what the
comment above pm_runtime_forbid() invites it to do.
Hold a runtime PM reference across the teardown and bringup so releasing
the clocks cannot re-enter the driver's own suspend callback.
Fixes: 2851117f8f42 ("phy: qcom-qmp-combo: Introduce orientation switching")
Signed-off-by: Oleg Keri <okerixx@gmail.com>
---
drivers/phy/qualcomm/phy-qcom-qmp-combo.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c
index fa1a91e67549..f6d3320a59d9 100644
--- a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c
+++ b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c
@@ -16,6 +16,7 @@
#include <linux/of_graph.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <linux/slab.h>
@@ -5179,6 +5180,8 @@ static int qmp_combo_typec_switch_set(struct typec_switch_dev *sw,
qmp->orientation = orientation;
if (qmp->init_count) {
+ guard(pm_runtime_active)(qmp->dev);
+
if (qmp->usb_init_count || qmp->usb4_init_count)
qmp_combo_usb_power_off(qmp->usb_phy);
qmp_combo_com_exit(qmp, true);
@@ -5265,6 +5268,8 @@ static int qmp_combo_typec_mux_set(struct typec_mux_dev *mux, struct typec_mux_s
if (!qmp->init_count)
return 0;
+ guard(pm_runtime_active)(qmp->dev);
+
return qmp_combo_reconfigure_phy(qmp, new_mode);
}
--
2.55.0
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH v2 2/2] phy: qcom: qmp-combo: check qmp_combo_com_init() in the typec callbacks
2026-09-16 12:56 [PATCH v2 0/2] phy: qcom: qmp-combo: keep the Type-C callbacks off unclocked registers Oleg Keri
2026-09-16 12:56 ` [PATCH v2 1/2] phy: qcom: qmp-combo: hold a runtime PM reference in the typec callbacks Oleg Keri
@ 2026-09-16 12:56 ` Oleg Keri
1 sibling, 0 replies; 3+ messages in thread
From: Oleg Keri @ 2026-09-16 12:56 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Manivannan Sadhasivam, Johan Hovold,
Bjorn Andersson
Cc: linux-arm-msm, linux-phy, linux-kernel, Konrad Dybcio
qmp_combo_typec_switch_set() and qmp_combo_reconfigure_phy(), which the
typec mux callback and the USB4 init path use, tear the common block down
and bring it straight back up on an orientation or altmode change, but
they discard the result of the bringup:
qmp_combo_com_exit(qmp, true);
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->usb4_init_count) &&
new_mode != QMPPHY_MODE_DP_ONLY)
qmp_combo_usb_power_on(qmp->usb_phy);
If qmp_combo_com_init() fails - a regulator, a reset or
clk_bulk_prepare_enable() - it unwinds what it had brought up and returns
an error, leaving the clocks disabled. Both paths then carry on and
qmp_combo_usb_power_on(), and dp_aux_init() after it, write PHY registers
with no clock running. qmp_combo_usb4_init() already checks the return
value of qmp_combo_reconfigure_phy(), but never sees a failure from it.
Propagate the failure instead. The PHY is then left logically
initialised with its clocks off, which is the state any forced bringup
failure already produces and which these callbacks have no way to report
to the PHY consumers; what changes is that the register writes that
would fault no longer happen.
Fixes: 2851117f8f42 ("phy: qcom-qmp-combo: Introduce orientation switching")
Signed-off-by: Oleg Keri <okerixx@gmail.com>
---
drivers/phy/qualcomm/phy-qcom-qmp-combo.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c
index f6d3320a59d9..ae70ebba36c4 100644
--- a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c
+++ b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c
@@ -4601,6 +4601,8 @@ static int qmp_combo_usb_set_mode(struct phy *phy, enum phy_mode mode, int submo
static int qmp_combo_reconfigure_phy(struct qmp_combo *qmp, enum qmpphy_mode new_mode)
{
+ int ret;
+
dev_dbg(qmp->dev, "qmp_combo_reconfigure_phy: switching from qmpphy mode %d to %d\n",
qmp->qmpphy_mode, new_mode);
@@ -4615,7 +4617,9 @@ static int qmp_combo_reconfigure_phy(struct qmp_combo *qmp, enum qmpphy_mode new
qmp->qmpphy_mode = new_mode;
/* Now everything's powered down, power up the right PHYs */
- qmp_combo_com_init(qmp, true);
+ ret = qmp_combo_com_init(qmp, true);
+ if (ret)
+ return ret;
if ((qmp->usb_init_count || qmp->usb4_init_count) &&
new_mode != QMPPHY_MODE_DP_ONLY)
@@ -5166,6 +5170,7 @@ static int qmp_combo_typec_switch_set(struct typec_switch_dev *sw,
{
struct qmp_combo *qmp = typec_switch_get_drvdata(sw);
const struct qmp_phy_cfg *cfg = qmp->cfg;
+ int ret;
if (qmp->qmpphy_mode == QMPPHY_MODE_USB4) {
/* QMPPHY has no orientation handling in USB4 mode, don't cache the setting */
@@ -5186,7 +5191,10 @@ static int qmp_combo_typec_switch_set(struct typec_switch_dev *sw,
qmp_combo_usb_power_off(qmp->usb_phy);
qmp_combo_com_exit(qmp, true);
- qmp_combo_com_init(qmp, true);
+ ret = qmp_combo_com_init(qmp, true);
+ if (ret)
+ return ret;
+
if (qmp->usb_init_count || qmp->usb4_init_count)
qmp_combo_usb_power_on(qmp->usb_phy);
if (qmp->dp_init_count)
--
2.55.0
^ permalink raw reply [flat|nested] 3+ messages in thread