Devicetree
 help / color / mirror / Atom feed
* [PATCH 0/2] Add charger detection support for Shikra
@ 2026-11-11 17:31 Krishna Kurapati
  2026-11-11 17:31 ` [PATCH 1/2] dt-bindings: phy: qcom,qusb2: Add support for remote endpoints Krishna Kurapati
  2026-11-11 17:31 ` [PATCH 2/2] phy: qcom: qusb2: Add charger detection support Krishna Kurapati
  0 siblings, 2 replies; 6+ messages in thread
From: Krishna Kurapati @ 2026-11-11 17:31 UTC (permalink / raw)
  To: Vinod Koul, Neil Armstrong, Manivannan Sadhasivam, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Wesley Cheng,
	Maxime Chevallier, Philipp Zabel
  Cc: linux-arm-msm, linux-phy, devicetree, linux-kernel,
	Krishna Kurapati

QUSB2 Phy HW is capable of doing BC.1.2 charger detection. On most of the
platforms, the charger detection is done by PMIC. But on Shikra CQM/CQS
platforms, the PM4125 doesn't have ability to do charger detection and
that responsibility is delegated to QUSB2 Phy which acts on the DP/DM
lines in its control to get the job done.

During process of charger detection, if the controller is supposed to
enter device mode, the charger is detected as SDP/CDP/DCP. The controller
must enter device mode only if the detected charger is SDP/CDP. In case of
DCP, the phy can be turned off after detection. If the controller must
enter host mode, charger detection can be skipped and hence the phy must
know the role before the controller so that appropriate decision can be
taken whether the controller must enter device/host mode or not.

Intention of adding remote endpoints and usb-role-switch property binding
support is as follows:

a) The DP/DM lines do go through the phy to the controller i.e., the phy
exists between connector and controller. Hence adding remote endpoints
depicting the same.

b) Although the usb2 phy is not the entity doing role switching, it still
does need to decide which mode the controller must move into and hence
adding usb-role-switch property to allow role notifications from charger
to go to controller via phy.

Also the driver changes do the following:

a) Refactor phy enable/disable calls to control regulators and clocks.

b) Let the usb-role information coming from PM4125 to pass through the phy
so that when a vbus active signal is detected, the phy can check if its
SDP/CDP/DCP and pass on the role to controller only when the charger is
SDP/CDP. In case of host mode, the role is forwarded without any charger
detection.

c) Since the controller probe relies on phy probe to be completed first,
during phy probe, it won't be possible to get role-switch handle of the
controller. Hence check for controller role switch whenever a role info
is obtained from the connector node.

Signed-off-by: Krishna Kurapati <krishna.kurapati@oss.qualcomm.com>
---
Krishna Kurapati (2):
      dt-bindings: phy: qcom,qusb2: Add support for remote endpoints
      phy: qcom: qusb2: Add charger detection support

 .../devicetree/bindings/phy/qcom,qusb2-phy.yaml    |  23 +
 drivers/phy/qualcomm/phy-qcom-qusb2.c              | 606 +++++++++++++++++++--
 2 files changed, 597 insertions(+), 32 deletions(-)
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20261111-shikra-qusb2-charger-det-de6a4299f6c2

Best regards,
--  
Krishna Kurapati <krishna.kurapati@oss.qualcomm.com>


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/2] dt-bindings: phy: qcom,qusb2: Add support for remote endpoints
  2026-11-11 17:31 [PATCH 0/2] Add charger detection support for Shikra Krishna Kurapati
@ 2026-11-11 17:31 ` Krishna Kurapati
  2026-11-11 19:03   ` sashiko-bot
  2026-11-11 17:31 ` [PATCH 2/2] phy: qcom: qusb2: Add charger detection support Krishna Kurapati
  1 sibling, 1 reply; 6+ messages in thread
From: Krishna Kurapati @ 2026-11-11 17:31 UTC (permalink / raw)
  To: Vinod Koul, Neil Armstrong, Manivannan Sadhasivam, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Wesley Cheng,
	Maxime Chevallier, Philipp Zabel
  Cc: linux-arm-msm, linux-phy, devicetree, linux-kernel,
	Krishna Kurapati

QUSB2 Phy HW is capable of doing BC.1.2 charger detection. On most of the
platforms, the charger detection is done by PMIC. But on Shikra CQM/CQS
platforms, the PM4125 doesn't have ability to do charger detection and
that responsibility is delegated to QUSB2 Phy which acts on the DP/DM
lines in its control to get the job done.

During process of charger detection, if the controller is supposed to
enter device mode, the charger is detected as SDP/CDP/DCP. The controller
must enter device mode only if the detected charger is SDP/CDP. In case of
DCP, the phy can be turned off after detection. If the controller must
enter host mode, charger detection can be skipped and hence the phy must
know the role before the controller so that appropriate decision can be
taken whether the controller must enter device/host mode or not.

Intention of adding remote endpoints and usb-role-switch property binding
support is as follows:

a) The DP/DM lines do go through the phy to the controller i.e., the phy
exists between connector and controller. Hence adding remote endpoints
depicting the same.

b) Although the usb2 phy is not the entity doing role switching, it still
does need to decide which mode the controller must move into and hence
adding usb-role-switch property to allow role notifications from charger
to go to controller via phy.

Signed-off-by: Krishna Kurapati <krishna.kurapati@oss.qualcomm.com>
---
 .../devicetree/bindings/phy/qcom,qusb2-phy.yaml    | 23 ++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/Documentation/devicetree/bindings/phy/qcom,qusb2-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,qusb2-phy.yaml
index 001fd0ccc985..0b6b2933dd98 100644
--- a/Documentation/devicetree/bindings/phy/qcom,qusb2-phy.yaml
+++ b/Documentation/devicetree/bindings/phy/qcom,qusb2-phy.yaml
@@ -159,6 +159,29 @@ properties:
     maximum: 3
     default: 0
 
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+    description:
+      Those ports can be used with any connector/pmic node that is able to
+      provide role switch notifications for charger detection purposes.
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: Connector Data bus
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: Controller data bus.
+
+  usb-role-switch:
+    description:
+      In the context of usb-phy, it denotes the capability to perform charger
+      detection. Although phy is not the one responsible for switching mode, it
+      is the one responsible to take a call on whether controller must switch
+      mode or not. In case of SDP/CDT, the phy would inform the controller to
+      switch to device and in case of DCP, role switch doesn't need to happen.
+
 required:
   - compatible
   - reg

-- 
2.34.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 2/2] phy: qcom: qusb2: Add charger detection support
  2026-11-11 17:31 [PATCH 0/2] Add charger detection support for Shikra Krishna Kurapati
  2026-11-11 17:31 ` [PATCH 1/2] dt-bindings: phy: qcom,qusb2: Add support for remote endpoints Krishna Kurapati
@ 2026-11-11 17:31 ` Krishna Kurapati
  2026-11-11 19:23   ` sashiko-bot
  2026-11-12  0:40   ` kernel test robot
  1 sibling, 2 replies; 6+ messages in thread
From: Krishna Kurapati @ 2026-11-11 17:31 UTC (permalink / raw)
  To: Vinod Koul, Neil Armstrong, Manivannan Sadhasivam, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Wesley Cheng,
	Maxime Chevallier, Philipp Zabel
  Cc: linux-arm-msm, linux-phy, devicetree, linux-kernel,
	Krishna Kurapati

QUSB2 Phy is capable of performing BC.1.2 Charger detection by controlling
the dp/dm lines. On platforms where the PMIC isn't capable of performing
charger detection, the responsibility is delegated to the phy.

On Shikra CQM/CQS platforms, the PM4125 relies on QUSB2 Phy to perform
charger detection. As part of this, the following changes are being done:

a) Refactor phy enable/disable calls to control regulators and clocks.

b) Let the usb-role information coming from PM4125 to pass through the phy
so that when a vbus active signal is detected, the phy can check if its
SDP/CDP/DCP and pass on the role to controller only when the charger is
SDP/CDP. In case of host mode, the role is forwarded without any charger
detection.

c) Since the controller probe relies on phy probe to be completed first,
during phy probe, it won't be possible to get role-switch handle of the
controller. Hence check for controller role switch whenever a role info
is obtained from the connector node.

Signed-off-by: Krishna Kurapati <krishna.kurapati@oss.qualcomm.com>
---
 drivers/phy/qualcomm/phy-qcom-qusb2.c | 606 ++++++++++++++++++++++++++++++++--
 1 file changed, 574 insertions(+), 32 deletions(-)

diff --git a/drivers/phy/qualcomm/phy-qcom-qusb2.c b/drivers/phy/qualcomm/phy-qcom-qusb2.c
index 2e5fdb620cb2..549dc8d1378a 100644
--- a/drivers/phy/qualcomm/phy-qcom-qusb2.c
+++ b/drivers/phy/qualcomm/phy-qcom-qusb2.c
@@ -18,9 +18,31 @@
 #include <linux/regulator/consumer.h>
 #include <linux/reset.h>
 #include <linux/slab.h>
+#include <linux/power_supply.h>
+#include <linux/usb/role.h>
 
 #include <dt-bindings/phy/phy-qcom-qusb2.h>
 
+#define QUSB2PHY_PORT_QC1	0x70
+#define VDM_SRC_EN		BIT(4)
+#define VDP_SRC_EN		BIT(2)
+
+#define QUSB2PHY_PORT_UTMI_CTRL1	0xC0
+#define SUSPEND_N			BIT(5)
+#define TERM_SELECT			BIT(4)
+#define XCVR_SELECT_FS			BIT(2)
+#define OP_MODE_NON_DRIVE		BIT(0)
+
+#define QUSB2PHY_PORT_UTMI_CTRL2	0xC4
+#define UTMI_ULPI_SEL			BIT(7)
+#define UTMI_TEST_MUX_SEL		BIT(6)
+
+#define QUSB2PHY_PORT_QC2	0x74
+#define RDM_UP_EN		BIT(1)
+#define RDP_UP_EN		BIT(3)
+#define RPUM_LOW_EN		BIT(4)
+#define RPUP_LOW_EN		BIT(5)
+
 #define QUSB2PHY_PLL			0x0
 #define QUSB2PHY_PLL_TEST		0x04
 #define CLK_REF_SEL			BIT(7)
@@ -30,10 +52,19 @@
 #define QUSB2PHY_PLL_USER_CTL2		0x10
 #define QUSB2PHY_PLL_AUTOPGM_CTL1	0x1c
 #define QUSB2PHY_PLL_PWR_CTRL		0x18
+#define PLL_RESET_N_CNT_5		0x5
+#define PLL_RESET_N			BIT(4)
+#define PLL_AUTOPGM_EN			BIT(7)
 
 /* QUSB2PHY_PLL_STATUS register bits */
 #define PLL_LOCKED			BIT(5)
 
+/* QUSB2PHY_PLL_PWR_CTRL register bits */
+#define REF_BUF_EN			BIT(6)
+#define REXT_EN				BIT(4)
+#define PLL_BYPASSNL			BIT(1)
+#define REXT_TRIM_0			BIT(0)
+
 /* QUSB2PHY_PLL_COMMON_STATUS_ONE register bits */
 #define CORE_READY_STATUS		BIT(0)
 
@@ -57,6 +88,10 @@
 #define DMSE_INTR_EN				BIT(1)
 #define DPSE_INTR_EN				BIT(0)
 
+#define QUSB2PHY_PORT_INTR_STATUS	0xF0
+#define DMSE_INTR_HIGH			BIT(2)
+#define DPSE_INTR_HIGH			BIT(0)
+
 /* QUSB2PHY_PLL_CORE_INPUT_OVERRIDE register bits */
 #define CORE_PLL_EN_FROM_RESET			BIT(4)
 #define CORE_RESET				BIT(5)
@@ -97,6 +132,29 @@
 #define QUSB2PHY_IMP_CTRL2			0x224
 #define QUSB2PHY_CHG_CTRL2			0x23c
 
+#define CHG_DCD_TIMEOUT_MSEC			750
+#define CHG_DCD_POLL_TIME_MSEC			50
+#define CHG_PRIMARY_DET_TIME_MSEC		100
+#define CHG_SECONDARY_DET_TIME_MSEC		100
+#define DP_PULSE_WIDTH_MSEC			200
+
+enum port_state {
+	PORT_UNKNOWN,
+	PORT_DISCONNECTED,
+	PORT_DCD_IN_PROGRESS,
+	PORT_PRIMARY_IN_PROGRESS,
+	PORT_SECONDARY_IN_PROGRESS,
+	PORT_CHG_DET_DONE,
+	PORT_HOST_MODE,
+};
+
+enum chg_det_state {
+	STATE_UNKNOWN,
+	STATE_DCD,
+	STATE_PRIMARY,
+	STATE_SECONDARY,
+};
+
 struct qusb2_phy_init_tbl {
 	unsigned int offset;
 	unsigned int val;
@@ -478,9 +536,425 @@ struct qusb2_phy {
 	const struct qusb2_phy_cfg *cfg;
 	bool has_se_clk_scheme;
 	bool phy_initialized;
+	bool is_phy_enabled;
 	enum phy_mode mode;
+	int vbus_active;
+	int id_state;
+	struct power_supply	*usb_psy;
+	struct delayed_work	port_det_w;
+	enum port_state		port_state;
+	unsigned int		dcd_timeout;
+	bool			dcp_charger;
+
+	struct usb_role_switch	*connector_role_sw;
+	struct usb_role_switch	*controller_role_sw;
+	enum usb_role		current_role;
+	struct mutex		role_lock;
 };
 
+static int qusb_phy_notify_charger(struct qusb2_phy *qphy,
+					enum power_supply_usb_type charger_type)
+{
+	union power_supply_propval pval = {0};
+
+	if (!qphy->usb_psy) {
+		qphy->usb_psy = power_supply_get_by_name("pm4125-charger");
+		if (!qphy->usb_psy) {
+			dev_err(&qphy->phy->dev, "could not get usb power supply\n");
+			return -ENODEV;
+		}
+	}
+
+	dev_dbg(&qphy->phy->dev, "notifying charger type: %d\n", charger_type);
+
+	pval.intval = charger_type;
+	power_supply_set_property(qphy->usb_psy, POWER_SUPPLY_PROP_USB_TYPE,
+									&pval);
+	return 0;
+}
+
+static bool qusb_phy_chg_det_status(struct qusb2_phy *qphy,
+						enum chg_det_state state)
+{
+	u32 reg, status;
+
+	reg = readl_relaxed(qphy->base + QUSB2PHY_PORT_INTR_STATUS);
+	dev_dbg(&qphy->phy->dev, "chg_det_status: state=%d intr_status=0x%02x\n",
+		state, reg);
+
+	status = reg & 0xff;
+
+	switch (state) {
+	case STATE_DCD:
+		return (status != DPSE_INTR_HIGH);
+	case STATE_PRIMARY:
+		return (status && (status != DPSE_INTR_HIGH));
+	case STATE_SECONDARY:
+		return status;
+	case STATE_UNKNOWN:
+	default:
+		break;
+	}
+
+	return false;
+}
+
+/*
+ * Different circuit blocks are enabled on DP and DM lines as part
+ * of different phases of charger detection. Then the state of
+ * DP and DM lines are monitored to identify different type of
+ * chargers.
+ * These circuit blocks can be enabled with the configuration of
+ * the QUICKCHARGE1 and QUICKCHARGE2 registers and the DP/DM lines
+ * can be monitored with the status of the INTR_STATUS register.
+ */
+static void qusb_phy_chg_det_enable_seq(struct qusb2_phy *qphy, int state)
+{
+	dev_dbg(&qphy->phy->dev, "chg_det_enable_seq: state=%d base: %pK\n", state, qphy->base);
+
+	writel_relaxed(0x23, qphy->base + qphy->cfg->regs[QUSB2PHY_PORT_POWERDOWN]);
+	writel_relaxed(0x35, qphy->base + QUSB2PHY_PORT_UTMI_CTRL1);
+	writel_relaxed(0xC0, qphy->base + QUSB2PHY_PORT_UTMI_CTRL2);
+	writel_relaxed(0x05, qphy->base + QUSB2PHY_PLL_AUTOPGM_CTL1);
+	writel_relaxed(0x22, qphy->base + qphy->cfg->regs[QUSB2PHY_PORT_POWERDOWN]);
+	writel_relaxed(0x17, qphy->base + QUSB2PHY_PLL_PWR_CTRL);
+
+	usleep_range(5, 10);
+
+	writel_relaxed(0x15, qphy->base + QUSB2PHY_PLL_AUTOPGM_CTL1);
+	writel_relaxed(0x00, qphy->base + QUSB2PHY_PORT_QC1);
+	writel_relaxed(0x00, qphy->base + QUSB2PHY_PORT_QC2);
+
+	usleep_range(50, 60);
+
+	switch (state) {
+	case STATE_DCD:
+		writel_relaxed(0x08, qphy->base + QUSB2PHY_PORT_QC1);
+		writel_relaxed(0x01, qphy->base + QUSB2PHY_PORT_QC2);
+		writel_relaxed(0x1F, qphy->base + qphy->cfg->regs[QUSB2PHY_INTR_CTRL]);
+		break;
+	case STATE_PRIMARY:
+		writel_relaxed(0x25, qphy->base + QUSB2PHY_PORT_QC1);
+		writel_relaxed(0x00, qphy->base + QUSB2PHY_PORT_QC2);
+		writel_relaxed(0x1F, qphy->base + qphy->cfg->regs[QUSB2PHY_INTR_CTRL]);
+		break;
+	case STATE_SECONDARY:
+		writel_relaxed(0x72, qphy->base + QUSB2PHY_PORT_QC1);
+		writel_relaxed(0x00, qphy->base + QUSB2PHY_PORT_QC2);
+		writel_relaxed(0x1F, qphy->base + qphy->cfg->regs[QUSB2PHY_INTR_CTRL]);
+		break;
+	case STATE_UNKNOWN:
+	default:
+		break;
+	};
+}
+
+/*
+ * qusb2_phy_enable_phy - power up regulators and enable clocks for the PHY.
+ *
+ * Used by the charger-detection path to bring the PHY up before programming
+ * the QC1/QC2 detection registers, and by qusb2_phy_init().
+ * No-op if the PHY is already enabled (is_phy_enabled is true).
+ */
+static int qusb2_phy_enable_phy(struct qusb2_phy *qphy)
+{
+	struct device *dev = &qphy->phy->dev;
+	int ret;
+
+	if (qphy->is_phy_enabled) {
+		dev_dbg(dev, "PHY already enabled, skipping\n");
+		return 0;
+	}
+
+	dev_vdbg(dev, "enabling PHY clocks and regulators\n");
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(qphy->vregs), qphy->vregs);
+	if (ret) {
+		dev_err(dev, "failed to enable regulators, %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(qphy->iface_clk);
+	if (ret) {
+		dev_err(dev, "failed to enable iface_clk, %d\n", ret);
+		goto disable_regs;
+	}
+
+	ret = clk_prepare_enable(qphy->cfg_ahb_clk);
+	if (ret) {
+		dev_err(dev, "failed to enable cfg_ahb_clk, %d\n", ret);
+		goto disable_iface_clk;
+	}
+
+	if (!qphy->has_se_clk_scheme) {
+		ret = clk_prepare_enable(qphy->ref_clk);
+		if (ret) {
+			dev_err(dev, "failed to enable ref_clk, %d\n", ret);
+			goto disable_ahb_clk;
+		}
+	}
+
+	qphy->is_phy_enabled = true;
+
+	return 0;
+
+disable_ahb_clk:
+	clk_disable_unprepare(qphy->cfg_ahb_clk);
+disable_iface_clk:
+	clk_disable_unprepare(qphy->iface_clk);
+disable_regs:
+	regulator_bulk_disable(ARRAY_SIZE(qphy->vregs), qphy->vregs);
+	return ret;
+}
+
+/*
+ * qusb2_phy_disable_phy - disable clocks and power down regulators for the PHY.
+ */
+static void qusb2_phy_disable_phy(struct qusb2_phy *qphy)
+{
+	if (!qphy->is_phy_enabled) {
+		dev_dbg(&qphy->phy->dev, "PHY already disabled, skipping\n");
+		return;
+	}
+
+	dev_vdbg(&qphy->phy->dev, "disabling PHY clocks and regulators\n");
+
+	if (!qphy->has_se_clk_scheme)
+		clk_disable_unprepare(qphy->ref_clk);
+
+	clk_disable_unprepare(qphy->cfg_ahb_clk);
+	clk_disable_unprepare(qphy->iface_clk);
+	regulator_bulk_disable(ARRAY_SIZE(qphy->vregs), qphy->vregs);
+	qphy->is_phy_enabled = false;
+}
+
+/*
+ * qusb2_phy_forward_role - forward a USB role to the controller.
+ *
+ * Only called after charger detection confirms the port is SDP or CDP, so
+ * the controller knows it should operate in device mode.
+ */
+static void qusb2_phy_forward_role(struct qusb2_phy *qphy, enum usb_role role)
+{
+	if (!qphy->controller_role_sw)
+		return;
+
+	dev_dbg(&qphy->phy->dev, "forwarding role %s to controller\n",
+		usb_role_string(role));
+
+	usb_role_switch_set_role(qphy->controller_role_sw, role);
+}
+
+static int qusb_phy_drive_dp_pulse(struct qusb2_phy *qphy)
+{
+	int ret;
+
+	dev_dbg(&qphy->phy->dev, "connected to a CDP, drive DP up\n");
+
+	ret = qusb2_phy_enable_phy(qphy);
+	if (ret)
+		return ret;
+
+	ret = reset_control_assert(qphy->phy_reset);
+	if (ret)
+		dev_err(&qphy->phy->dev, "phy assert failed\n");
+	usleep_range(100, 150);
+	ret = reset_control_deassert(qphy->phy_reset);
+	if (ret)
+		dev_err(&qphy->phy->dev, "phy deassert failed\n");
+
+	writel_relaxed(CLAMP_N_EN | FREEZIO_N | POWER_DOWN,
+			qphy->base + qphy->cfg->regs[QUSB2PHY_PORT_POWERDOWN]);
+	writel_relaxed(TERM_SELECT | XCVR_SELECT_FS | OP_MODE_NON_DRIVE |
+			SUSPEND_N, qphy->base + QUSB2PHY_PORT_UTMI_CTRL1);
+	writel_relaxed(UTMI_ULPI_SEL | UTMI_TEST_MUX_SEL,
+			qphy->base + QUSB2PHY_PORT_UTMI_CTRL2);
+	writel_relaxed(PLL_RESET_N_CNT_5,
+			qphy->base + QUSB2PHY_PLL_AUTOPGM_CTL1);
+	writel_relaxed(CLAMP_N_EN | FREEZIO_N,
+			qphy->base + qphy->cfg->regs[QUSB2PHY_PORT_POWERDOWN]);
+	writel_relaxed(REF_BUF_EN | REXT_EN | PLL_BYPASSNL | REXT_TRIM_0,
+			qphy->base + QUSB2PHY_PLL_PWR_CTRL);
+
+	usleep_range(5, 10);
+
+	writel_relaxed(0x15, qphy->base + QUSB2PHY_PLL_AUTOPGM_CTL1);
+	writel_relaxed(PLL_RESET_N | PLL_RESET_N_CNT_5,
+			qphy->base + QUSB2PHY_PLL_AUTOPGM_CTL1);
+	writel_relaxed(0x00, qphy->base + QUSB2PHY_PORT_QC1);
+	writel_relaxed(0x00, qphy->base + QUSB2PHY_PORT_QC2);
+
+	usleep_range(50, 60);
+
+	writel_relaxed(RDP_UP_EN, qphy->base + QUSB2PHY_PORT_QC2);
+
+	msleep(DP_PULSE_WIDTH_MSEC);
+
+	writel_relaxed(CLAMP_N_EN | FREEZIO_N | POWER_DOWN,
+			qphy->base + qphy->cfg->regs[QUSB2PHY_PORT_POWERDOWN]);
+	writel_relaxed(PLL_AUTOPGM_EN | PLL_RESET_N | PLL_RESET_N_CNT_5,
+			qphy->base + QUSB2PHY_PLL_AUTOPGM_CTL1);
+	writel_relaxed(UTMI_ULPI_SEL, qphy->base + QUSB2PHY_PORT_UTMI_CTRL2);
+	writel_relaxed(TERM_SELECT, qphy->base + QUSB2PHY_PORT_UTMI_CTRL1);
+
+	qusb2_phy_disable_phy(qphy);
+
+	return 0;
+}
+
+static void qusb_phy_port_state_work(struct work_struct *w)
+{
+	struct qusb2_phy *qphy = container_of(w, struct qusb2_phy,
+							port_det_w.work);
+	unsigned long delay = 0;
+	int status, ret = 0;
+
+	dev_dbg(&qphy->phy->dev, "port_state_work: state=%d vbus_active=%d id_state=%d\n",
+		qphy->port_state, qphy->vbus_active, qphy->id_state);
+
+	switch (qphy->port_state) {
+	case PORT_UNKNOWN:
+		if (!qphy->id_state) {
+			dev_dbg(&qphy->phy->dev, "ID grounded, entering host mode\n");
+			qphy->port_state = PORT_HOST_MODE;
+			qusb2_phy_forward_role(qphy, USB_ROLE_HOST);
+			return;
+		}
+
+		if (qphy->vbus_active) {
+			dev_dbg(&qphy->phy->dev, "VBUS active, starting DCD\n");
+
+			/* Enable DCD sequence */
+			ret = qusb2_phy_enable_phy(qphy);
+			if (ret)
+				return;
+
+			qusb_phy_chg_det_enable_seq(qphy, STATE_DCD);
+			qphy->port_state = PORT_DCD_IN_PROGRESS;
+			qphy->dcd_timeout = 0;
+			delay = CHG_DCD_POLL_TIME_MSEC;
+			break;
+		}
+		return;
+	case PORT_DISCONNECTED:
+		dev_dbg(&qphy->phy->dev, "port disconnected, resetting state\n");
+		qusb2_phy_disable_phy(qphy);
+		qphy->port_state = PORT_UNKNOWN;
+		qphy->dcp_charger = false;
+		break;
+	case PORT_DCD_IN_PROGRESS:
+		if (!qphy->vbus_active) {
+			dev_dbg(&qphy->phy->dev, "VBUS lost during DCD, aborting\n");
+
+			/* Disable PHY sequence */
+			qusb2_phy_disable_phy(qphy);
+			qphy->port_state = PORT_DISCONNECTED;
+			break;
+		}
+
+		status = qusb_phy_chg_det_status(qphy, STATE_DCD);
+		if (!status && qphy->dcd_timeout < CHG_DCD_TIMEOUT_MSEC) {
+			dev_dbg(&qphy->phy->dev, "DCD polling: timeout=%u ms\n",
+				qphy->dcd_timeout);
+			delay = CHG_DCD_POLL_TIME_MSEC;
+			qphy->dcd_timeout += delay;
+		} else if (status) {
+			dev_dbg(&qphy->phy->dev, "DCD complete, starting primary detection\n");
+			qusb_phy_chg_det_enable_seq(qphy, STATE_PRIMARY);
+			qphy->port_state = PORT_PRIMARY_IN_PROGRESS;
+			delay = CHG_PRIMARY_DET_TIME_MSEC;
+		} else if (qphy->dcd_timeout >= CHG_DCD_TIMEOUT_MSEC) {
+			dev_dbg(&qphy->phy->dev,
+				 "charger detected: DCP (DCD timeout after %u ms)\n",
+				 qphy->dcd_timeout);
+			qusb_phy_notify_charger(qphy,
+						POWER_SUPPLY_USB_TYPE_DCP);
+			qusb2_phy_disable_phy(qphy);
+			qphy->port_state = PORT_CHG_DET_DONE;
+		}
+		break;
+	case PORT_PRIMARY_IN_PROGRESS:
+		if (!qphy->vbus_active) {
+			dev_dbg(&qphy->phy->dev, "VBUS lost during primary detection, aborting\n");
+			qusb2_phy_disable_phy(qphy);
+			qphy->port_state = PORT_DISCONNECTED;
+			break;
+		}
+
+		status = qusb_phy_chg_det_status(qphy, STATE_PRIMARY);
+		if (status) {
+			dev_dbg(&qphy->phy->dev,
+				"primary detection positive, starting secondary detection\n");
+			qusb_phy_chg_det_enable_seq(qphy, STATE_SECONDARY);
+			qphy->port_state = PORT_SECONDARY_IN_PROGRESS;
+			delay = CHG_SECONDARY_DET_TIME_MSEC;
+
+		} else {
+			dev_dbg(&qphy->phy->dev, "charger detected: SDP (standard USB host)\n");
+			qusb2_phy_disable_phy(qphy);
+			qusb_phy_notify_charger(qphy, POWER_SUPPLY_USB_TYPE_SDP);
+			qusb2_phy_forward_role(qphy, USB_ROLE_DEVICE);
+			qphy->port_state = PORT_CHG_DET_DONE;
+		}
+		break;
+	case PORT_SECONDARY_IN_PROGRESS:
+		if (!qphy->vbus_active) {
+			dev_dbg(&qphy->phy->dev, "VBUS lost during secondary detection, aborting\n");
+			qusb2_phy_disable_phy(qphy);
+			qphy->port_state = PORT_DISCONNECTED;
+			break;
+		}
+
+		status = qusb_phy_chg_det_status(qphy, STATE_SECONDARY);
+		if (status) {
+			dev_dbg(&qphy->phy->dev, "charger detected: DCP (dedicated charging port)\n");
+			qusb_phy_notify_charger(qphy,
+						POWER_SUPPLY_USB_TYPE_DCP);
+			qphy->dcp_charger = true;
+		} else {
+			dev_dbg(&qphy->phy->dev, "charger detected: CDP (charging downstream port)\n");
+			qusb_phy_notify_charger(qphy,
+						POWER_SUPPLY_USB_TYPE_CDP);
+			qusb_phy_drive_dp_pulse(qphy);
+			qusb2_phy_disable_phy(qphy);
+			qusb2_phy_forward_role(qphy, USB_ROLE_DEVICE);
+		}
+
+		qphy->port_state = PORT_CHG_DET_DONE;
+		break;
+	case PORT_CHG_DET_DONE:
+		if (!qphy->vbus_active) {
+			dev_dbg(&qphy->phy->dev, "VBUS removed, resetting from CHG_DET_DONE\n");
+
+			if (qphy->dcp_charger) {
+				qphy->dcp_charger = false;
+				qusb2_phy_disable_phy(qphy);
+			} else {
+				qusb2_phy_forward_role(qphy, USB_ROLE_NONE);
+			}
+			qphy->port_state = PORT_UNKNOWN;
+		}
+
+		return;
+	case PORT_HOST_MODE:
+		if (qphy->id_state) {
+			dev_dbg(&qphy->phy->dev, "ID released, leaving host mode\n");
+			qphy->port_state = PORT_UNKNOWN;
+			qusb2_phy_forward_role(qphy, USB_ROLE_NONE);
+		}
+
+		if (!qphy->vbus_active)
+			return;
+
+		break;
+	default:
+		return;
+	}
+
+	queue_delayed_work(system_freezable_wq,
+			&qphy->port_det_w, msecs_to_jiffies(delay));
+}
+
 static inline void qusb2_write_mask(void __iomem *base, u32 offset,
 				    u32 val, u32 mask)
 {
@@ -767,29 +1241,16 @@ static int qusb2_phy_init(struct phy *phy)
 
 	dev_vdbg(&phy->dev, "%s(): Initializing QUSB2 phy\n", __func__);
 
-	/* turn on regulator supplies */
-	ret = regulator_bulk_enable(ARRAY_SIZE(qphy->vregs), qphy->vregs);
+	/* Power up regulators and enable clocks (including ref_clk if needed). */
+	ret = qusb2_phy_enable_phy(qphy);
 	if (ret)
 		return ret;
 
-	ret = clk_prepare_enable(qphy->iface_clk);
-	if (ret) {
-		dev_err(&phy->dev, "failed to enable iface_clk, %d\n", ret);
-		goto poweroff_phy;
-	}
-
-	/* enable ahb interface clock to program phy */
-	ret = clk_prepare_enable(qphy->cfg_ahb_clk);
-	if (ret) {
-		dev_err(&phy->dev, "failed to enable cfg ahb clock, %d\n", ret);
-		goto disable_iface_clk;
-	}
-
 	/* Perform phy reset */
 	ret = reset_control_assert(qphy->phy_reset);
 	if (ret) {
 		dev_err(&phy->dev, "failed to assert phy_reset, %d\n", ret);
-		goto disable_ahb_clk;
+		return ret;
 	}
 
 	/* 100 us delay to keep PHY in reset mode */
@@ -798,7 +1259,7 @@ static int qusb2_phy_init(struct phy *phy)
 	ret = reset_control_deassert(qphy->phy_reset);
 	if (ret) {
 		dev_err(&phy->dev, "failed to de-assert phy_reset, %d\n", ret);
-		goto disable_ahb_clk;
+		goto assert_phy_reset;
 	}
 
 	/* Disable the PHY */
@@ -887,23 +1348,15 @@ static int qusb2_phy_init(struct phy *phy)
 		dev_err(&phy->dev,
 			"QUSB2PHY pll lock failed: status reg = %x\n", val);
 		ret = -EBUSY;
-		goto disable_ref_clk;
+		goto assert_phy_reset;
 	}
 	qphy->phy_initialized = true;
 
 	return 0;
 
-disable_ref_clk:
-	if (!qphy->has_se_clk_scheme)
-		clk_disable_unprepare(qphy->ref_clk);
 assert_phy_reset:
 	reset_control_assert(qphy->phy_reset);
-disable_ahb_clk:
-	clk_disable_unprepare(qphy->cfg_ahb_clk);
-disable_iface_clk:
-	clk_disable_unprepare(qphy->iface_clk);
-poweroff_phy:
-	regulator_bulk_disable(ARRAY_SIZE(qphy->vregs), qphy->vregs);
+	qusb2_phy_disable_phy(qphy);
 
 	return ret;
 }
@@ -921,16 +1374,83 @@ static int qusb2_phy_exit(struct phy *phy)
 
 	reset_control_assert(qphy->phy_reset);
 
-	clk_disable_unprepare(qphy->cfg_ahb_clk);
-	clk_disable_unprepare(qphy->iface_clk);
-
-	regulator_bulk_disable(ARRAY_SIZE(qphy->vregs), qphy->vregs);
+	qusb2_phy_disable_phy(qphy);
 
 	qphy->phy_initialized = false;
 
 	return 0;
 }
 
+/*
+ * qusb2_role_switch_set - USB role switch callback.
+ *
+ * Invoked by the role-switch framework when an upstream user (e.g. a Type-C
+ * port manager) requests a role change.  For USB_ROLE_DEVICE we kick off
+ * charger detection; the state machine will forward the role to the
+ * controller switch only after confirming SDP or CDP.  For USB_ROLE_HOST
+ * and USB_ROLE_NONE we forward immediately.
+ */
+static int qusb2_role_switch_set(struct usb_role_switch *sw, enum usb_role role)
+{
+	struct qusb2_phy *qphy = usb_role_switch_get_drvdata(sw);
+
+	dev_dbg(&qphy->phy->dev, "role switch set: %s\n", usb_role_string(role));
+
+	mutex_lock(&qphy->role_lock);
+	qphy->current_role = role;
+
+	/*
+	 * If the controller role switch was not available at probe time (e.g.
+	 * the USB controller driver registered its role switch after us), try
+	 * to look it up now.  A return of NULL or -ENODEV simply means there
+	 * is no controller switch to forward to, which is fine.
+	 */
+	if (!qphy->controller_role_sw) {
+		struct usb_role_switch *dsw = usb_role_switch_get(&qphy->phy->dev);
+
+		if (IS_ERR_OR_NULL(dsw)) {
+			if (!IS_ERR(dsw) || PTR_ERR(dsw) == -ENODEV)
+				dev_dbg(&qphy->phy->dev,
+					"no controller role switch available\n");
+			else
+				dev_err(&qphy->phy->dev,
+					"failed to get controller role switch: %ld\n",
+					PTR_ERR(dsw));
+		} else {
+			qphy->controller_role_sw = dsw;
+		}
+	}
+
+	switch (role) {
+	case USB_ROLE_DEVICE:
+		/*
+		 * VBUS is active; start charger detection.  The state machine
+		 * will forward USB_ROLE_DEVICE controller only for SDP/CDP.
+		 */
+		qphy->vbus_active = 1;
+		qphy->id_state = 1;
+		qphy->port_state = PORT_UNKNOWN;
+		queue_delayed_work(system_freezable_wq, &qphy->port_det_w, 0);
+		break;
+	case USB_ROLE_HOST:
+		qphy->id_state = 0;
+		qphy->vbus_active = 0;
+		qphy->port_state = PORT_UNKNOWN;
+		queue_delayed_work(system_freezable_wq, &qphy->port_det_w, 0);
+		break;
+	case USB_ROLE_NONE:
+	default:
+		qphy->vbus_active = 0;
+		qphy->id_state = 1;
+		cancel_delayed_work(&qphy->port_det_w);
+		qusb2_phy_forward_role(qphy, USB_ROLE_NONE);
+		break;
+	}
+	mutex_unlock(&qphy->role_lock);
+
+	return 0;
+}
+
 static const struct phy_ops qusb2_phy_gen_ops = {
 	.init		= qusb2_phy_init,
 	.exit		= qusb2_phy_exit,
@@ -1131,7 +1651,29 @@ static int qusb2_phy_probe(struct platform_device *pdev)
 
 	phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
 
-	return PTR_ERR_OR_ZERO(phy_provider);
+	if (IS_ERR(phy_provider))
+		return PTR_ERR(phy_provider);
+
+	mutex_init(&qphy->role_lock);
+	INIT_DELAYED_WORK(&qphy->port_det_w, qusb_phy_port_state_work);
+
+	if (device_property_read_bool(dev, "usb-role-switch")) {
+		struct usb_role_switch_desc role_sw_desc = {};
+
+		role_sw_desc.fwnode = dev->fwnode;
+		role_sw_desc.set = qusb2_role_switch_set;
+		role_sw_desc.driver_data = qphy;
+
+		qphy->connector_role_sw = usb_role_switch_register(dev, &role_sw_desc);
+		if (IS_ERR(qphy->connector_role_sw)) {
+			ret = PTR_ERR(qphy->connector_role_sw);
+			dev_err(dev, "failed to register role switch, %d\n", ret);
+			return ret;
+		}
+	}
+	qphy->controller_role_sw = NULL;
+
+	return 0;
 }
 
 static struct platform_driver qusb2_phy_driver = {

-- 
2.34.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/2] dt-bindings: phy: qcom,qusb2: Add support for remote endpoints
  2026-11-11 17:31 ` [PATCH 1/2] dt-bindings: phy: qcom,qusb2: Add support for remote endpoints Krishna Kurapati
@ 2026-11-11 19:03   ` sashiko-bot
  0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-11-11 19:03 UTC (permalink / raw)
  To: Krishna Kurapati
  Cc: linux-phy, devicetree, robh, conor+dt, vkoul, neil.armstrong,
	olteanv

Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [Medium] dt-bindings: phy: qcom,qusb2: usb-role-switch overloaded for
  BC1.2 detection
- [Low] dt-bindings: phy: qcom,qusb2: incomplete usb-role-switch and
  ports schema
--

commit fd796c127a8188028d89719a01ee3f717ede3073
Author: Krishna Kurapati <krishna.kurapati@oss.qualcomm.com>

dt-bindings: phy: qcom,qusb2: Add support for remote endpoints

This commit adds OF graph ports and the usb-role-switch property to the
Qualcomm QUSB2 PHY Devicetree binding to support BC1.2 charger detection
and role notification routing.

> diff --git a/Documentation/devicetree/bindings/phy/qcom,qusb2-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,qusb2-phy.yaml
> index 001fd0ccc9852..0b6b2933dd98b 100644
> --- a/Documentation/devicetree/bindings/phy/qcom,qusb2-phy.yaml
> +++ b/Documentation/devicetree/bindings/phy/qcom,qusb2-phy.yaml
> @@ -159,6 +159,29 @@ properties:
>      maximum: 3
>      default: 0
>  
> +  ports:
> +    $ref: /schemas/graph.yaml#/properties/ports
> +    description:
> +      Those ports can be used with any connector/pmic node that is able to
> +      provide role switch notifications for charger detection purposes.
> +
> +    properties:
> +      port@0:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        description: Connector Data bus
> +
> +      port@1:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        description: Controller data bus.
> +
> +  usb-role-switch:
> +    description:
> +      In the context of usb-phy, it denotes the capability to perform charger
> +      detection. Although phy is not the one responsible for switching mode, it
> +      is the one responsible to take a call on whether controller must switch
> +      mode or not. In case of SDP/CDT, the phy would inform the controller to
> +      switch to device and in case of DCP, role switch doesn't need to happen.
> +

[Severity: Medium]
Should usb-role-switch be used on the QUSB2 PHY node to represent BC1.2
charger detection and role notification forwarding?

In Documentation/devicetree/bindings/usb/usb-drd.yaml, usb-role-switch
indicates that a device is capable of assigning the USB data role, which
on these platforms is the USB controller rather than the PHY.  Using it
here to make fwnode_usb_role_switch_get() match the PHY endpoint encodes
Linux driver notification routing into the Devicetree ABI, while BC1.2
detection capability is already implied by the PHY compatible string and
the DP/DM routing is already described by ports.

[Severity: Low]
Does this schema need type and port constraints for dtbs_check validation?

Because usb-role-switch is defined here without referencing usb-drd.yaml,
it is missing type: boolean.  In addition, since the PHY does not permit a
child usb-connector node and requires both port@0 (incoming notification)
and port@1 (outgoing notification to the controller) when intercepting
role switches, ports should list port@0 and port@1 under required and a
dependencies entry should require ports when usb-role-switch is present.
Also, the description has a typo ("SDP/CDT" instead of "SDP/CDP").

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261111-shikra-qusb2-charger-det-v1-0-5a9d919c66b5@oss.qualcomm.com?part=1

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 2/2] phy: qcom: qusb2: Add charger detection support
  2026-11-11 17:31 ` [PATCH 2/2] phy: qcom: qusb2: Add charger detection support Krishna Kurapati
@ 2026-11-11 19:23   ` sashiko-bot
  2026-11-12  0:40   ` kernel test robot
  1 sibling, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-11-11 19:23 UTC (permalink / raw)
  To: Krishna Kurapati
  Cc: linux-phy, devicetree, robh, conor+dt, vkoul, neil.armstrong,
	olteanv

Thank you for your contribution! Sashiko AI review found 10 potential
issues to consider:
- [High] phy: qcom: qusb2: unbalanced ref_clk accounting in
  qusb2_phy_init()
- [High] phy: qcom: qusb2: charger detection disables initialized PHY
- [High] phy: qcom: qusb2: PHY left enabled on USB_ROLE_NONE disconnect
- [High] phy: qcom: qusb2: missing teardown of work and role switches on
  unbind
- [High] phy: qcom: qusb2: missing Kconfig dependency on USB_ROLE_SWITCH
- [Medium] phy: qcom: qusb2: clock and regulator leak in
  qusb2_phy_init()
- [Medium] phy: qcom: qusb2: race between qusb2_role_switch_set() and
  work
- [Medium] phy: qcom: qusb2: mismatched register writes in
  qusb_phy_drive_dp_pulse()
- [Low] phy: qcom: qusb2: undocumented new members in struct qusb2_phy
  kernel-doc
- [Low] phy: qcom: qusb2: hardcoded charger name in
  qusb_phy_notify_charger()
--

commit 24edde049bf5427e2a864a633231dd8850451f54
Author: Krishna Kurapati <krishna.kurapati@oss.qualcomm.com>

phy: qcom: qusb2: Add charger detection support

This commit adds BC 1.2 charger detection to the QUSB2 PHY driver and
registers a USB role switch to intercept role transitions and forward
device mode to the controller only after detecting SDP or CDP.

> diff --git a/drivers/phy/qualcomm/phy-qcom-qusb2.c b/drivers/phy/qualcomm/phy-qcom-qusb2.c
> index 2e5fdb620cb2d..549dc8d1378a4 100644
> --- a/drivers/phy/qualcomm/phy-qcom-qusb2.c
> +++ b/drivers/phy/qualcomm/phy-qcom-qusb2.c
[ ... ]
> @@ -478,9 +536,425 @@ struct qusb2_phy {
>  	const struct qusb2_phy_cfg *cfg;
>  	bool has_se_clk_scheme;
>  	bool phy_initialized;
> +	bool is_phy_enabled;
>  	enum phy_mode mode;
> +	int vbus_active;
> +	int id_state;
> +	struct power_supply	*usb_psy;
> +	struct delayed_work	port_det_w;
> +	enum port_state		port_state;
> +	unsigned int		dcd_timeout;
> +	bool			dcp_charger;
> +
> +	struct usb_role_switch	*connector_role_sw;
> +	struct usb_role_switch	*controller_role_sw;
> +	enum usb_role		current_role;
> +	struct mutex		role_lock;
>  };

[Severity: Low]
struct qusb2_phy has a kernel-doc comment above it, so adding these 12
members without @member descriptions will trigger scripts/kernel-doc
warnings under W=1.

> +static int qusb_phy_notify_charger(struct qusb2_phy *qphy,
> +					enum power_supply_usb_type charger_type)
> +{
> +	union power_supply_propval pval = {0};
> +
> +	if (!qphy->usb_psy) {
> +		qphy->usb_psy = power_supply_get_by_name("pm4125-charger");
> +		if (!qphy->usb_psy) {
> +			dev_err(&qphy->phy->dev, "could not get usb power supply\n");
> +			return -ENODEV;
> +		}
> +	}

[Severity: Low]
Should this look up the charger power supply via a firmware reference
(such as devm_power_supply_get_by_reference()) instead of hardcoding
"pm4125-charger" in the generic QUSB2 PHY driver?  Any platform with
usb-role-switch enabled whose charger power supply uses a different name
will fail with -ENODEV here.

[ ... ]

> +static int qusb_phy_drive_dp_pulse(struct qusb2_phy *qphy)
> +{
> +	int ret;
> +
> +	dev_dbg(&qphy->phy->dev, "connected to a CDP, drive DP up\n");
> +
> +	ret = qusb2_phy_enable_phy(qphy);
> +	if (ret)
> +		return ret;
> +
> +	ret = reset_control_assert(qphy->phy_reset);
> +	if (ret)
> +		dev_err(&qphy->phy->dev, "phy assert failed\n");
> +	usleep_range(100, 150);
> +	ret = reset_control_deassert(qphy->phy_reset);
> +	if (ret)
> +		dev_err(&qphy->phy->dev, "phy deassert failed\n");
> +
> +	writel_relaxed(CLAMP_N_EN | FREEZIO_N | POWER_DOWN,
> +			qphy->base + qphy->cfg->regs[QUSB2PHY_PORT_POWERDOWN]);
> +	writel_relaxed(TERM_SELECT | XCVR_SELECT_FS | OP_MODE_NON_DRIVE |
> +			SUSPEND_N, qphy->base + QUSB2PHY_PORT_UTMI_CTRL1);
> +	writel_relaxed(UTMI_ULPI_SEL | UTMI_TEST_MUX_SEL,
> +			qphy->base + QUSB2PHY_PORT_UTMI_CTRL2);
> +	writel_relaxed(PLL_RESET_N_CNT_5,
> +			qphy->base + QUSB2PHY_PLL_AUTOPGM_CTL1);
> +	writel_relaxed(CLAMP_N_EN | FREEZIO_N,
> +			qphy->base + qphy->cfg->regs[QUSB2PHY_PORT_POWERDOWN]);
> +	writel_relaxed(REF_BUF_EN | REXT_EN | PLL_BYPASSNL | REXT_TRIM_0,
> +			qphy->base + QUSB2PHY_PLL_PWR_CTRL);
> +
> +	usleep_range(5, 10);
> +
> +	writel_relaxed(0x15, qphy->base + QUSB2PHY_PLL_AUTOPGM_CTL1);
> +	writel_relaxed(PLL_RESET_N | PLL_RESET_N_CNT_5,
> +			qphy->base + QUSB2PHY_PLL_AUTOPGM_CTL1);
> +	writel_relaxed(0x00, qphy->base + QUSB2PHY_PORT_QC1);
> +	writel_relaxed(0x00, qphy->base + QUSB2PHY_PORT_QC2);
> +
> +	usleep_range(50, 60);
> +
> +	writel_relaxed(RDP_UP_EN, qphy->base + QUSB2PHY_PORT_QC2);
> +
> +	msleep(DP_PULSE_WIDTH_MSEC);
> +
> +	writel_relaxed(CLAMP_N_EN | FREEZIO_N | POWER_DOWN,
> +			qphy->base + qphy->cfg->regs[QUSB2PHY_PORT_POWERDOWN]);
> +	writel_relaxed(PLL_AUTOPGM_EN | PLL_RESET_N | PLL_RESET_N_CNT_5,
> +			qphy->base + QUSB2PHY_PLL_AUTOPGM_CTL1);
> +	writel_relaxed(UTMI_ULPI_SEL, qphy->base + QUSB2PHY_PORT_UTMI_CTRL2);
> +	writel_relaxed(TERM_SELECT, qphy->base + QUSB2PHY_PORT_UTMI_CTRL1);
> +
> +	qusb2_phy_disable_phy(qphy);
> +
> +	return 0;
> +}

[Severity: Medium]
Are the register values and relaxed writes in qusb_phy_drive_dp_pulse()
intended?

The bring-up sequence in qusb_phy_chg_det_enable_seq() writes 0x17 to
QUSB2PHY_PLL_PWR_CTRL, whereas REF_BUF_EN | REXT_EN | PLL_BYPASSNL |
REXT_TRIM_0 evaluates to 0x53 because REF_BUF_EN is defined as BIT(6)
rather than BIT(2).  Below that, QUSB2PHY_PLL_AUTOPGM_CTL1 is written
twice with 0x15 (raw 0x15 followed by PLL_RESET_N | PLL_RESET_N_CNT_5),
and the four teardown writes at the end use writel_relaxed() without a
readback flush right before qusb2_phy_disable_phy() gates cfg_ahb_clk.

[ ... ]

> +	case PORT_PRIMARY_IN_PROGRESS:
> +		if (!qphy->vbus_active) {
> +			dev_dbg(&qphy->phy->dev, "VBUS lost during primary detection, aborting\n");
> +			qusb2_phy_disable_phy(qphy);
> +			qphy->port_state = PORT_DISCONNECTED;
> +			break;
> +		}
> +
> +		status = qusb_phy_chg_det_status(qphy, STATE_PRIMARY);
> +		if (status) {
> +			dev_dbg(&qphy->phy->dev,
> +				"primary detection positive, starting secondary detection\n");
> +			qusb_phy_chg_det_enable_seq(qphy, STATE_SECONDARY);
> +			qphy->port_state = PORT_SECONDARY_IN_PROGRESS;
> +			delay = CHG_SECONDARY_DET_TIME_MSEC;
> +
> +		} else {
> +			dev_dbg(&qphy->phy->dev, "charger detected: SDP (standard USB host)\n");
> +			qusb2_phy_disable_phy(qphy);
> +			qusb_phy_notify_charger(qphy, POWER_SUPPLY_USB_TYPE_SDP);
> +			qusb2_phy_forward_role(qphy, USB_ROLE_DEVICE);
> +			qphy->port_state = PORT_CHG_DET_DONE;
> +		}
> +		break;

[Severity: High]
What happens when charger detection runs after the USB controller has
already called phy_init()?

Controller drivers such as dwc3 call phy_init() -> qusb2_phy_init() during
probe, which sets qphy->is_phy_enabled = true and qphy->phy_initialized =
true.  When USB_ROLE_DEVICE later triggers qusb_phy_port_state_work(),
qusb_phy_chg_det_enable_seq() overwrites the operational PHY registers (and
qusb_phy_drive_dp_pulse() resets the PHY on CDP), and then
qusb2_phy_disable_phy() disables all clocks and regulators while
qphy->phy_initialized remains true.  Forwarding USB_ROLE_DEVICE then puts
the controller into device mode with a powered-down, unconfigured PHY, and
a subsequent qusb2_phy_runtime_suspend() or qusb2_phy_exit() will access
MMIO registers with cfg_ahb_clk and regulators disabled.

[ ... ]

> @@ -767,29 +1241,16 @@ static int qusb2_phy_init(struct phy *phy)
>  
>  	dev_vdbg(&phy->dev, "%s(): Initializing QUSB2 phy\n", __func__);
>  
> -	/* turn on regulator supplies */
> -	ret = regulator_bulk_enable(ARRAY_SIZE(qphy->vregs), qphy->vregs);
> +	/* Power up regulators and enable clocks (including ref_clk if needed). */
> +	ret = qusb2_phy_enable_phy(qphy);
>  	if (ret)
>  		return ret;
>  
> -	ret = clk_prepare_enable(qphy->iface_clk);
> -	if (ret) {
> -		dev_err(&phy->dev, "failed to enable iface_clk, %d\n", ret);
> -		goto poweroff_phy;
> -	}
> -
> -	/* enable ahb interface clock to program phy */
> -	ret = clk_prepare_enable(qphy->cfg_ahb_clk);
> -	if (ret) {
> -		dev_err(&phy->dev, "failed to enable cfg ahb clock, %d\n", ret);
> -		goto disable_iface_clk;
> -	}
> -
>  	/* Perform phy reset */
>  	ret = reset_control_assert(qphy->phy_reset);
>  	if (ret) {
>  		dev_err(&phy->dev, "failed to assert phy_reset, %d\n", ret);
> -		goto disable_ahb_clk;
> +		return ret;
>  	}

[Severity: Medium]
Does returning directly when reset_control_assert() fails leak the clocks
and regulators enabled by qusb2_phy_enable_phy() and leave
qphy->is_phy_enabled set to true?

>  	/* 100 us delay to keep PHY in reset mode */
>  	usleep_range(100, 150);
>  
>  	ret = reset_control_deassert(qphy->phy_reset);
>  	if (ret) {
>  		dev_err(&phy->dev, "failed to de-assert phy_reset, %d\n", ret);
> -		goto disable_ahb_clk;
> +		goto assert_phy_reset;
>  	}
>  
>  	/* Disable the PHY */
> @@ -887,23 +1348,15 @@ static int qusb2_phy_init(struct phy *phy)
>  		dev_err(&phy->dev,
>  			"QUSB2PHY pll lock failed: status reg = %x\n", val);
>  		ret = -EBUSY;
> -		goto disable_ref_clk;
> +		goto assert_phy_reset;
>  	}
>  	qphy->phy_initialized = true;
>  
>  	return 0;
>  
> -disable_ref_clk:
> -	if (!qphy->has_se_clk_scheme)
> -		clk_disable_unprepare(qphy->ref_clk);
>  assert_phy_reset:
>  	reset_control_assert(qphy->phy_reset);
> -disable_ahb_clk:
> -	clk_disable_unprepare(qphy->cfg_ahb_clk);
> -disable_iface_clk:
> -	clk_disable_unprepare(qphy->iface_clk);
> -poweroff_phy:
> -	regulator_bulk_disable(ARRAY_SIZE(qphy->vregs), qphy->vregs);
> +	qusb2_phy_disable_phy(qphy);
>  
>  	return ret;
>  }

[Severity: High]
Does moving clk_prepare_enable(qphy->ref_clk) into qusb2_phy_enable_phy()
unbalance ref_clk across both single-ended and differential clock
platforms?

qphy->has_se_clk_scheme starts as false and is only determined halfway
through qusb2_phy_init() from cfg->se_clk_scheme_default and qphy->tcsr:

drivers/phy/qualcomm/phy-qcom-qusb2.c:qusb2_phy_init() {
    ...
	qphy->has_se_clk_scheme = cfg->se_clk_scheme_default;
    ...
	if (!qphy->has_se_clk_scheme) {
		ret = clk_prepare_enable(qphy->ref_clk);
    ...
}

Because qusb2_phy_enable_phy() runs at the start of qusb2_phy_init() (or
earlier during charger detection) while qphy->has_se_clk_scheme is still
false, it always enables ref_clk.  On single-ended platforms,
qusb2_phy_init() then sets qphy->has_se_clk_scheme = true, so
qusb2_phy_exit() and qusb2_phy_disable_phy() both skip disabling ref_clk
and leak the enable reference.  On differential platforms,
qusb2_phy_init() still calls clk_prepare_enable(qphy->ref_clk) a second
time, leaving ref_clk enabled twice so qusb2_phy_runtime_suspend() cannot
gate it and the assert_phy_reset error path leaks one reference.

[ ... ]

> +static int qusb2_role_switch_set(struct usb_role_switch *sw, enum usb_role role)
> +{
[ ... ]
> +	switch (role) {
> +	case USB_ROLE_DEVICE:
> +		/*
> +		 * VBUS is active; start charger detection.  The state machine
> +		 * will forward USB_ROLE_DEVICE controller only for SDP/CDP.
> +		 */
> +		qphy->vbus_active = 1;
> +		qphy->id_state = 1;
> +		qphy->port_state = PORT_UNKNOWN;
> +		queue_delayed_work(system_freezable_wq, &qphy->port_det_w, 0);
> +		break;
> +	case USB_ROLE_HOST:
> +		qphy->id_state = 0;
> +		qphy->vbus_active = 0;
> +		qphy->port_state = PORT_UNKNOWN;
> +		queue_delayed_work(system_freezable_wq, &qphy->port_det_w, 0);
> +		break;
> +	case USB_ROLE_NONE:
> +	default:
> +		qphy->vbus_active = 0;
> +		qphy->id_state = 1;
> +		cancel_delayed_work(&qphy->port_det_w);
> +		qusb2_phy_forward_role(qphy, USB_ROLE_NONE);
> +		break;
> +	}
> +	mutex_unlock(&qphy->role_lock);
> +
> +	return 0;
> +}

[Severity: High]
Does canceling port_det_w on USB_ROLE_NONE prevent the disconnect cleanup
in qusb_phy_port_state_work() from ever running?

When a DCP charger is detected in PORT_SECONDARY_IN_PROGRESS, the PHY is
left enabled with qphy->dcp_charger = true, and PORT_CHG_DET_DONE returns
once while qphy->vbus_active is 1 without re-queuing port_det_w.  When the
cable is unplugged (either after DCP detection or mid-detection in
PORT_DCD_IN_PROGRESS / PORT_PRIMARY_IN_PROGRESS /
PORT_SECONDARY_IN_PROGRESS), USB_ROLE_NONE sets qphy->vbus_active = 0 and
calls cancel_delayed_work(&qphy->port_det_w) instead of running the state
machine or calling qusb2_phy_disable_phy(), leaving the PHY clocks,
regulators, and DP/DM detection circuits enabled and qphy->dcp_charger set
to true.

[Severity: Medium]
Can qusb2_role_switch_set() also race with an already-running
qusb_phy_port_state_work()?

CPU0                                      CPU1 (kworker)
qusb_phy_port_state_work()
  case PORT_SECONDARY_IN_PROGRESS:
    qusb_phy_drive_dp_pulse()
      msleep(DP_PULSE_WIDTH_MSEC)
qusb2_role_switch_set(USB_ROLE_NONE)
  mutex_lock(&qphy->role_lock)
  qphy->vbus_active = 0
  cancel_delayed_work(&qphy->port_det_w)
  qusb2_phy_forward_role(USB_ROLE_NONE)
  mutex_unlock(&qphy->role_lock)
                                          qusb2_phy_forward_role(USB_ROLE_DEVICE)
                                            <-- stale role forwarded after NONE
                                          qphy->port_state = PORT_CHG_DET_DONE

qusb_phy_port_state_work() does not acquire qphy->role_lock, and
cancel_delayed_work() does not wait for an executing work item to finish,
so a disconnect during the 200 ms sleep in qusb_phy_drive_dp_pulse() (or
in qusb_phy_chg_det_enable_seq()) allows the worker to overwrite
qphy->port_state and forward a stale USB_ROLE_DEVICE after USB_ROLE_NONE.

[ ... ]

> @@ -1131,7 +1651,29 @@ static int qusb2_phy_probe(struct platform_device *pdev)
>  
>  	phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
>  
> -	return PTR_ERR_OR_ZERO(phy_provider);
> +	if (IS_ERR(phy_provider))
> +		return PTR_ERR(phy_provider);
> +
> +	mutex_init(&qphy->role_lock);
> +	INIT_DELAYED_WORK(&qphy->port_det_w, qusb_phy_port_state_work);
> +
> +	if (device_property_read_bool(dev, "usb-role-switch")) {
> +		struct usb_role_switch_desc role_sw_desc = {};
> +
> +		role_sw_desc.fwnode = dev->fwnode;
> +		role_sw_desc.set = qusb2_role_switch_set;
> +		role_sw_desc.driver_data = qphy;
> +
> +		qphy->connector_role_sw = usb_role_switch_register(dev, &role_sw_desc);
> +		if (IS_ERR(qphy->connector_role_sw)) {
> +			ret = PTR_ERR(qphy->connector_role_sw);
> +			dev_err(dev, "failed to register role switch, %d\n", ret);
> +			return ret;
> +		}
> +	}
> +	qphy->controller_role_sw = NULL;
> +
> +	return 0;
>  }

[Severity: High]
Does PHY_QCOM_QUSB2 in drivers/phy/qualcomm/Kconfig need a dependency on
or selection of USB_ROLE_SWITCH?

With CONFIG_PHY_QCOM_QUSB2=y and CONFIG_USB_ROLE_SWITCH=m, vmlinux fails
to link with undefined references to usb_role_switch_register(),
usb_role_switch_get(), usb_role_switch_set_role(),
usb_role_switch_get_drvdata(), and usb_role_string().  In addition, when
CONFIG_USB_ROLE_SWITCH=n, the usb_role_switch_register() stub returns
ERR_PTR(-ENODEV), failing probe for any QUSB2 PHY node with the
usb-role-switch property.

[Severity: High]
What cleans up connector_role_sw, port_det_w, controller_role_sw, and
usb_psy when the driver is unbound?

qusb2_phy_driver has no .remove callback and registers no devm cleanup
actions for these resources.  Unbinding the device leaves
qphy->connector_role_sw registered and qphy->port_det_w queued or running
after devm frees qphy and unmaps qphy->base (causing a use-after-free),
and leaks the references acquired via usb_role_switch_get() and
power_supply_get_by_name().

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261111-shikra-qusb2-charger-det-v1-0-5a9d919c66b5@oss.qualcomm.com?part=2

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 2/2] phy: qcom: qusb2: Add charger detection support
  2026-11-11 17:31 ` [PATCH 2/2] phy: qcom: qusb2: Add charger detection support Krishna Kurapati
  2026-11-11 19:23   ` sashiko-bot
@ 2026-11-12  0:40   ` kernel test robot
  1 sibling, 0 replies; 6+ messages in thread
From: kernel test robot @ 2026-11-12  0:40 UTC (permalink / raw)
  To: Krishna Kurapati, Vinod Koul, Neil Armstrong,
	Manivannan Sadhasivam, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Wesley Cheng, Maxime Chevallier, Philipp Zabel
  Cc: oe-kbuild-all, linux-arm-msm, linux-phy, devicetree, linux-kernel,
	Krishna Kurapati

Hi Krishna,

kernel test robot noticed the following build warnings:

[auto build test WARNING on 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4]

url:    https://github.com/intel-lab-lkp/linux/commits/Krishna-Kurapati/dt-bindings-phy-qcom-qusb2-Add-support-for-remote-endpoints/20261111-230126
base:   6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
patch link:    https://lore.kernel.org/r/20261111-shikra-qusb2-charger-det-v1-2-5a9d919c66b5%40oss.qualcomm.com
patch subject: [PATCH 2/2] phy: qcom: qusb2: Add charger detection support
config: arm64-defconfig (https://download.01.org/0day-ci/archive/20261112/202611120841.GECRO9c3-lkp@intel.com/config)
compiler: aarch64-linux-gcc (GCC) 16.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261112/202611120841.GECRO9c3-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202611120841.GECRO9c3-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'is_phy_enabled' not described in 'qusb2_phy'
>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'vbus_active' not described in 'qusb2_phy'
>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'id_state' not described in 'qusb2_phy'
>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'usb_psy' not described in 'qusb2_phy'
>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'port_det_w' not described in 'qusb2_phy'
>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'port_state' not described in 'qusb2_phy'
>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'dcd_timeout' not described in 'qusb2_phy'
>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'dcp_charger' not described in 'qusb2_phy'
>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'connector_role_sw' not described in 'qusb2_phy'
>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'controller_role_sw' not described in 'qusb2_phy'
>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'current_role' not described in 'qusb2_phy'
>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'role_lock' not described in 'qusb2_phy'
>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'is_phy_enabled' not described in 'qusb2_phy'
>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'vbus_active' not described in 'qusb2_phy'
>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'id_state' not described in 'qusb2_phy'
>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'usb_psy' not described in 'qusb2_phy'
>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'port_det_w' not described in 'qusb2_phy'
>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'port_state' not described in 'qusb2_phy'
>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'dcd_timeout' not described in 'qusb2_phy'
>> Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'dcp_charger' not described in 'qusb2_phy'
   Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'connector_role_sw' not described in 'qusb2_phy'
   Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'controller_role_sw' not described in 'qusb2_phy'
   Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'current_role' not described in 'qusb2_phy'
   Warning: drivers/phy/qualcomm/phy-qcom-qusb2.c:552 struct member 'role_lock' not described in 'qusb2_phy'

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-11-12  0:41 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-11 17:31 [PATCH 0/2] Add charger detection support for Shikra Krishna Kurapati
2026-11-11 17:31 ` [PATCH 1/2] dt-bindings: phy: qcom,qusb2: Add support for remote endpoints Krishna Kurapati
2026-11-11 19:03   ` sashiko-bot
2026-11-11 17:31 ` [PATCH 2/2] phy: qcom: qusb2: Add charger detection support Krishna Kurapati
2026-11-11 19:23   ` sashiko-bot
2026-11-12  0:40   ` kernel test robot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox