All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support
@ 2026-06-21  2:06 Peng Fan (OSS)
  2026-06-21  2:06 ` [PATCH v2 01/10] usb: tcpm: avoid hard reset for sink-only ports on Source_Cap timeout Peng Fan (OSS)
                   ` (10 more replies)
  0 siblings, 11 replies; 14+ messages in thread
From: Peng Fan (OSS) @ 2026-06-21  2:06 UTC (permalink / raw)
  To: u-boot, NXP i.MX U-Boot Team, Sebastian Reichel, Marek Vasut,
	Fabio Estevam, Wang Jie
  Cc: Tom Rini, Patrice Chotard, Yao Zi, Mattijs Korpershoek,
	Jonas Karlman, Kever Yang, Kaustabh Chakraborty, Casey Connolly,
	Ye Li, Sam Protsenko, Stefano Babic, Marek Vasut, Alice Guo,
	Balaji Selvanathan, Peng Fan

[V2] Just resend this patchset without
"usb: tcpm: fix inverted poll condition in tcpm_pd_transmit()"

This series adds TCPCI (Type-C Port Controller Interface) driver support
to U-Boot's TCPM framework and enables runtime USB host/device mode
switching on platforms using DWC3 with Type-C connectors.

The work is motivated by the NXP i.MX8MP EVK, which has a PTN5110 TCPCI
chip managing a USB Type-C data port. The goal is to support both
"usb start" (host mode, for USB storage) and "ums"/"fastboot" (device
mode, for flashing) on the same Type-C port, switchable at runtime.

The series breaks down into three logical groups:

TCPM bugfixes (patches 1-3):
  Fix three issues discovered in the upstream TCPM state machine during
  PD sink testing with various chargers:
  - Inverted poll condition causing transmit to always time out
  - Unnecessary hard resets on sink-only ports when source doesn't
    respond to capabilities request
  - Premature exit from poll loop after SNK_READY, missing source
    follow-up messages (Get_Sink_Cap etc.) that trigger hard reset

TCPCI driver and TCPM API extensions (patches 4-7):
  - Lightweight PD negotiation tracing (RAM ring buffer, zero serial
    overhead) for debugging charger interop issues
  - TCPCI driver for NXP PTN5110 and compatible chips, ported from
    the Linux kernel with adaptations for U-Boot's polled model
  - New tcpm_setup_host_mode()/tcpm_setup_device_mode() APIs that
    configure Type-C CC role and detect cable orientation without
    running the full PD state machine -- appropriate for U-Boot's
    synchronous, command-driven execution model
  - pd-disable connector property support in tcpm_post_probe() to
    skip the PD state machine for data-only ports while keeping the
    TCPCI driver ops accessible

DWC3 OTG and board support (patches 8-11):
  - board_usb_init()/board_usb_cleanup() hooks in DWC3 generic glue,
    called during child probe/remove to allow board-level Type-C
    configuration before DWC3 core init
  - OTG dual-bind: when dr_mode="otg", bind both dwc3-generic-host
    and dwc3-generic-peripheral children; runtime mode switching via
    dwc3_otg_deactivate_sibling() which removes the active sibling
    before probing the new one
  - i.MX8MP EVK board support wiring it all together: TCPCI DT nodes,
    board_usb_init using TCPM APIs for CC role setup and SS mux
    control, PD sink configuration for the power port

Tested on NXP i.MX8MP EVK.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
Changes in v2:
- Drop "usb: tcpm: fix inverted poll condition in tcpm_pd_transmit"
  which was sent to list as a separate fix.
- Link to v1: https://patch.msgid.link/20260617-tcpci-dwc3-v1-0-b824fe1e9299@nxp.com

---
Peng Fan (9):
      usb: tcpm: avoid hard reset for sink-only ports on Source_Cap timeout
      usb: tcpm: continue polling after PD negotiation to handle source messages
      usb: tcpm: add lightweight PD negotiation tracing
      usb: tcpm: add TCPCI (Type-C Port Controller Interface) driver
      usb: tcpm: add setup_host_mode/device_mode/disable_src_vbus APIs
      usb: tcpm: skip state machine for pd-disable ports
      usb: dwc3: generic: support OTG dual-bind for runtime host/device switching
      board: imx8mp_evk: enable USB PD sink via TCPCI
      board: imx8mp_evk: add USB Type-C OTG support with TCPCI

Ye Li (1):
      usb: dwc3: generic: add board_usb_init/cleanup hooks

 arch/arm/dts/imx8mp-evk-u-boot.dtsi |  52 ++-
 board/nxp/imx8mp_evk/imx8mp_evk.c   |  78 ++++
 configs/imx8mp_evk_defconfig        |   3 +
 drivers/usb/dwc3/dwc3-generic.c     |  73 +++-
 drivers/usb/tcpm/Kconfig            |   9 +
 drivers/usb/tcpm/Makefile           |   1 +
 drivers/usb/tcpm/tcpci.c            | 778 ++++++++++++++++++++++++++++++++++++
 drivers/usb/tcpm/tcpm-internal.h    |  40 ++
 drivers/usb/tcpm/tcpm-uclass.c      |  87 ++++
 drivers/usb/tcpm/tcpm.c             |  46 ++-
 include/usb/tcpci.h                 | 185 +++++++++
 include/usb/tcpm.h                  |   4 +
 12 files changed, 1350 insertions(+), 6 deletions(-)
---
base-commit: debaadb75013a6d3706a2137d540bf4660475e3e
change-id: 20260617-tcpci-dwc3-653a5d9c8893

Best regards,
--  
Peng Fan <peng.fan@nxp.com>


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

* [PATCH v2 01/10] usb: tcpm: avoid hard reset for sink-only ports on Source_Cap timeout
  2026-06-21  2:06 [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support Peng Fan (OSS)
@ 2026-06-21  2:06 ` Peng Fan (OSS)
  2026-06-21  2:06 ` [PATCH v2 02/10] usb: tcpm: continue polling after PD negotiation to handle source messages Peng Fan (OSS)
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 14+ messages in thread
From: Peng Fan (OSS) @ 2026-06-21  2:06 UTC (permalink / raw)
  To: u-boot, NXP i.MX U-Boot Team, Sebastian Reichel, Marek Vasut,
	Fabio Estevam, Wang Jie
  Cc: Tom Rini, Patrice Chotard, Yao Zi, Mattijs Korpershoek,
	Jonas Karlman, Kever Yang, Kaustabh Chakraborty, Casey Connolly,
	Ye Li, Sam Protsenko, Stefano Babic, Marek Vasut, Alice Guo,
	Balaji Selvanathan, Peng Fan

From: Peng Fan <peng.fan@nxp.com>

When a sink-only port times out waiting for Source_Capabilities in
SNK_WAIT_CAPABILITIES, the current code falls through to
hard_reset_state() which sends a Hard Reset. For sink-only ports
powered via VBUS, this causes the source to cut VBUS, resetting
the board.

Unlike DRP ports which can potentially recover by switching roles,
sink-only ports have no recovery path after a Hard Reset induced
VBUS loss. Instead, gracefully fall back to SNK_READY to operate
at the Type-C default current.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/usb/tcpm/tcpm.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/tcpm/tcpm.c b/drivers/usb/tcpm/tcpm.c
index d6c0be82333..25ff0926cdf 100644
--- a/drivers/usb/tcpm/tcpm.c
+++ b/drivers/usb/tcpm/tcpm.c
@@ -1647,6 +1647,8 @@ static void run_state_machine(struct udevice *dev)
 			port->vbus_never_low = false;
 			tcpm_set_state(dev, SOFT_RESET_SEND,
 				       PD_T_SINK_WAIT_CAP);
+		} else if (port->port_type == TYPEC_PORT_SNK) {
+			tcpm_set_state(dev, SNK_READY, 0);
 		} else {
 			tcpm_set_state(dev, hard_reset_state(port),
 				       PD_T_SINK_WAIT_CAP);
@@ -1770,9 +1772,13 @@ static void run_state_machine(struct udevice *dev)
 		port->message_id = 0;
 		port->rx_msgid = -1;
 		if (tcpm_pd_send_control(dev, PD_CTRL_SOFT_RESET))
-			tcpm_set_state_cond(dev, hard_reset_state(port), 0);
+			tcpm_set_state_cond(dev,
+					    port->port_type == TYPEC_PORT_SNK ?
+					    SNK_READY : hard_reset_state(port), 0);
 		else
-			tcpm_set_state_cond(dev, hard_reset_state(port),
+			tcpm_set_state_cond(dev,
+					    port->port_type == TYPEC_PORT_SNK ?
+					    SNK_READY : hard_reset_state(port),
 					    PD_T_SENDER_RESPONSE);
 		break;
 

-- 
2.51.0


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

* [PATCH v2 02/10] usb: tcpm: continue polling after PD negotiation to handle source messages
  2026-06-21  2:06 [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support Peng Fan (OSS)
  2026-06-21  2:06 ` [PATCH v2 01/10] usb: tcpm: avoid hard reset for sink-only ports on Source_Cap timeout Peng Fan (OSS)
@ 2026-06-21  2:06 ` Peng Fan (OSS)
  2026-06-21  2:06 ` [PATCH v2 03/10] usb: tcpm: add lightweight PD negotiation tracing Peng Fan (OSS)
                   ` (8 subsequent siblings)
  10 siblings, 0 replies; 14+ messages in thread
From: Peng Fan (OSS) @ 2026-06-21  2:06 UTC (permalink / raw)
  To: u-boot, NXP i.MX U-Boot Team, Sebastian Reichel, Marek Vasut,
	Fabio Estevam, Wang Jie
  Cc: Tom Rini, Patrice Chotard, Yao Zi, Mattijs Korpershoek,
	Jonas Karlman, Kever Yang, Kaustabh Chakraborty, Casey Connolly,
	Ye Li, Sam Protsenko, Stefano Babic, Marek Vasut, Alice Guo,
	Balaji Selvanathan, Peng Fan

From: Peng Fan <peng.fan@nxp.com>

After reaching SNK_READY or SRC_READY, the poll loop exits immediately.
However, some PD sources send follow-up messages (e.g., Get_Sink_Cap,
Get_Status) shortly after the explicit contract is established. Since
the TCPCI hardware auto-sends GoodCRC for received messages, the
source expects a timely response. If no response arrives within
SenderResponseTimer (24-30ms), the source sends a Hard Reset,
causing VBUS to drop and resetting VBUS-powered boards.

Add a 500ms settling period after reaching Ready state to process
any follow-up messages from the source before entering low power
mode and disabling PD message reception.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/usb/tcpm/tcpm.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/usb/tcpm/tcpm.c b/drivers/usb/tcpm/tcpm.c
index 25ff0926cdf..d6af0fae7c1 100644
--- a/drivers/usb/tcpm/tcpm.c
+++ b/drivers/usb/tcpm/tcpm.c
@@ -2252,6 +2252,16 @@ static void tcpm_poll_event(struct udevice *dev)
 		tcpm_check_and_run_delayed_work(dev);
 	}
 
+	if (port->state == SNK_READY || port->state == SRC_READY) {
+		int settle;
+
+		for (settle = 0; settle < 1000; settle++) {
+			drvops->poll_event(dev);
+			udelay(500);
+			tcpm_check_and_run_delayed_work(dev);
+		}
+	}
+
 	if (port->state != SNK_READY && port->state != SRC_READY)
 		dev_warn(dev, "TCPM: exit in state %s\n",
 			 tcpm_states[port->state]);

-- 
2.51.0


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

* [PATCH v2 03/10] usb: tcpm: add lightweight PD negotiation tracing
  2026-06-21  2:06 [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support Peng Fan (OSS)
  2026-06-21  2:06 ` [PATCH v2 01/10] usb: tcpm: avoid hard reset for sink-only ports on Source_Cap timeout Peng Fan (OSS)
  2026-06-21  2:06 ` [PATCH v2 02/10] usb: tcpm: continue polling after PD negotiation to handle source messages Peng Fan (OSS)
@ 2026-06-21  2:06 ` Peng Fan (OSS)
  2026-06-21  2:06 ` [PATCH v2 04/10] usb: tcpm: add TCPCI (Type-C Port Controller Interface) driver Peng Fan (OSS)
                   ` (7 subsequent siblings)
  10 siblings, 0 replies; 14+ messages in thread
From: Peng Fan (OSS) @ 2026-06-21  2:06 UTC (permalink / raw)
  To: u-boot, NXP i.MX U-Boot Team, Sebastian Reichel, Marek Vasut,
	Fabio Estevam, Wang Jie
  Cc: Tom Rini, Patrice Chotard, Yao Zi, Mattijs Korpershoek,
	Jonas Karlman, Kever Yang, Kaustabh Chakraborty, Casey Connolly,
	Ye Li, Sam Protsenko, Stefano Babic, Marek Vasut, Alice Guo,
	Balaji Selvanathan, Peng Fan

From: Peng Fan <peng.fan@nxp.com>

Add a zero-overhead ring buffer trace facility for debugging USB PD
negotiation timing issues. Trace points record timestamp and event
to a RAM buffer without any serial output, avoiding interference
with PD protocol timing constraints.

Trace points cover the critical PD sink negotiation path:
- Source_Capabilities reception
- Request construction and transmission
- TX completion polling
- Accept/PS_RDY reception
- SNK_READY state entry
- Post-ready polling

The trace buffer can be dumped on demand via pd_trace_dump() for
post-mortem analysis of PD negotiation failures.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/usb/tcpm/tcpm-internal.h | 40 ++++++++++++++++++++++++++++++++++++++++
 drivers/usb/tcpm/tcpm.c          | 17 +++++++++++++++++
 2 files changed, 57 insertions(+)

diff --git a/drivers/usb/tcpm/tcpm-internal.h b/drivers/usb/tcpm/tcpm-internal.h
index 56144209002..138b0dc089e 100644
--- a/drivers/usb/tcpm/tcpm-internal.h
+++ b/drivers/usb/tcpm/tcpm-internal.h
@@ -166,6 +166,46 @@ struct tcpm_port {
 	unsigned long delay_target;
 };
 
+#define PD_TRACE_MAX 32
+
+struct pd_trace_entry {
+	unsigned long ts_us;
+	const char *event;
+	u32 val;
+};
+
+struct pd_trace_buf {
+	struct pd_trace_entry entries[PD_TRACE_MAX];
+	int count;
+};
+
+extern struct pd_trace_buf g_pd_trace;
+
+static inline void pd_trace(const char *event, u32 val)
+{
+	if (g_pd_trace.count < PD_TRACE_MAX) {
+		struct pd_trace_entry *e = &g_pd_trace.entries[g_pd_trace.count++];
+
+		e->ts_us = timer_get_us();
+		e->event = event;
+		e->val = val;
+	}
+}
+
+static inline void pd_trace_dump(void)
+{
+	int i;
+	unsigned long base = g_pd_trace.count > 0 ? g_pd_trace.entries[0].ts_us : 0;
+
+	printf("=== PD TRACE (%d entries) ===\n", g_pd_trace.count);
+	for (i = 0; i < g_pd_trace.count; i++) {
+		struct pd_trace_entry *e = &g_pd_trace.entries[i];
+
+		printf("[+%7luus] %-30s 0x%08x\n",
+		       e->ts_us - base, e->event, e->val);
+	}
+}
+
 extern const char * const tcpm_states[];
 
 int tcpm_post_probe(struct udevice *dev);
diff --git a/drivers/usb/tcpm/tcpm.c b/drivers/usb/tcpm/tcpm.c
index d6af0fae7c1..1ce46118fea 100644
--- a/drivers/usb/tcpm/tcpm.c
+++ b/drivers/usb/tcpm/tcpm.c
@@ -23,6 +23,8 @@ const char * const tcpm_states[] = {
 	FOREACH_TCPM_STATE(GENERATE_TCPM_STRING)
 };
 
+struct pd_trace_buf g_pd_trace;
+
 const char * const typec_pd_rev_name[] = {
 	[PD_REV10]		= "rev1",
 	[PD_REV20]		= "rev2",
@@ -193,7 +195,9 @@ static int tcpm_pd_transmit(struct udevice *dev,
 		dev_dbg(dev, "TCPM: PD TX, type: %#x\n", type);
 
 	port->tx_complete = false;
+	pd_trace("PD_TX_START", type);
 	ret = drvops->pd_transmit(dev, type, msg, port->negotiated_rev);
+	pd_trace("PD_TX_DONE", ret);
 	if (ret < 0)
 		return ret;
 
@@ -213,6 +217,7 @@ static int tcpm_pd_transmit(struct udevice *dev,
 		return ret;
 	}
 
+	pd_trace("TX_POLL_DONE", port->tx_status);
 	switch (port->tx_status) {
 	case TCPC_TX_SUCCESS:
 		port->message_id = (port->message_id + 1) & PD_HEADER_ID_MASK;
@@ -626,6 +631,7 @@ static void tcpm_pd_data_request(struct udevice *dev,
 
 	switch (type) {
 	case PD_DATA_SOURCE_CAP:
+		pd_trace("RX_SOURCE_CAP", cnt);
 		for (i = 0; i < cnt; i++)
 			port->source_caps[i] = le32_to_cpu(msg->payload[i]);
 
@@ -668,6 +674,7 @@ static void tcpm_pd_data_request(struct udevice *dev,
 		 * but be prepared to keep waiting for VBUS after it was
 		 * handled.
 		 */
+		pd_trace("SET_SNK_NEGOTIATE", 0);
 		tcpm_set_state(dev, SNK_NEGOTIATE_CAPABILITIES, 0);
 		break;
 	case PD_DATA_REQUEST:
@@ -736,6 +743,7 @@ static void tcpm_pd_ctrl_request(struct udevice *dev,
 	case PD_CTRL_GOTO_MIN:
 		break;
 	case PD_CTRL_PS_RDY:
+		pd_trace("RX_PS_RDY", port->state);
 		switch (port->state) {
 		case SNK_TRANSITION_SINK:
 			if (port->vbus_present) {
@@ -775,6 +783,7 @@ static void tcpm_pd_ctrl_request(struct udevice *dev,
 		}
 		break;
 	case PD_CTRL_ACCEPT:
+		pd_trace("RX_ACCEPT", port->state);
 		switch (port->state) {
 		case SNK_NEGOTIATE_CAPABILITIES:
 			tcpm_set_state(dev, SNK_TRANSITION_SINK, 0);
@@ -1655,9 +1664,13 @@ static void run_state_machine(struct udevice *dev)
 		}
 		break;
 	case SNK_NEGOTIATE_CAPABILITIES:
+		pd_trace("SNK_NEGOTIATE_ENTER", 0);
 		port->pd_capable = true;
 		port->hard_reset_count = 0;
 		ret = tcpm_pd_send_request(dev);
+		pd_trace("SNK_NEGOTIATE_SENT", ret);
+		pd_trace("REQ_MV", port->req_supply_voltage);
+		pd_trace("REQ_MA", port->req_current_limit);
 		if (ret < 0) {
 			/* Let the Source send capabilities again. */
 			tcpm_set_state(dev, SNK_WAIT_CAPABILITIES, 0);
@@ -1672,8 +1685,10 @@ static void run_state_machine(struct udevice *dev)
 			       PD_T_PS_TRANSITION);
 		break;
 	case SNK_READY:
+		pd_trace("SNK_READY", 0);
 		port->update_sink_caps = false;
 		tcpm_typec_connect(port);
+
 		/*
 		 * Here poll_event_cnt is cleared, waiting for self-powered Type-C devices
 		 * to send DR_swap Messge until 1s (TCPM_POLL_EVENT_TIME_OUT * 500us)timeout
@@ -2255,11 +2270,13 @@ static void tcpm_poll_event(struct udevice *dev)
 	if (port->state == SNK_READY || port->state == SRC_READY) {
 		int settle;
 
+		pd_trace("POST_READY_POLL_START", port->state);
 		for (settle = 0; settle < 1000; settle++) {
 			drvops->poll_event(dev);
 			udelay(500);
 			tcpm_check_and_run_delayed_work(dev);
 		}
+		pd_trace("POST_READY_POLL_DONE", port->state);
 	}
 
 	if (port->state != SNK_READY && port->state != SRC_READY)

-- 
2.51.0


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

* [PATCH v2 04/10] usb: tcpm: add TCPCI (Type-C Port Controller Interface) driver
  2026-06-21  2:06 [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support Peng Fan (OSS)
                   ` (2 preceding siblings ...)
  2026-06-21  2:06 ` [PATCH v2 03/10] usb: tcpm: add lightweight PD negotiation tracing Peng Fan (OSS)
@ 2026-06-21  2:06 ` Peng Fan (OSS)
  2026-06-21  2:06 ` [PATCH v2 05/10] usb: tcpm: add setup_host_mode/device_mode/disable_src_vbus APIs Peng Fan (OSS)
                   ` (6 subsequent siblings)
  10 siblings, 0 replies; 14+ messages in thread
From: Peng Fan (OSS) @ 2026-06-21  2:06 UTC (permalink / raw)
  To: u-boot, NXP i.MX U-Boot Team, Sebastian Reichel, Marek Vasut,
	Fabio Estevam, Wang Jie
  Cc: Tom Rini, Patrice Chotard, Yao Zi, Mattijs Korpershoek,
	Jonas Karlman, Kever Yang, Kaustabh Chakraborty, Casey Connolly,
	Ye Li, Sam Protsenko, Stefano Babic, Marek Vasut, Alice Guo,
	Balaji Selvanathan, Peng Fan

From: Peng Fan <peng.fan@nxp.com>

Add a generic TCPCI-compliant driver for USB Type-C Port Controller
Interface chips such as NXP PTN5110. The driver implements the
dm_tcpm_ops interface and communicates with the TCPC via I2C.

Key features:
- Full TCPCI register-level implementation for PD sink negotiation
- Proper TX buffer handling (burst write from TX_HDR)
- Alert-based polling for RX, TX completion, CC and VBUS changes
- Re-entrant poll protection to prevent state machine corruption
- Low power mode with RX_DETECT disable and alert masking to
  prevent post-negotiation Hard Reset from PD sources
- Stale RX buffer drain during initialization for dead-battery
  boot scenarios

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/usb/tcpm/Kconfig  |   9 +
 drivers/usb/tcpm/Makefile |   1 +
 drivers/usb/tcpm/tcpci.c  | 778 ++++++++++++++++++++++++++++++++++++++++++++++
 include/usb/tcpci.h       | 185 +++++++++++
 4 files changed, 973 insertions(+)

diff --git a/drivers/usb/tcpm/Kconfig b/drivers/usb/tcpm/Kconfig
index 9be4b496e82..695c49b64ce 100644
--- a/drivers/usb/tcpm/Kconfig
+++ b/drivers/usb/tcpm/Kconfig
@@ -14,3 +14,12 @@ config TYPEC_FUSB302
 	  The Fairchild FUSB302 Type-C chip driver that works with
 	  Type-C Port Controller Manager to provide USB PD and USB
 	  Type-C functionalities.
+
+config TYPEC_TCPCI
+	tristate "TCPCI Type-C chip driver"
+	depends on DM && DM_I2C && TYPEC_TCPM
+	help
+	  Generic TCPCI (USB Type-C Port Controller Interface)
+	  compliant chip driver that works with Type-C Port
+	  Controller Manager to provide USB PD and USB Type-C
+	  functionalities. Supports NXP PTN5110 and similar.
diff --git a/drivers/usb/tcpm/Makefile b/drivers/usb/tcpm/Makefile
index 668d33155bf..5cabcc06e0a 100644
--- a/drivers/usb/tcpm/Makefile
+++ b/drivers/usb/tcpm/Makefile
@@ -2,3 +2,4 @@
 
 obj-$(CONFIG_TYPEC_TCPM) += tcpm.o tcpm-uclass.o
 obj-$(CONFIG_TYPEC_FUSB302) += fusb302.o
+obj-$(CONFIG_TYPEC_TCPCI) += tcpci.o
diff --git a/drivers/usb/tcpm/tcpci.c b/drivers/usb/tcpm/tcpci.c
new file mode 100644
index 00000000000..9ade6d16009
--- /dev/null
+++ b/drivers/usb/tcpm/tcpci.c
@@ -0,0 +1,778 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2026 NXP
+ *
+ * USB Type-C Port Controller Interface I2C driver for U-Boot.
+ */
+
+#define LOG_CATEGORY UCLASS_TCPM
+
+#include <dm.h>
+#include <i2c.h>
+#include <watchdog.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <dm/device_compat.h>
+#include <time.h>
+#include <usb/tcpm.h>
+#include <usb/tcpci.h>
+#include "tcpm-internal.h"
+
+#define PD_RETRY_COUNT_DEFAULT		3
+#define PD_RETRY_COUNT_3_0_OR_HIGHER	2
+
+struct tcpci_chip {
+	u16 alert_mask;
+	bool controls_vbus;
+	bool in_poll;
+};
+
+static int tcpci_read8(struct udevice *dev, u8 reg, u8 *val)
+{
+	int ret;
+
+	ret = dm_i2c_read(dev, reg, val, 1);
+	if (ret)
+		dev_err(dev, "cannot read 0x%02x, ret=%d\n", reg, ret);
+
+	dev_dbg(dev, "read 0x%02x, val=0x%02x\n", reg, *val);
+
+	return ret;
+}
+
+static int tcpci_write8(struct udevice *dev, u8 reg, u8 val)
+{
+	int ret;
+
+	ret = dm_i2c_write(dev, reg, &val, 1);
+	if (ret)
+		dev_err(dev, "cannot write 0x%02x to 0x%02x, ret=%d\n",
+			val, reg, ret);
+	dev_dbg(dev, "write 0x%02x, val=0x%02x\n", reg, val);
+
+	return ret;
+}
+
+static int tcpci_read16(struct udevice *dev, u8 reg, u16 *val)
+{
+	int ret;
+
+	ret = dm_i2c_read(dev, reg, (u8 *)val, 2);
+	if (ret)
+		dev_err(dev, "cannot read 16-bit 0x%02x, ret=%d\n", reg, ret);
+
+	dev_dbg(dev, "read16 0x%02x, val=0x%04x\n", reg, *val);
+
+	return ret;
+}
+
+static int tcpci_write16(struct udevice *dev, u8 reg, u16 val)
+{
+	int ret;
+
+	ret = dm_i2c_write(dev, reg, (u8 *)&val, 2);
+	if (ret)
+		dev_err(dev, "cannot write 16-bit 0x%04x to 0x%02x, ret=%d\n",
+			val, reg, ret);
+	dev_dbg(dev, "write16 0x%02x, val=0x%04x\n", reg, val);
+
+	return ret;
+}
+
+static int tcpci_get_connector_node(struct udevice *dev, ofnode *connector_node)
+{
+	dev_dbg(dev, "%s: entering\n", __func__);
+
+	*connector_node = dev_read_subnode(dev, "connector");
+	if (!ofnode_valid(*connector_node)) {
+		dev_err(dev, "'connector' node is not found\n");
+		return -ENODEV;
+	}
+
+	dev_dbg(dev, "%s: connector node found\n", __func__);
+	return 0;
+}
+
+static int tcpci_set_cc(struct udevice *dev, enum typec_cc_status cc)
+{
+	unsigned int reg;
+	int ret;
+	u8 power_status;
+	bool vconn_pres;
+	enum typec_cc_polarity polarity = TYPEC_POLARITY_CC1;
+
+	dev_dbg(dev, "%s: entering cc=%d\n", __func__, cc);
+
+	ret = tcpci_read8(dev, TCPC_POWER_STATUS, &power_status);
+	if (ret < 0)
+		return ret;
+
+	vconn_pres = !!(power_status & TCPC_POWER_STATUS_VCONN_PRES);
+	if (vconn_pres) {
+		u8 ctrl;
+
+		ret = tcpci_read8(dev, TCPC_TCPC_CTRL, &ctrl);
+		if (ret < 0)
+			return ret;
+
+		if (ctrl & TCPC_TCPC_CTRL_ORIENTATION)
+			polarity = TYPEC_POLARITY_CC2;
+	}
+
+	switch (cc) {
+	case TYPEC_CC_RA:
+		reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RA) |
+		       FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RA));
+		break;
+	case TYPEC_CC_RD:
+		reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RD) |
+		       FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RD));
+		break;
+	case TYPEC_CC_RP_DEF:
+		reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RP) |
+		       FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RP) |
+		       FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL,
+				  TCPC_ROLE_CTRL_RP_VAL_DEF));
+		break;
+	case TYPEC_CC_RP_1_5:
+		reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RP) |
+		       FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RP) |
+		       FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL,
+				  TCPC_ROLE_CTRL_RP_VAL_1_5));
+		break;
+	case TYPEC_CC_RP_3_0:
+		reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RP) |
+		       FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RP) |
+		       FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL,
+				  TCPC_ROLE_CTRL_RP_VAL_3_0));
+		break;
+	case TYPEC_CC_OPEN:
+	default:
+		reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_OPEN) |
+		       FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_OPEN));
+		break;
+	}
+
+	if (vconn_pres) {
+		if (polarity == TYPEC_POLARITY_CC2)
+			reg = (reg & ~TCPC_ROLE_CTRL_CC1) |
+			      FIELD_PREP(TCPC_ROLE_CTRL_CC1,
+					 TCPC_ROLE_CTRL_CC_OPEN);
+		else
+			reg = (reg & ~TCPC_ROLE_CTRL_CC2) |
+			      FIELD_PREP(TCPC_ROLE_CTRL_CC2,
+					 TCPC_ROLE_CTRL_CC_OPEN);
+	}
+
+	ret = tcpci_write8(dev, TCPC_ROLE_CTRL, (u8)reg);
+	if (ret < 0)
+		return ret;
+
+	dev_dbg(dev, "%s: done\n", __func__);
+	return 0;
+}
+
+static int tcpci_start_toggling(struct udevice *dev,
+				enum typec_port_type port_type,
+				enum typec_cc_status cc)
+{
+	unsigned int reg = 0;
+	int ret;
+
+	dev_dbg(dev, "%s: entering port_type=%d cc=%d\n", __func__,
+		port_type, cc);
+
+	switch (port_type) {
+	case TYPEC_PORT_DRP:
+		reg |= TCPC_ROLE_CTRL_DRP;
+		fallthrough;
+	case TYPEC_PORT_SRC:
+		switch (cc) {
+		default:
+		case TYPEC_CC_RP_DEF:
+			reg |= FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL,
+					  TCPC_ROLE_CTRL_RP_VAL_DEF);
+			break;
+		case TYPEC_CC_RP_1_5:
+			reg |= FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL,
+					  TCPC_ROLE_CTRL_RP_VAL_1_5);
+			break;
+		case TYPEC_CC_RP_3_0:
+			reg |= FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL,
+					  TCPC_ROLE_CTRL_RP_VAL_3_0);
+			break;
+		}
+
+		reg |= (FIELD_PREP(TCPC_ROLE_CTRL_CC1,
+				   TCPC_ROLE_CTRL_CC_RP) |
+			FIELD_PREP(TCPC_ROLE_CTRL_CC2,
+				   TCPC_ROLE_CTRL_CC_RP));
+		ret = tcpci_write8(dev, TCPC_ROLE_CTRL, (u8)reg);
+		if (ret < 0)
+			return ret;
+		ret = tcpci_write8(dev, TCPC_COMMAND, TCPC_CMD_LOOK4CONNECTION);
+		if (ret < 0)
+			return ret;
+		dev_dbg(dev, "%s: done\n", __func__);
+		return 0;
+	case TYPEC_PORT_SNK:
+		reg |= (FIELD_PREP(TCPC_ROLE_CTRL_CC1,
+				   TCPC_ROLE_CTRL_CC_RD) |
+			FIELD_PREP(TCPC_ROLE_CTRL_CC2,
+				   TCPC_ROLE_CTRL_CC_RD));
+		ret = tcpci_write8(dev, TCPC_ROLE_CTRL, (u8)reg);
+		if (ret < 0)
+			return ret;
+		dev_dbg(dev, "%s: done sink CC=Rd\n", __func__);
+		return 0;
+	}
+
+	return -EOPNOTSUPP;
+}
+
+static int tcpci_get_cc(struct udevice *dev, enum typec_cc_status *cc1,
+			enum typec_cc_status *cc2)
+{
+	unsigned int role_control, reg;
+	int ret;
+
+	dev_dbg(dev, "%s: entering\n", __func__);
+
+	ret = tcpci_read8(dev, TCPC_ROLE_CTRL, (u8 *)&role_control);
+	if (ret < 0)
+		return ret;
+
+	ret = tcpci_read8(dev, TCPC_CC_STATUS, (u8 *)&reg);
+	if (ret < 0)
+		return ret;
+
+	*cc1 = tcpci_to_typec_cc(FIELD_GET(TCPC_CC_STATUS_CC1, reg),
+				 reg & TCPC_CC_STATUS_TERM ||
+				 tcpc_presenting_rd(role_control, CC1));
+	*cc2 = tcpci_to_typec_cc(FIELD_GET(TCPC_CC_STATUS_CC2, reg),
+				 reg & TCPC_CC_STATUS_TERM ||
+				 tcpc_presenting_rd(role_control, CC2));
+
+	dev_dbg(dev, "%s: cc1=%d cc2=%d\n", __func__, *cc1, *cc2);
+	return 0;
+}
+
+static int tcpci_set_polarity(struct udevice *dev,
+			      enum typec_cc_polarity polarity)
+{
+	unsigned int reg;
+	enum typec_cc_status cc1, cc2;
+	int ret;
+
+	dev_dbg(dev, "%s: entering polarity=%d\n", __func__, polarity);
+
+	ret = tcpci_read8(dev, TCPC_ROLE_CTRL, (u8 *)&reg);
+	if (ret < 0)
+		return ret;
+
+	ret = tcpci_get_cc(dev, &cc1, &cc2);
+	if (ret < 0)
+		return ret;
+
+	if (reg & TCPC_ROLE_CTRL_DRP) {
+		reg &= ~TCPC_ROLE_CTRL_DRP;
+
+		if (polarity == TYPEC_POLARITY_CC2) {
+			reg &= ~TCPC_ROLE_CTRL_CC2;
+			if (cc2 == TYPEC_CC_RD)
+				reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC2,
+						  TCPC_ROLE_CTRL_CC_RP);
+			else if (cc2 >= TYPEC_CC_RP_DEF)
+				reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC2,
+						  TCPC_ROLE_CTRL_CC_RD);
+		} else {
+			reg &= ~TCPC_ROLE_CTRL_CC1;
+			if (cc1 == TYPEC_CC_RD)
+				reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC1,
+						  TCPC_ROLE_CTRL_CC_RP);
+			else if (cc1 >= TYPEC_CC_RP_DEF)
+				reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC1,
+						  TCPC_ROLE_CTRL_CC_RD);
+		}
+
+		/* Transitioning from DRP toggling: set unused CC to OPEN */
+		if (polarity == TYPEC_POLARITY_CC2)
+			reg = (reg & ~TCPC_ROLE_CTRL_CC1) |
+			      FIELD_PREP(TCPC_ROLE_CTRL_CC1,
+					 TCPC_ROLE_CTRL_CC_OPEN);
+		else
+			reg = (reg & ~TCPC_ROLE_CTRL_CC2) |
+			      FIELD_PREP(TCPC_ROLE_CTRL_CC2,
+					 TCPC_ROLE_CTRL_CC_OPEN);
+	}
+
+	ret = tcpci_write8(dev, TCPC_ROLE_CTRL, (u8)reg);
+	if (ret < 0)
+		return ret;
+
+	ret = tcpci_write8(dev, TCPC_TCPC_CTRL,
+			   (polarity == TYPEC_POLARITY_CC2) ?
+			   TCPC_TCPC_CTRL_ORIENTATION : 0);
+	if (ret < 0)
+		return ret;
+
+	dev_dbg(dev, "%s: done\n", __func__);
+	return 0;
+}
+
+static int tcpci_set_vconn(struct udevice *dev, bool enable)
+{
+	int ret;
+	u8 reg;
+
+	dev_dbg(dev, "%s: entering enable=%d\n", __func__, enable);
+
+	ret = tcpci_read8(dev, TCPC_POWER_CTRL, &reg);
+	if (ret < 0)
+		return ret;
+
+	if (enable)
+		reg |= TCPC_POWER_CTRL_VCONN_ENABLE;
+	else
+		reg &= ~TCPC_POWER_CTRL_VCONN_ENABLE;
+
+	ret = tcpci_write8(dev, TCPC_POWER_CTRL, reg);
+	if (ret < 0)
+		return ret;
+
+	dev_dbg(dev, "%s: done\n", __func__);
+	return 0;
+}
+
+static int tcpci_set_vbus(struct udevice *dev, bool on, bool charge)
+{
+	int ret;
+	u8 power_status;
+
+	dev_dbg(dev, "%s: entering on=%d charge=%d\n", __func__, on, charge);
+
+	ret = tcpci_read8(dev, TCPC_POWER_STATUS, &power_status);
+	if (ret < 0)
+		return ret;
+
+	dev_dbg(dev, "%s: POWER_STATUS=0x%02x VBUS_PRES=%d SOURCING=%d SINKING=%d\n",
+		__func__, power_status,
+		!!(power_status & TCPC_POWER_STATUS_VBUS_PRES),
+		!!(power_status & TCPC_POWER_STATUS_SOURCING_VBUS),
+		!!(power_status & TCPC_POWER_STATUS_SINKING_VBUS));
+
+	/*
+	 * If VBUS is present, don't send any VBUS COMMAND register writes
+	 * at all - the PTN5110 uses a shared power path and disabling
+	 * source or sink while VBUS is active cuts the board's power.
+	 */
+	if (power_status & TCPC_POWER_STATUS_VBUS_PRES) {
+		dev_dbg(dev, "%s: VBUS present, skipping all VBUS commands\n",
+			__func__);
+		return 0;
+	}
+
+	if (!on) {
+		ret = tcpci_write8(dev, TCPC_COMMAND,
+				   TCPC_CMD_DISABLE_SRC_VBUS);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (!charge) {
+		ret = tcpci_write8(dev, TCPC_COMMAND,
+				   TCPC_CMD_DISABLE_SINK_VBUS);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (on) {
+		ret = tcpci_write8(dev, TCPC_COMMAND,
+				   TCPC_CMD_SRC_VBUS_DEFAULT);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (charge) {
+		ret = tcpci_write8(dev, TCPC_COMMAND,
+				   TCPC_CMD_SINK_VBUS);
+		if (ret < 0)
+			return ret;
+	}
+
+	dev_dbg(dev, "%s: done\n", __func__);
+	return 0;
+}
+
+static int tcpci_set_pd_rx(struct udevice *dev, bool enable)
+{
+	u8 reg = 0;
+	int ret;
+
+	dev_dbg(dev, "%s: entering enable=%d\n", __func__, enable);
+
+	if (enable)
+		reg = TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET;
+
+	ret = tcpci_write8(dev, TCPC_RX_DETECT, reg);
+	if (ret < 0)
+		return ret;
+
+	dev_dbg(dev, "%s: done\n", __func__);
+	return 0;
+}
+
+static int tcpci_set_roles(struct udevice *dev, bool attached,
+			   enum typec_role role, enum typec_data_role data)
+{
+	unsigned int reg;
+	int ret;
+
+	dev_dbg(dev, "%s: entering attached=%d role=%d data=%d\n",
+		__func__, attached, role, data);
+
+	reg = FIELD_PREP(TCPC_MSG_HDR_INFO_REV, PD_REV20);
+	if (role == TYPEC_SOURCE)
+		reg |= TCPC_MSG_HDR_INFO_PWR_ROLE;
+	if (data == TYPEC_HOST)
+		reg |= TCPC_MSG_HDR_INFO_DATA_ROLE;
+
+	ret = tcpci_write8(dev, TCPC_MSG_HDR_INFO, (u8)reg);
+	if (ret < 0)
+		return ret;
+
+	dev_dbg(dev, "%s: done\n", __func__);
+	return 0;
+}
+
+static int tcpci_get_vbus(struct udevice *dev)
+{
+	u8 reg;
+	int ret;
+
+	ret = tcpci_read8(dev, TCPC_POWER_STATUS, &reg);
+	if (ret < 0)
+		return 0;
+
+	dev_dbg(dev, "%s: vbus=%d\n", __func__,
+		!!(reg & TCPC_POWER_STATUS_VBUS_PRES));
+	return !!(reg & TCPC_POWER_STATUS_VBUS_PRES);
+}
+
+static int tcpci_pd_transmit(struct udevice *dev,
+			     enum tcpm_transmit_type type,
+			     const struct pd_message *msg,
+			     unsigned int negotiated_rev)
+{
+	u16 header = msg ? le16_to_cpu(msg->header) : 0;
+	unsigned int reg, cnt;
+	int ret;
+	u8 byte_cnt;
+	u8 buf[TCPC_TRANSMIT_BUFFER_MAX_LEN];
+	u8 pos = 0;
+
+	cnt = msg ? pd_header_cnt(header) * 4 : 0;
+
+	schedule();
+
+	byte_cnt = cnt + 2;
+
+	ret = dm_i2c_write(dev, TCPC_TX_BYTE_CNT, &byte_cnt, 1);
+	if (ret < 0)
+		return ret;
+
+	if (msg) {
+		memcpy(&buf[pos], &msg->header, sizeof(msg->header));
+		pos += sizeof(msg->header);
+
+		if (cnt > 0) {
+			memcpy(&buf[pos], msg->payload, cnt);
+			pos += cnt;
+		}
+
+		ret = dm_i2c_write(dev, TCPC_TX_HDR, buf, byte_cnt);
+		if (ret < 0)
+			return ret;
+	}
+
+	reg = FIELD_PREP(TCPC_TRANSMIT_RETRY,
+			 (negotiated_rev > PD_REV20
+			  ? PD_RETRY_COUNT_3_0_OR_HIGHER
+			  : PD_RETRY_COUNT_DEFAULT));
+	reg |= FIELD_PREP(TCPC_TRANSMIT_TYPE, type);
+	ret = tcpci_write8(dev, TCPC_TRANSMIT, (u8)reg);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int tcpci_init(struct udevice *dev)
+{
+	struct tcpci_chip *chip = dev_get_priv(dev);
+	ulong timeout_ms = 1000;
+	u8 power_status;
+	u16 alert_mask;
+	ulong start;
+	int ret;
+	u8 cnt;
+	u8 rx_buf[64];
+
+	dev_dbg(dev, "%s: entering\n", __func__);
+	dev_dbg(dev, "TCPCI: init start\n");
+
+	start = get_timer(0);
+	do {
+		ret = tcpci_read8(dev, TCPC_POWER_STATUS, &power_status);
+		if (ret < 0)
+			return ret;
+
+		if (!(power_status & TCPC_POWER_STATUS_UNINIT))
+			break;
+
+	} while (get_timer(0) < (start + timeout_ms));
+
+	if (power_status & TCPC_POWER_STATUS_UNINIT) {
+		dev_err(dev, "TCPC initialization timed out\n");
+		return -ETIMEDOUT;
+	}
+
+	dev_dbg(dev, "%s: TCPC ready after wait\n", __func__);
+	dev_dbg(dev, "%s: POWER_STATUS=0x%02x\n", __func__, power_status);
+
+	ret = tcpci_write8(dev, TCPC_FAULT_STATUS, 0xff);
+	if (ret < 0)
+		return ret;
+
+	/* Mask all fault alerts (ALL_REG_RST_TO_DEFAULT, VCONN_OC) */
+	ret = tcpci_write8(dev, TCPC_FAULT_STATUS_MASK, 0xff);
+	if (ret < 0)
+		return ret;
+
+	ret = tcpci_write16(dev, TCPC_ALERT, 0xffff);
+	if (ret < 0)
+		return ret;
+
+	/* Drain any stale RX buffer (e.g. from dead battery phase) */
+	ret = tcpci_read8(dev, TCPC_RX_BYTE_CNT, &cnt);
+	if (ret == 0 && cnt > 0 && cnt <= sizeof(rx_buf)) {
+		dm_i2c_read(dev, TCPC_RX_BUF_FRAME_TYPE, rx_buf, cnt);
+		dev_dbg(dev, "%s: drained %d bytes from stale RX buffer\n",
+			__func__, cnt);
+		tcpci_write16(dev, TCPC_ALERT, TCPC_ALERT_RX_STATUS);
+	}
+
+	power_status = chip->controls_vbus ? TCPC_POWER_STATUS_VBUS_PRES : 0;
+	ret = tcpci_write8(dev, TCPC_POWER_STATUS_MASK, power_status);
+	if (ret < 0)
+		return ret;
+
+	ret = tcpci_write8(dev, TCPC_COMMAND, TCPC_CMD_ENABLE_VBUS_DETECT);
+	if (ret < 0)
+		return ret;
+
+	alert_mask = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_FAILED |
+		     TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_RX_STATUS |
+		     TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS;
+	if (chip->controls_vbus)
+		alert_mask |= TCPC_ALERT_POWER_STATUS;
+
+	chip->alert_mask = alert_mask;
+
+	/* Program hardware ALERT_MASK so unwanted bits never fire */
+	ret = tcpci_write16(dev, TCPC_ALERT_MASK, chip->alert_mask);
+	if (ret < 0)
+		return ret;
+
+	dev_dbg(dev, "%s: done alert_mask=0x%04x controls_vbus=%d\n",
+		__func__, alert_mask, chip->controls_vbus);
+	return 0;
+}
+
+static void tcpci_poll_event(struct udevice *dev)
+{
+	struct tcpci_chip *chip = dev_get_priv(dev);
+	u16 status;
+	int max_iter = 5;
+	int ret;
+	bool was_in_poll = chip->in_poll;
+
+	schedule();
+	chip->in_poll = true;
+
+	do {
+		u16 raw;
+
+		ret = tcpci_read16(dev, TCPC_ALERT, &raw);
+		if (ret < 0)
+			break;
+
+		if (raw & ~TCPC_ALERT_RX_STATUS)
+			tcpci_write16(dev, TCPC_ALERT,
+				      raw & ~TCPC_ALERT_RX_STATUS);
+
+		if (raw & TCPC_ALERT_FAULT) {
+			u8 fault;
+			tcpci_read8(dev, TCPC_FAULT_STATUS, &fault);
+			tcpci_write8(dev, TCPC_FAULT_STATUS, fault);
+		}
+
+		status = raw & chip->alert_mask;
+		if (!status)
+			break;
+
+		if (status & TCPC_ALERT_CC_STATUS)
+			tcpm_cc_change(dev);
+
+		if (status & TCPC_ALERT_POWER_STATUS)
+			tcpm_vbus_change(dev);
+
+		if (status & TCPC_ALERT_RX_STATUS) {
+			struct pd_message msg;
+			u16 header = 0;
+			u8 cnt;
+			u8 rx_buf[64];
+
+			/*
+			 * On re-entrant calls (during TX poll helper), the
+			 * state machine is locked.  Drain and clear the
+			 * FIFO without processing, letting the NEXT poll
+			 * (after state machine unlocks) handle the message.
+			 */
+			if (was_in_poll) {
+				ret = tcpci_read8(dev, TCPC_RX_BYTE_CNT, &cnt);
+				if (ret == 0 && cnt > 0 && cnt <= sizeof(rx_buf))
+					dm_i2c_read(dev, TCPC_RX_BUF_FRAME_TYPE,
+						    rx_buf, cnt);
+				tcpci_write16(dev, TCPC_ALERT, TCPC_ALERT_RX_STATUS);
+				continue;
+			}
+
+			memset(&msg, 0, sizeof(msg));
+
+			ret = tcpci_read8(dev, TCPC_RX_BYTE_CNT, &cnt);
+			if (ret < 0)
+				continue;
+
+			if (cnt > 0 && cnt <= sizeof(rx_buf)) {
+				ret = dm_i2c_read(dev, TCPC_RX_BUF_FRAME_TYPE,
+						  rx_buf, cnt);
+				if (ret < 0)
+					continue;
+
+				if (cnt >= 3) {
+					header = rx_buf[1] | (rx_buf[2] << 8);
+					msg.header = cpu_to_le16(header);
+
+					if (cnt > 3) {
+						unsigned int payload_cnt = cnt - 3;
+						if (payload_cnt > sizeof(msg.payload))
+							payload_cnt = sizeof(msg.payload);
+						memcpy(&msg.payload, &rx_buf[3],
+						       payload_cnt);
+					}
+				}
+			}
+
+			tcpci_write16(dev, TCPC_ALERT, TCPC_ALERT_RX_STATUS);
+
+			if (header != 0)
+				tcpm_pd_receive(dev, &msg);
+			}
+
+		if (status & TCPC_ALERT_RX_HARD_RST) {
+			pd_trace("ALERT_RX_HARD_RST", raw);
+			dev_dbg(dev, "%s: RX_HARD_RST\n", __func__);
+			tcpm_pd_hard_reset(dev);
+		}
+
+		if (status & TCPC_ALERT_TX_SUCCESS) {
+			dev_dbg(dev, "%s: TX_SUCCESS\n", __func__);
+			tcpm_pd_transmit_complete(dev, TCPC_TX_SUCCESS);
+		} else if (status & TCPC_ALERT_TX_DISCARDED) {
+			dev_dbg(dev, "%s: TX_DISCARDED\n", __func__);
+			tcpm_pd_transmit_complete(dev, TCPC_TX_DISCARDED);
+		} else if (status & TCPC_ALERT_TX_FAILED) {
+			dev_dbg(dev, "%s: TX_FAILED\n", __func__);
+			tcpm_pd_transmit_complete(dev, TCPC_TX_FAILED);
+		}
+	} while (--max_iter > 0);
+
+	chip->in_poll = was_in_poll;
+}
+
+static int tcpci_enter_low_power_mode(struct udevice *dev, bool attached,
+				      bool pd_capable)
+{
+	int ret;
+
+	dev_dbg(dev, "%s: entering attached=%d pd_capable=%d\n",
+		__func__, attached, pd_capable);
+
+	/*
+	 * Stop SOP reception so TCPCI hardware won't auto-send GoodCRC
+	 * for incoming messages.  Without this, the source may send
+	 * Get_Sink_Cap or Get_Status after negotiation completes;
+	 * TCPCI auto-ACKs with GoodCRC, source starts SenderResponseTimer,
+	 * nobody sends a response, timer expires -> Hard Reset -> VBUS
+	 * drops -> board loses power.
+	 *
+	 * Per PD spec 6.5.2, failure to receive GoodCRC does NOT trigger
+	 * Hard Reset, so source stays in Ready state with VBUS up.
+	 */
+	u8 pwr_ctrl, pwr_status;
+
+	tcpci_read8(dev, TCPC_POWER_CTRL, &pwr_ctrl);
+	tcpci_read8(dev, TCPC_POWER_STATUS, &pwr_status);
+	pd_trace("LOW_PWR_PWR_CTRL", pwr_ctrl);
+	pd_trace("LOW_PWR_PWR_STAT", pwr_status);
+
+	pd_trace("LOW_PWR_ENTER", 0);
+	ret = tcpci_write8(dev, TCPC_RX_DETECT, 0);
+	if (ret < 0)
+		return ret;
+
+	pd_trace("LOW_PWR_RX_OFF", 0);
+	ret = tcpci_write16(dev, TCPC_ALERT_MASK, 0);
+	if (ret < 0)
+		return ret;
+
+	ret = tcpci_write16(dev, TCPC_ALERT, 0xffff);
+	if (ret < 0)
+		return ret;
+
+	pd_trace("LOW_PWR_DONE", 0);
+	return 0;
+}
+
+static struct dm_tcpm_ops tcpci_ops = {
+	.get_connector_node = tcpci_get_connector_node,
+	.init = tcpci_init,
+	.get_vbus = tcpci_get_vbus,
+	.set_cc = tcpci_set_cc,
+	.get_cc = tcpci_get_cc,
+	.set_polarity = tcpci_set_polarity,
+	.set_vconn = tcpci_set_vconn,
+	.set_vbus = tcpci_set_vbus,
+	.set_pd_rx = tcpci_set_pd_rx,
+	.set_roles = tcpci_set_roles,
+	.start_toggling = tcpci_start_toggling,
+	.pd_transmit = tcpci_pd_transmit,
+	.poll_event = tcpci_poll_event,
+	.enter_low_power_mode = tcpci_enter_low_power_mode,
+};
+
+static const struct udevice_id tcpci_ids[] = {
+	{ .compatible = "nxp,ptn5110" },
+	{ .compatible = "tcpci" },
+	{ }
+};
+
+U_BOOT_DRIVER(tcpci) = {
+	.name = "tcpci",
+	.id = UCLASS_TCPM,
+	.of_match = tcpci_ids,
+	.ops = &tcpci_ops,
+	.priv_auto = sizeof(struct tcpci_chip),
+};
diff --git a/include/usb/tcpci.h b/include/usb/tcpci.h
new file mode 100644
index 00000000000..f87d10c6c53
--- /dev/null
+++ b/include/usb/tcpci.h
@@ -0,0 +1,185 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2015-2017 Google, Inc
+ *
+ * USB Type-C Port Controller Interface.
+ */
+
+#ifndef __USB_TCPCI_H
+#define __USB_TCPCI_H
+
+#include <linux/bitops.h>
+#include <usb/pd.h>
+
+#define TCPC_VENDOR_ID			0x0
+#define TCPC_PRODUCT_ID			0x2
+#define TCPC_BCD_DEV			0x4
+#define TCPC_TC_REV			0x6
+#define TCPC_PD_REV			0x8
+#define TCPC_PD_INT_REV			0xa
+
+#define TCPC_ALERT			0x10
+#define TCPC_ALERT_EXTND		BIT(14)
+#define TCPC_ALERT_EXTENDED_STATUS	BIT(13)
+#define TCPC_ALERT_VBUS_DISCNCT		BIT(11)
+#define TCPC_ALERT_RX_BUF_OVF		BIT(10)
+#define TCPC_ALERT_FAULT		BIT(9)
+#define TCPC_ALERT_V_ALARM_LO		BIT(8)
+#define TCPC_ALERT_V_ALARM_HI		BIT(7)
+#define TCPC_ALERT_TX_SUCCESS		BIT(6)
+#define TCPC_ALERT_TX_DISCARDED		BIT(5)
+#define TCPC_ALERT_TX_FAILED		BIT(4)
+#define TCPC_ALERT_RX_HARD_RST		BIT(3)
+#define TCPC_ALERT_RX_STATUS		BIT(2)
+#define TCPC_ALERT_POWER_STATUS		BIT(1)
+#define TCPC_ALERT_CC_STATUS		BIT(0)
+
+#define TCPC_ALERT_MASK			0x12
+#define TCPC_POWER_STATUS_MASK		0x14
+
+#define TCPC_FAULT_STATUS_MASK			0x15
+#define TCPC_FAULT_STATUS_MASK_VCONN_OC		BIT(1)
+
+#define TCPC_EXTENDED_STATUS_MASK		0x16
+#define TCPC_EXTENDED_STATUS_MASK_VSAFE0V	BIT(0)
+
+#define TCPC_ALERT_EXTENDED_MASK	0x17
+#define TCPC_SINK_FAST_ROLE_SWAP	BIT(0)
+
+#define TCPC_CONFIG_STD_OUTPUT		0x18
+#define TCPC_CONFIG_STD_OUTPUT_ORIENTATION_MASK		BIT(0)
+#define TCPC_CONFIG_STD_OUTPUT_ORIENTATION_NORMAL	0
+#define TCPC_CONFIG_STD_OUTPUT_ORIENTATION_FLIPPED	1
+
+#define TCPC_TCPC_CTRL			0x19
+#define TCPC_TCPC_CTRL_ORIENTATION	BIT(0)
+#define PLUG_ORNT_CC1			0
+#define PLUG_ORNT_CC2			1
+#define TCPC_TCPC_CTRL_BIST_TM		BIT(1)
+#define TCPC_TCPC_CTRL_EN_LK4CONN_ALRT	BIT(6)
+
+#define TCPC_EXTENDED_STATUS		0x20
+#define TCPC_EXTENDED_STATUS_VSAFE0V	BIT(0)
+
+#define TCPC_ROLE_CTRL			0x1a
+#define TCPC_ROLE_CTRL_DRP		BIT(6)
+#define TCPC_ROLE_CTRL_RP_VAL		GENMASK(5, 4)
+#define TCPC_ROLE_CTRL_RP_VAL_DEF	0x0
+#define TCPC_ROLE_CTRL_RP_VAL_1_5	0x1
+#define TCPC_ROLE_CTRL_RP_VAL_3_0	0x2
+#define TCPC_ROLE_CTRL_CC2		GENMASK(3, 2)
+#define TCPC_ROLE_CTRL_CC1		GENMASK(1, 0)
+#define TCPC_ROLE_CTRL_CC_RA		0x0
+#define TCPC_ROLE_CTRL_CC_RP		0x1
+#define TCPC_ROLE_CTRL_CC_RD		0x2
+#define TCPC_ROLE_CTRL_CC_OPEN		0x3
+
+#define TCPC_FAULT_CTRL			0x1b
+
+#define TCPC_POWER_CTRL			0x1c
+#define TCPC_POWER_CTRL_VCONN_ENABLE	BIT(0)
+#define TCPC_POWER_CTRL_BLEED_DISCHARGE	BIT(3)
+#define TCPC_POWER_CTRL_AUTO_DISCHARGE	BIT(4)
+#define TCPC_DIS_VOLT_ALRM		BIT(5)
+#define TCPC_POWER_CTRL_VBUS_VOLT_MON	BIT(6)
+#define TCPC_FAST_ROLE_SWAP_EN		BIT(7)
+
+#define TCPC_CC_STATUS			0x1d
+#define TCPC_CC_STATUS_TOGGLING		BIT(5)
+#define TCPC_CC_STATUS_TERM		BIT(4)
+#define TCPC_CC_STATUS_CC2		GENMASK(3, 2)
+#define TCPC_CC_STATUS_CC1		GENMASK(1, 0)
+#define TCPC_CC_STATE_SRC_OPEN		0
+
+#define TCPC_POWER_STATUS		0x1e
+#define TCPC_POWER_STATUS_DBG_ACC_CON	BIT(7)
+#define TCPC_POWER_STATUS_UNINIT	BIT(6)
+#define TCPC_POWER_STATUS_SOURCING_VBUS	BIT(4)
+#define TCPC_POWER_STATUS_VBUS_DET	BIT(3)
+#define TCPC_POWER_STATUS_VBUS_PRES	BIT(2)
+#define TCPC_POWER_STATUS_VCONN_PRES	BIT(1)
+#define TCPC_POWER_STATUS_SINKING_VBUS	BIT(0)
+
+#define TCPC_FAULT_STATUS		0x1f
+#define TCPC_FAULT_STATUS_ALL_REG_RST_TO_DEFAULT BIT(7)
+#define TCPC_FAULT_STATUS_VCONN_OC	BIT(1)
+
+#define TCPC_ALERT_EXTENDED		0x21
+
+#define TCPC_COMMAND			0x23
+#define TCPC_CMD_WAKE_I2C		0x11
+#define TCPC_CMD_DISABLE_VBUS_DETECT	0x22
+#define TCPC_CMD_ENABLE_VBUS_DETECT	0x33
+#define TCPC_CMD_DISABLE_SINK_VBUS	0x44
+#define TCPC_CMD_SINK_VBUS		0x55
+#define TCPC_CMD_DISABLE_SRC_VBUS	0x66
+#define TCPC_CMD_SRC_VBUS_DEFAULT	0x77
+#define TCPC_CMD_SRC_VBUS_HIGH		0x88
+#define TCPC_CMD_LOOK4CONNECTION	0x99
+#define TCPC_CMD_RXONEMORE		0xAA
+#define TCPC_CMD_I2C_IDLE		0xFF
+
+#define TCPC_DEV_CAP_1			0x24
+#define TCPC_DEV_CAP_2			0x26
+#define TCPC_STD_INPUT_CAP		0x28
+#define TCPC_STD_OUTPUT_CAP		0x29
+#define TCPC_STD_OUTPUT_CAP_ORIENTATION	BIT(0)
+
+#define TCPC_MSG_HDR_INFO		0x2e
+#define TCPC_MSG_HDR_INFO_DATA_ROLE	BIT(3)
+#define TCPC_MSG_HDR_INFO_REV		GENMASK(2, 1)
+#define TCPC_MSG_HDR_INFO_PWR_ROLE	BIT(0)
+
+#define TCPC_RX_DETECT			0x2f
+#define TCPC_RX_DETECT_HARD_RESET	BIT(5)
+#define TCPC_RX_DETECT_SOP		BIT(0)
+#define TCPC_RX_DETECT_SOP1		BIT(1)
+#define TCPC_RX_DETECT_SOP2		BIT(2)
+#define TCPC_RX_DETECT_DBG1		BIT(3)
+#define TCPC_RX_DETECT_DBG2		BIT(4)
+
+#define TCPC_RX_BYTE_CNT		0x30
+#define TCPC_RX_BUF_FRAME_TYPE		0x31
+#define TCPC_RX_BUF_FRAME_TYPE_SOP	0
+#define TCPC_RX_BUF_FRAME_TYPE_SOP1	1
+#define TCPC_RX_HDR			0x32
+#define TCPC_RX_DATA			0x34
+
+#define TCPC_TRANSMIT			0x50
+#define TCPC_TRANSMIT_RETRY		GENMASK(5, 4)
+#define TCPC_TRANSMIT_TYPE		GENMASK(2, 0)
+
+#define TCPC_TX_BYTE_CNT		0x51
+#define TCPC_TX_HDR			0x52
+#define TCPC_TX_DATA			0x54
+
+#define TCPC_VBUS_VOLTAGE			0x70
+#define TCPC_VBUS_SINK_DISCONNECT_THRESH	0x72
+#define TCPC_VBUS_STOP_DISCHARGE_THRESH		0x74
+#define TCPC_VBUS_VOLTAGE_ALARM_HI_CFG		0x76
+#define TCPC_VBUS_VOLTAGE_ALARM_LO_CFG		0x78
+
+#define TCPC_TRANSMIT_BUFFER_MAX_LEN		31
+
+#define tcpc_presenting_rd(reg, cc) \
+	(!(TCPC_ROLE_CTRL_DRP & (reg)) && \
+	 FIELD_GET(TCPC_ROLE_CTRL_## cc, reg) == TCPC_ROLE_CTRL_CC_RD)
+
+static inline enum typec_cc_status tcpci_to_typec_cc(unsigned int cc, bool sink)
+{
+	switch (cc) {
+	case 0x1:
+		return sink ? TYPEC_CC_RP_DEF : TYPEC_CC_RA;
+	case 0x2:
+		return sink ? TYPEC_CC_RP_1_5 : TYPEC_CC_RD;
+	case 0x3:
+		if (sink)
+			return TYPEC_CC_RP_3_0;
+		fallthrough;
+	case TCPC_CC_STATE_SRC_OPEN:
+	default:
+		return TYPEC_CC_OPEN;
+	}
+}
+
+#endif /* __USB_TCPCI_H */

-- 
2.51.0


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

* [PATCH v2 05/10] usb: tcpm: add setup_host_mode/device_mode/disable_src_vbus APIs
  2026-06-21  2:06 [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support Peng Fan (OSS)
                   ` (3 preceding siblings ...)
  2026-06-21  2:06 ` [PATCH v2 04/10] usb: tcpm: add TCPCI (Type-C Port Controller Interface) driver Peng Fan (OSS)
@ 2026-06-21  2:06 ` Peng Fan (OSS)
  2026-06-21  2:06 ` [PATCH v2 06/10] usb: tcpm: skip state machine for pd-disable ports Peng Fan (OSS)
                   ` (5 subsequent siblings)
  10 siblings, 0 replies; 14+ messages in thread
From: Peng Fan (OSS) @ 2026-06-21  2:06 UTC (permalink / raw)
  To: u-boot, NXP i.MX U-Boot Team, Sebastian Reichel, Marek Vasut,
	Fabio Estevam, Wang Jie
  Cc: Tom Rini, Patrice Chotard, Yao Zi, Mattijs Korpershoek,
	Jonas Karlman, Kever Yang, Kaustabh Chakraborty, Casey Connolly,
	Ye Li, Sam Protsenko, Stefano Babic, Marek Vasut, Alice Guo,
	Balaji Selvanathan, Peng Fan

From: Peng Fan <peng.fan@nxp.com>

Add lightweight Type-C mode setup functions that bypass the TCPM state
machine and call TCPCI driver ops directly:

- tcpm_setup_host_mode(): set CC=Rp, detect device, set polarity,
  source VBUS
- tcpm_setup_device_mode(): set CC=Rd, detect host, set polarity
- tcpm_disable_src_vbus(): stop sourcing VBUS

These are used by board_usb_init/cleanup to configure the Type-C port
for USB host or device mode without running the full PD state machine.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/usb/tcpm/tcpm-uclass.c | 87 ++++++++++++++++++++++++++++++++++++++++++
 include/usb/tcpm.h             |  4 ++
 2 files changed, 91 insertions(+)

diff --git a/drivers/usb/tcpm/tcpm-uclass.c b/drivers/usb/tcpm/tcpm-uclass.c
index d4fe260e0db..e0f7ce62a79 100644
--- a/drivers/usb/tcpm/tcpm-uclass.c
+++ b/drivers/usb/tcpm/tcpm-uclass.c
@@ -8,6 +8,7 @@
 #include <dm/device.h>
 #include <dm/device_compat.h>
 #include <dm/uclass.h>
+#include <linux/delay.h>
 #include <linux/err.h>
 #include <usb/tcpm.h>
 #include "tcpm-internal.h"
@@ -140,6 +141,92 @@ static int tcpm_post_bind(struct udevice *dev)
 	return 0;
 }
 
+int tcpm_setup_host_mode(struct udevice *dev, enum typec_cc_polarity *polarity)
+{
+	const struct dm_tcpm_ops *ops = dev_get_driver_ops(dev);
+	enum typec_cc_status cc1, cc2;
+	int ret;
+
+	ops->set_vbus(dev, false, false);
+
+	ops->set_cc(dev, TYPEC_CC_RP_DEF);
+
+	mdelay(100);
+
+	ret = ops->get_cc(dev, &cc1, &cc2);
+	if (ret) {
+		dev_err(dev, "Failed to get cc for host mode: %d\n", ret);
+		return ret;
+	}
+
+	if (cc1 == TYPEC_CC_RD) {
+		*polarity = TYPEC_POLARITY_CC1;
+	} else if (cc2 == TYPEC_CC_RD) {
+		*polarity = TYPEC_POLARITY_CC2;
+	} else {
+		dev_err(dev, "no device detected (cc1=%d cc2=%d)\n", cc1, cc2);
+		return -ENODEV;
+	}
+
+	ret = ops->set_polarity(dev, *polarity);
+	if (ret)
+		return ret;
+
+	ops->set_vbus(dev, true, false);
+	mdelay(300);
+
+	return 0;
+}
+
+int tcpm_setup_device_mode(struct udevice *dev, enum typec_cc_polarity *polarity)
+{
+	const struct dm_tcpm_ops *ops = dev_get_driver_ops(dev);
+	enum typec_cc_status cc1, cc2;
+	int ret, retry;
+
+	ops->set_vbus(dev, false, false);
+
+	ops->set_cc(dev, TYPEC_CC_RD);
+
+	if (ops->start_toggling)
+		ops->start_toggling(dev, TYPEC_PORT_SNK, TYPEC_CC_RD);
+
+	for (retry = 0; retry < 20; retry++) {
+		mdelay(100);
+
+		ret = ops->get_cc(dev, &cc1, &cc2);
+		if (ret) {
+			dev_err(dev, "Failed to get cc for device mode: %d\n", ret);
+			return ret;
+		}
+
+		if (cc1 != TYPEC_CC_OPEN || cc2 != TYPEC_CC_OPEN)
+			break;
+	}
+
+	if (cc1 != TYPEC_CC_OPEN && cc2 == TYPEC_CC_OPEN) {
+		*polarity = TYPEC_POLARITY_CC1;
+	} else if (cc1 == TYPEC_CC_OPEN && cc2 != TYPEC_CC_OPEN) {
+		*polarity = TYPEC_POLARITY_CC2;
+	} else {
+		dev_err(dev, "no host detected (cc1=%d cc2=%d)\n", cc1, cc2);
+		return -ENODEV;
+	}
+
+	ret = ops->set_polarity(dev, *polarity);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+int tcpm_disable_src_vbus(struct udevice *dev)
+{
+	const struct dm_tcpm_ops *ops = dev_get_driver_ops(dev);
+
+	return ops->set_vbus(dev, false, false);
+}
+
 UCLASS_DRIVER(tcpm) = {
 	.id		= UCLASS_TCPM,
 	.name		= "tcpm",
diff --git a/include/usb/tcpm.h b/include/usb/tcpm.h
index 10f0515fe12..caa67a5639d 100644
--- a/include/usb/tcpm.h
+++ b/include/usb/tcpm.h
@@ -96,4 +96,8 @@ enum typec_data_role tcpm_get_data_role(struct udevice *dev);
 bool tcpm_is_connected(struct udevice *dev);
 const char *tcpm_get_state(struct udevice *dev);
 
+int tcpm_setup_host_mode(struct udevice *dev, enum typec_cc_polarity *polarity);
+int tcpm_setup_device_mode(struct udevice *dev, enum typec_cc_polarity *polarity);
+int tcpm_disable_src_vbus(struct udevice *dev);
+
 #endif /* __LINUX_USB_TCPM_H */

-- 
2.51.0


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

* [PATCH v2 06/10] usb: tcpm: skip state machine for pd-disable ports
  2026-06-21  2:06 [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support Peng Fan (OSS)
                   ` (4 preceding siblings ...)
  2026-06-21  2:06 ` [PATCH v2 05/10] usb: tcpm: add setup_host_mode/device_mode/disable_src_vbus APIs Peng Fan (OSS)
@ 2026-06-21  2:06 ` Peng Fan (OSS)
  2026-06-21  2:06 ` [PATCH v2 07/10] usb: dwc3: generic: add board_usb_init/cleanup hooks Peng Fan (OSS)
                   ` (4 subsequent siblings)
  10 siblings, 0 replies; 14+ messages in thread
From: Peng Fan (OSS) @ 2026-06-21  2:06 UTC (permalink / raw)
  To: u-boot, NXP i.MX U-Boot Team, Sebastian Reichel, Marek Vasut,
	Fabio Estevam, Wang Jie
  Cc: Tom Rini, Patrice Chotard, Yao Zi, Mattijs Korpershoek,
	Jonas Karlman, Kever Yang, Kaustabh Chakraborty, Casey Connolly,
	Ye Li, Sam Protsenko, Stefano Babic, Marek Vasut, Alice Guo,
	Balaji Selvanathan, Peng Fan

From: Peng Fan <peng.fan@nxp.com>

Check the pd-disable property in the connector node during
tcpm_post_probe(). When set, skip tcpm_port_init() and
tcpm_poll_event() so the TCPCI driver is probed (I2C accessible,
ops available) but the PD state machine does not run.

This is needed for dual-role data ports where running the state
machine during probe would disrupt an already-established USB
connection.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/usb/tcpm/tcpm.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/drivers/usb/tcpm/tcpm.c b/drivers/usb/tcpm/tcpm.c
index 1ce46118fea..32c67b8886e 100644
--- a/drivers/usb/tcpm/tcpm.c
+++ b/drivers/usb/tcpm/tcpm.c
@@ -2306,8 +2306,15 @@ static void tcpm_poll_event(struct udevice *dev)
 
 int tcpm_post_probe(struct udevice *dev)
 {
-	int ret = tcpm_port_init(dev);
+	const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+	ofnode connector;
+	int ret;
+
+	ret = drvops->get_connector_node(dev, &connector);
+	if (!ret && ofnode_read_bool(connector, "pd-disable"))
+		return 0;
 
+	ret = tcpm_port_init(dev);
 	if (ret < 0) {
 		dev_err(dev, "failed to tcpm port init\n");
 		return ret;

-- 
2.51.0


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

* [PATCH v2 07/10] usb: dwc3: generic: add board_usb_init/cleanup hooks
  2026-06-21  2:06 [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support Peng Fan (OSS)
                   ` (5 preceding siblings ...)
  2026-06-21  2:06 ` [PATCH v2 06/10] usb: tcpm: skip state machine for pd-disable ports Peng Fan (OSS)
@ 2026-06-21  2:06 ` Peng Fan (OSS)
  2026-06-21  2:06 ` [PATCH v2 08/10] usb: dwc3: generic: support OTG dual-bind for runtime host/device switching Peng Fan (OSS)
                   ` (3 subsequent siblings)
  10 siblings, 0 replies; 14+ messages in thread
From: Peng Fan (OSS) @ 2026-06-21  2:06 UTC (permalink / raw)
  To: u-boot, NXP i.MX U-Boot Team, Sebastian Reichel, Marek Vasut,
	Fabio Estevam, Wang Jie
  Cc: Tom Rini, Patrice Chotard, Yao Zi, Mattijs Korpershoek,
	Jonas Karlman, Kever Yang, Kaustabh Chakraborty, Casey Connolly,
	Ye Li, Sam Protsenko, Stefano Babic, Marek Vasut, Alice Guo,
	Balaji Selvanathan, Peng Fan

From: Ye Li <ye.li@nxp.com>

Call board_usb_init() during dwc3_generic_probe() and
board_usb_cleanup() during dwc3_generic_remove() to allow
board-level USB configuration (e.g. Type-C CC role, SS mux,
VBUS control) before DWC3 core initialization.

Signed-off-by: Ye Li <ye.li@nxp.com>
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/usb/dwc3/dwc3-generic.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/drivers/usb/dwc3/dwc3-generic.c b/drivers/usb/dwc3/dwc3-generic.c
index 2356b3bc0aa..2c6f8e6d8ba 100644
--- a/drivers/usb/dwc3/dwc3-generic.c
+++ b/drivers/usb/dwc3/dwc3-generic.c
@@ -9,6 +9,7 @@
 
 #include <dm.h>
 #include <reset.h>
+#include <usb.h>
 #include <asm/gpio.h>
 #include <dm/device_compat.h>
 #include <dm/lists.h>
@@ -118,6 +119,11 @@ static int dwc3_generic_probe(struct udevice *dev,
 	if (device_is_compatible(dev->parent, "rockchip,rk3399-dwc3"))
 		reset_deassert_bulk(&glue->resets);
 
+	if (mode == USB_DR_MODE_HOST)
+		board_usb_init(dev_seq(dev), USB_INIT_HOST);
+	else if (mode == USB_DR_MODE_PERIPHERAL)
+		board_usb_init(dev_seq(dev), USB_INIT_DEVICE);
+
 	priv->base = map_physmem(plat->base, DWC3_OTG_REGS_END, MAP_NOCACHE);
 	dwc3->regs = priv->base + DWC3_GLOBALS_REGS_START;
 
@@ -134,6 +140,7 @@ static int dwc3_generic_remove(struct udevice *dev,
 			       struct dwc3_generic_priv *priv)
 {
 	struct dwc3 *dwc3 = &priv->dwc3;
+	enum usb_dr_mode mode = dwc3->dr_mode;
 
 	if (CONFIG_IS_ENABLED(DM_GPIO) &&
 	    device_is_compatible(dev->parent, "xlnx,zynqmp-dwc3") &&
@@ -147,6 +154,11 @@ static int dwc3_generic_remove(struct udevice *dev,
 	dwc3_shutdown_phy(dev, &priv->phys);
 	unmap_physmem(dwc3->regs, MAP_NOCACHE);
 
+	if (mode == USB_DR_MODE_HOST)
+		board_usb_cleanup(dev_seq(dev), USB_INIT_HOST);
+	else if (mode == USB_DR_MODE_PERIPHERAL)
+		board_usb_cleanup(dev_seq(dev), USB_INIT_DEVICE);
+
 	return 0;
 }
 

-- 
2.51.0


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

* [PATCH v2 08/10] usb: dwc3: generic: support OTG dual-bind for runtime host/device switching
  2026-06-21  2:06 [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support Peng Fan (OSS)
                   ` (6 preceding siblings ...)
  2026-06-21  2:06 ` [PATCH v2 07/10] usb: dwc3: generic: add board_usb_init/cleanup hooks Peng Fan (OSS)
@ 2026-06-21  2:06 ` Peng Fan (OSS)
  2026-06-21  2:06 ` [PATCH v2 09/10] board: imx8mp_evk: enable USB PD sink via TCPCI Peng Fan (OSS)
                   ` (2 subsequent siblings)
  10 siblings, 0 replies; 14+ messages in thread
From: Peng Fan (OSS) @ 2026-06-21  2:06 UTC (permalink / raw)
  To: u-boot, NXP i.MX U-Boot Team, Sebastian Reichel, Marek Vasut,
	Fabio Estevam, Wang Jie
  Cc: Tom Rini, Patrice Chotard, Yao Zi, Mattijs Korpershoek,
	Jonas Karlman, Kever Yang, Kaustabh Chakraborty, Casey Connolly,
	Ye Li, Sam Protsenko, Stefano Babic, Marek Vasut, Alice Guo,
	Balaji Selvanathan, Peng Fan

From: Peng Fan <peng.fan@nxp.com>

When dr_mode is "otg", bind both dwc3-generic-host and
dwc3-generic-peripheral children to the same DT node. This allows
runtime mode switching: "usb start" probes the host child while
"ums" or "fastboot" probes the peripheral child.

Add dwc3_otg_deactivate_sibling() which is called at the top of both
host and peripheral probe. It removes the active sibling via
device_remove() so the DWC3 core can be re-initialized in the new
mode (PHY teardown, GCTL.PRTCAPDIR flip, board_usb_cleanup/init).

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/usb/dwc3/dwc3-generic.c | 61 +++++++++++++++++++++++++++++++++++++++--
 1 file changed, 59 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/dwc3/dwc3-generic.c b/drivers/usb/dwc3/dwc3-generic.c
index 2c6f8e6d8ba..7fcb201d5f0 100644
--- a/drivers/usb/dwc3/dwc3-generic.c
+++ b/drivers/usb/dwc3/dwc3-generic.c
@@ -8,6 +8,7 @@
  */
 
 #include <dm.h>
+#include <dm/device-internal.h>
 #include <reset.h>
 #include <usb.h>
 #include <asm/gpio.h>
@@ -194,11 +195,33 @@ static int dwc3_generic_of_to_plat(struct udevice *dev)
 	return 0;
 }
 
+/*
+ * When OTG mode is used, both host and peripheral children are bound to
+ * the same DWC3 node.  Only one can be active at a time.  Before probing
+ * one child, remove the other if it is currently active so that the DWC3
+ * core can be re-initialized in the new mode.
+ */
+static void dwc3_otg_deactivate_sibling(struct udevice *dev)
+{
+	struct udevice *parent = dev->parent;
+	struct udevice *child;
+
+	device_foreach_child(child, parent) {
+		if (child != dev && device_active(child)) {
+			debug("%s: deactivating sibling %s\n",
+			      dev->name, child->name);
+			device_remove(child, DM_REMOVE_NORMAL);
+		}
+	}
+}
+
 #if CONFIG_IS_ENABLED(DM_USB_GADGET)
 static int dwc3_generic_peripheral_probe(struct udevice *dev)
 {
 	struct dwc3_generic_priv *priv = dev_get_priv(dev);
 
+	dwc3_otg_deactivate_sibling(dev);
+
 	return dwc3_generic_probe(dev, priv, USB_DR_MODE_PERIPHERAL);
 }
 
@@ -243,6 +266,8 @@ static int dwc3_generic_host_probe(struct udevice *dev)
 	struct dwc3_generic_host_priv *priv = dev_get_priv(dev);
 	int rc;
 
+	dwc3_otg_deactivate_sibling(dev);
+
 	rc = dwc3_generic_probe(dev, &priv->gen_priv, USB_DR_MODE_HOST);
 	if (rc)
 		return rc;
@@ -550,9 +575,41 @@ static int dwc3_glue_bind_common(struct udevice *parent, ofnode node)
 	if (dr_mode == USB_DR_MODE_UNKNOWN)
 		dr_mode = USB_DR_MODE_OTG;
 
+	/*
+	 * For OTG mode, bind both host and peripheral children so that
+	 * runtime mode switching is possible via usb start / ums / fastboot.
+	 * Only the one that gets probed first will actually initialize;
+	 * the other stays bound but inactive until needed.
+	 */
+	if (dr_mode == USB_DR_MODE_OTG) {
+		if (CONFIG_IS_ENABLED(USB_HOST)) {
+			ret = device_bind_driver_to_node(parent, "dwc3-generic-host",
+							 name, node, &dev);
+			if (ret) {
+				debug("%s: failed to bind host: %d\n",
+				      __func__, ret);
+				return ret;
+			}
+			debug("%s: OTG: bound host child\n", __func__);
+		}
+
+		if (CONFIG_IS_ENABLED(DM_USB_GADGET)) {
+			ret = device_bind_driver_to_node(parent, "dwc3-generic-peripheral",
+							 name, node, &dev);
+			if (ret) {
+				debug("%s: failed to bind peripheral: %d\n",
+				      __func__, ret);
+				return ret;
+			}
+			debug("%s: OTG: bound peripheral child\n", __func__);
+		}
+
+		return 0;
+	}
+
 	if (CONFIG_IS_ENABLED(DM_USB_GADGET) &&
-	    (dr_mode == USB_DR_MODE_PERIPHERAL || dr_mode == USB_DR_MODE_OTG)) {
-		debug("%s: dr_mode: OTG or Peripheral\n", __func__);
+	    dr_mode == USB_DR_MODE_PERIPHERAL) {
+		debug("%s: dr_mode: Peripheral\n", __func__);
 		driver = "dwc3-generic-peripheral";
 	} else if (CONFIG_IS_ENABLED(USB_HOST) && dr_mode == USB_DR_MODE_HOST) {
 		debug("%s: dr_mode: HOST\n", __func__);

-- 
2.51.0


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

* [PATCH v2 09/10] board: imx8mp_evk: enable USB PD sink via TCPCI
  2026-06-21  2:06 [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support Peng Fan (OSS)
                   ` (7 preceding siblings ...)
  2026-06-21  2:06 ` [PATCH v2 08/10] usb: dwc3: generic: support OTG dual-bind for runtime host/device switching Peng Fan (OSS)
@ 2026-06-21  2:06 ` Peng Fan (OSS)
  2026-06-21  2:06 ` [PATCH v2 10/10] board: imx8mp_evk: add USB Type-C OTG support with TCPCI Peng Fan (OSS)
  2026-06-30  1:48 ` [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support Peng Fan
  10 siblings, 0 replies; 14+ messages in thread
From: Peng Fan (OSS) @ 2026-06-21  2:06 UTC (permalink / raw)
  To: u-boot, NXP i.MX U-Boot Team, Sebastian Reichel, Marek Vasut,
	Fabio Estevam, Wang Jie
  Cc: Tom Rini, Patrice Chotard, Yao Zi, Mattijs Korpershoek,
	Jonas Karlman, Kever Yang, Kaustabh Chakraborty, Casey Connolly,
	Ye Li, Sam Protsenko, Stefano Babic, Marek Vasut, Alice Guo,
	Balaji Selvanathan, Peng Fan

From: Peng Fan <peng.fan@nxp.com>

Add PTN5110 TCPCI node on I2C3 with USB-C connector configured as
sink-only. The sink PDOs advertise 5V/3A fixed and 5-20V/2.25A
variable, enabling PD negotiation up to 45W.

Enable TYPEC_TCPM, TYPEC_TCPCI and CMD_TCPM in defconfig.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 arch/arm/dts/imx8mp-evk-u-boot.dtsi | 18 ++++++++++++++++++
 configs/imx8mp_evk_defconfig        |  3 +++
 2 files changed, 21 insertions(+)

diff --git a/arch/arm/dts/imx8mp-evk-u-boot.dtsi b/arch/arm/dts/imx8mp-evk-u-boot.dtsi
index 51c84383673..fc9e9da09dd 100644
--- a/arch/arm/dts/imx8mp-evk-u-boot.dtsi
+++ b/arch/arm/dts/imx8mp-evk-u-boot.dtsi
@@ -3,6 +3,7 @@
  * Copyright 2019, 2021 NXP
  */
 
+#include <dt-bindings/usb/pd.h>
 #include "imx8mp-u-boot.dtsi"
 
 / {
@@ -91,6 +92,23 @@
 
 &i2c3 {
 	bootph-pre-ram;
+
+	tcpc_port2: i2c3-ptcpc@50 {
+		compatible = "nxp,ptn5110", "tcpci";
+		reg = <0x50>;
+
+		connector {
+			compatible = "usb-c-connector";
+			label = "USB-C-2";
+			power-role = "sink";
+			data-role = "device";
+			try-power-role = "sink";
+			sink-pdos =
+				<PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)>,
+				<PDO_VAR(5000, 20000, 2250)>;
+			op-sink-microwatt = <15000000>;
+		};
+	};
 };
 
 &i2c4 {
diff --git a/configs/imx8mp_evk_defconfig b/configs/imx8mp_evk_defconfig
index 7af538ee367..2849d910538 100644
--- a/configs/imx8mp_evk_defconfig
+++ b/configs/imx8mp_evk_defconfig
@@ -150,6 +150,9 @@ CONFIG_USB_EHCI_HCD=y
 # CONFIG_USB_EHCI_MX7 is not set
 CONFIG_USB_DWC3=y
 CONFIG_USB_DWC3_GENERIC=y
+CONFIG_TYPEC_TCPM=y
+CONFIG_TYPEC_TCPCI=y
+CONFIG_CMD_TCPM=y
 CONFIG_USB_GADGET=y
 CONFIG_USB_GADGET_MANUFACTURER="FSL"
 CONFIG_USB_GADGET_VENDOR_NUM=0x0525

-- 
2.51.0


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

* [PATCH v2 10/10] board: imx8mp_evk: add USB Type-C OTG support with TCPCI
  2026-06-21  2:06 [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support Peng Fan (OSS)
                   ` (8 preceding siblings ...)
  2026-06-21  2:06 ` [PATCH v2 09/10] board: imx8mp_evk: enable USB PD sink via TCPCI Peng Fan (OSS)
@ 2026-06-21  2:06 ` Peng Fan (OSS)
  2026-06-30  1:48 ` [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support Peng Fan
  10 siblings, 0 replies; 14+ messages in thread
From: Peng Fan (OSS) @ 2026-06-21  2:06 UTC (permalink / raw)
  To: u-boot, NXP i.MX U-Boot Team, Sebastian Reichel, Marek Vasut,
	Fabio Estevam, Wang Jie
  Cc: Tom Rini, Patrice Chotard, Yao Zi, Mattijs Korpershoek,
	Jonas Karlman, Kever Yang, Kaustabh Chakraborty, Casey Connolly,
	Ye Li, Sam Protsenko, Stefano Babic, Marek Vasut, Alice Guo,
	Balaji Selvanathan, Peng Fan

From: Peng Fan <peng.fan@nxp.com>

Enable runtime USB host/device mode switching on USB1 Type-C port:

- Add PTN5110 TCPCI node on I2C2 with pd-disable (data port only,
  PD negotiation handled by port2 on I2C3)
- Set dr_mode="otg" on usb_dwc3_0 with USB PHY tuning parameters
- Add board_usb_init(): host mode uses tcpm_setup_host_mode(),
  device mode uses tcpm_setup_device_mode() via TCPM uclass ops
- Add board_usb_cleanup(): disable source VBUS on host stop
- SS mux GPIO (GPIO4_IO20) set based on CC polarity
- Enable USB_DWC3_GENERIC, DM_USB_GADGET in defconfig

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 arch/arm/dts/imx8mp-evk-u-boot.dtsi | 34 +++++++++++++++-
 board/nxp/imx8mp_evk/imx8mp_evk.c   | 78 +++++++++++++++++++++++++++++++++++++
 2 files changed, 111 insertions(+), 1 deletion(-)

diff --git a/arch/arm/dts/imx8mp-evk-u-boot.dtsi b/arch/arm/dts/imx8mp-evk-u-boot.dtsi
index fc9e9da09dd..d315dfb3d03 100644
--- a/arch/arm/dts/imx8mp-evk-u-boot.dtsi
+++ b/arch/arm/dts/imx8mp-evk-u-boot.dtsi
@@ -88,6 +88,24 @@
 
 &i2c2 {
 	bootph-pre-ram;
+
+	tcpc_port1: i2c2-ptcpc@50 {
+		compatible = "nxp,ptn5110", "tcpci";
+		reg = <0x50>;
+
+		connector {
+			compatible = "usb-c-connector";
+			label = "USB-C-1";
+			power-role = "dual";
+			data-role = "dual";
+			try-power-role = "sink";
+			pd-disable;
+			source-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)>;
+			sink-pdos =
+				<PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)>;
+			op-sink-microwatt = <15000000>;
+		};
+	};
 };
 
 &i2c3 {
@@ -124,7 +142,15 @@
 };
 
 &usb_dwc3_0 {
-	dr_mode = "peripheral";
+	dr_mode = "otg";
+	maximum-speed = "high-speed";
+	hnp-disable;
+	srp-disable;
+	adp-disable;
+	usb-role-switch;
+	role-switch-default-mode = "none";
+	snps,dis-u1-entry-quirk;
+	snps,dis-u2-entry-quirk;
 	status = "okay";
 };
 
@@ -133,6 +159,12 @@
 };
 
 &usb3_phy0 {
+	fsl,phy-tx-vref-tune = <0xe>;
+	fsl,phy-tx-preemp-amp-tune = <3>;
+	fsl,phy-tx-vboost-level = <5>;
+	fsl,phy-comp-dis-tune = <7>;
+	fsl,pcs-tx-deemph-3p5db = <0x21>;
+	fsl,phy-pcs-tx-swing-full = <0x7f>;
 	status = "okay";
 };
 
diff --git a/board/nxp/imx8mp_evk/imx8mp_evk.c b/board/nxp/imx8mp_evk/imx8mp_evk.c
index e17100e51ec..37a391908c4 100644
--- a/board/nxp/imx8mp_evk/imx8mp_evk.c
+++ b/board/nxp/imx8mp_evk/imx8mp_evk.c
@@ -4,9 +4,14 @@
  */
 
 #include <config.h>
+#include <dm.h>
 #include <efi_loader.h>
 #include <env.h>
+#include <init.h>
+#include <usb.h>
 #include <asm/arch/sys_proto.h>
+#include <asm/gpio.h>
+#include <usb/tcpm.h>
 
 #if CONFIG_IS_ENABLED(EFI_HAVE_CAPSULE_SUPPORT)
 #define IMX_BOOT_IMAGE_GUID \
@@ -35,6 +40,79 @@ int board_mmc_get_env_dev(int devno)
 }
 #endif
 
+#define USB_TYPEC_SEL	IMX_GPIO_NR(4, 20)
+#define USB_TYPEC_EN	IMX_GPIO_NR(2, 20)
+
+static struct udevice *tcpc1_tcpm;
+static bool usb1_gpio_inited;
+
+static int usb1_tcpm_init(void)
+{
+	if (tcpc1_tcpm)
+		return 0;
+
+	return tcpm_get(0, &tcpc1_tcpm);
+}
+
+static void ss_mux_select(enum typec_cc_polarity polarity)
+{
+	if (polarity == TYPEC_POLARITY_CC1)
+		gpio_direction_output(USB_TYPEC_SEL, 0);
+	else
+		gpio_direction_output(USB_TYPEC_SEL, 1);
+}
+
+int board_usb_init(int index, enum usb_init_type init)
+{
+	enum typec_cc_polarity pol;
+	int ret;
+
+	if (index != 0)
+		return 0;
+
+	if (!usb1_gpio_inited) {
+		gpio_request(USB_TYPEC_SEL, "typec_sel");
+		gpio_request(USB_TYPEC_EN, "typec_en");
+		usb1_gpio_inited = true;
+	}
+	gpio_direction_output(USB_TYPEC_EN, 0);
+
+	ret = usb1_tcpm_init();
+	if (ret) {
+		printf("USB1: TCPM init failed: %d\n", ret);
+		return ret;
+	}
+
+	if (init == USB_INIT_HOST) {
+		ret = tcpm_setup_host_mode(tcpc1_tcpm, &pol);
+		if (ret) {
+			printf("USB1: no device detected\n");
+			return 0;
+		}
+	} else {
+		ret = tcpm_setup_device_mode(tcpc1_tcpm, &pol);
+		if (ret) {
+			printf("USB1: no host detected\n");
+			return 0;
+		}
+	}
+
+	ss_mux_select(pol);
+
+	return 0;
+}
+
+int board_usb_cleanup(int index, enum usb_init_type init)
+{
+	if (index != 0)
+		return 0;
+
+	if (init == USB_INIT_HOST && tcpc1_tcpm)
+		tcpm_disable_src_vbus(tcpc1_tcpm);
+
+	return 0;
+}
+
 int board_late_init(void)
 {
 #if CONFIG_IS_ENABLED(ENV_IS_IN_MMC)

-- 
2.51.0


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

* Re: [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support
  2026-06-21  2:06 [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support Peng Fan (OSS)
                   ` (9 preceding siblings ...)
  2026-06-21  2:06 ` [PATCH v2 10/10] board: imx8mp_evk: add USB Type-C OTG support with TCPCI Peng Fan (OSS)
@ 2026-06-30  1:48 ` Peng Fan
  2026-06-30  6:58   ` Marek Vasut
  10 siblings, 1 reply; 14+ messages in thread
From: Peng Fan @ 2026-06-30  1:48 UTC (permalink / raw)
  To: u-boot, NXP i.MX U-Boot Team, Sebastian Reichel, Marek Vasut,
	Fabio Estevam, Wang Jie
  Cc: Tom Rini, Patrice Chotard, Yao Zi, Mattijs Korpershoek,
	Jonas Karlman, Kever Yang, Kaustabh Chakraborty, Casey Connolly,
	Ye Li, Sam Protsenko, Stefano Babic, Marek Vasut, Alice Guo,
	Balaji Selvanathan, Peng Fan

Hi Marek,

Do you have time to give a look at this series?

Thanks,
Peng

On Sun, Jun 21, 2026 at 10:06:36AM +0800, Peng Fan (OSS) wrote:
>[V2] Just resend this patchset without
>"usb: tcpm: fix inverted poll condition in tcpm_pd_transmit()"
>
>This series adds TCPCI (Type-C Port Controller Interface) driver support
>to U-Boot's TCPM framework and enables runtime USB host/device mode
>switching on platforms using DWC3 with Type-C connectors.
>
>The work is motivated by the NXP i.MX8MP EVK, which has a PTN5110 TCPCI
>chip managing a USB Type-C data port. The goal is to support both
>"usb start" (host mode, for USB storage) and "ums"/"fastboot" (device
>mode, for flashing) on the same Type-C port, switchable at runtime.
>
>The series breaks down into three logical groups:
>
>TCPM bugfixes (patches 1-3):
>  Fix three issues discovered in the upstream TCPM state machine during
>  PD sink testing with various chargers:
>  - Inverted poll condition causing transmit to always time out
>  - Unnecessary hard resets on sink-only ports when source doesn't
>    respond to capabilities request
>  - Premature exit from poll loop after SNK_READY, missing source
>    follow-up messages (Get_Sink_Cap etc.) that trigger hard reset
>
>TCPCI driver and TCPM API extensions (patches 4-7):
>  - Lightweight PD negotiation tracing (RAM ring buffer, zero serial
>    overhead) for debugging charger interop issues
>  - TCPCI driver for NXP PTN5110 and compatible chips, ported from
>    the Linux kernel with adaptations for U-Boot's polled model
>  - New tcpm_setup_host_mode()/tcpm_setup_device_mode() APIs that
>    configure Type-C CC role and detect cable orientation without
>    running the full PD state machine -- appropriate for U-Boot's
>    synchronous, command-driven execution model
>  - pd-disable connector property support in tcpm_post_probe() to
>    skip the PD state machine for data-only ports while keeping the
>    TCPCI driver ops accessible
>
>DWC3 OTG and board support (patches 8-11):
>  - board_usb_init()/board_usb_cleanup() hooks in DWC3 generic glue,
>    called during child probe/remove to allow board-level Type-C
>    configuration before DWC3 core init
>  - OTG dual-bind: when dr_mode="otg", bind both dwc3-generic-host
>    and dwc3-generic-peripheral children; runtime mode switching via
>    dwc3_otg_deactivate_sibling() which removes the active sibling
>    before probing the new one
>  - i.MX8MP EVK board support wiring it all together: TCPCI DT nodes,
>    board_usb_init using TCPM APIs for CC role setup and SS mux
>    control, PD sink configuration for the power port
>
>Tested on NXP i.MX8MP EVK.
>
>Signed-off-by: Peng Fan <peng.fan@nxp.com>
>---
>Changes in v2:
>- Drop "usb: tcpm: fix inverted poll condition in tcpm_pd_transmit"
>  which was sent to list as a separate fix.
>- Link to v1: https://patch.msgid.link/20260617-tcpci-dwc3-v1-0-b824fe1e9299@nxp.com
>
>---
>Peng Fan (9):
>      usb: tcpm: avoid hard reset for sink-only ports on Source_Cap timeout
>      usb: tcpm: continue polling after PD negotiation to handle source messages
>      usb: tcpm: add lightweight PD negotiation tracing
>      usb: tcpm: add TCPCI (Type-C Port Controller Interface) driver
>      usb: tcpm: add setup_host_mode/device_mode/disable_src_vbus APIs
>      usb: tcpm: skip state machine for pd-disable ports
>      usb: dwc3: generic: support OTG dual-bind for runtime host/device switching
>      board: imx8mp_evk: enable USB PD sink via TCPCI
>      board: imx8mp_evk: add USB Type-C OTG support with TCPCI
>
>Ye Li (1):
>      usb: dwc3: generic: add board_usb_init/cleanup hooks
>
> arch/arm/dts/imx8mp-evk-u-boot.dtsi |  52 ++-
> board/nxp/imx8mp_evk/imx8mp_evk.c   |  78 ++++
> configs/imx8mp_evk_defconfig        |   3 +
> drivers/usb/dwc3/dwc3-generic.c     |  73 +++-
> drivers/usb/tcpm/Kconfig            |   9 +
> drivers/usb/tcpm/Makefile           |   1 +
> drivers/usb/tcpm/tcpci.c            | 778 ++++++++++++++++++++++++++++++++++++
> drivers/usb/tcpm/tcpm-internal.h    |  40 ++
> drivers/usb/tcpm/tcpm-uclass.c      |  87 ++++
> drivers/usb/tcpm/tcpm.c             |  46 ++-
> include/usb/tcpci.h                 | 185 +++++++++
> include/usb/tcpm.h                  |   4 +
> 12 files changed, 1350 insertions(+), 6 deletions(-)
>---
>base-commit: debaadb75013a6d3706a2137d540bf4660475e3e
>change-id: 20260617-tcpci-dwc3-653a5d9c8893
>
>Best regards,
>--  
>Peng Fan <peng.fan@nxp.com>
>

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

* Re: [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support
  2026-06-30  1:48 ` [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support Peng Fan
@ 2026-06-30  6:58   ` Marek Vasut
  2026-06-30  8:16     ` Peng Fan (OSS)
  0 siblings, 1 reply; 14+ messages in thread
From: Marek Vasut @ 2026-06-30  6:58 UTC (permalink / raw)
  To: Peng Fan, u-boot, NXP i.MX U-Boot Team, Jonas Karlman,
	Sebastian Reichel, Fabio Estevam, Wang Jie
  Cc: Tom Rini, Patrice Chotard, Yao Zi, Mattijs Korpershoek,
	Kever Yang, Kaustabh Chakraborty, Casey Connolly, Ye Li,
	Sam Protsenko, Stefano Babic, Alice Guo, Balaji Selvanathan,
	Peng Fan

On 6/30/26 3:48 AM, Peng Fan wrote:

Hello Peng,

> Do you have time to give a look at this series?

No, sorry, I am recently entirely overwhelmed by emails and my backlog 
only grows. Did Sebastian/Jonas provide any feedback on the TCPM part?

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

* RE: [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support
  2026-06-30  6:58   ` Marek Vasut
@ 2026-06-30  8:16     ` Peng Fan (OSS)
  0 siblings, 0 replies; 14+ messages in thread
From: Peng Fan (OSS) @ 2026-06-30  8:16 UTC (permalink / raw)
  To: Marek Vasut, Peng Fan (OSS), u-boot@lists.denx.de, dl-uboot-imx,
	Jonas Karlman, Sebastian Reichel, Fabio Estevam, Wang Jie
  Cc: Tom Rini, Patrice Chotard, Yao Zi, Mattijs Korpershoek,
	Kever Yang, Kaustabh Chakraborty, Casey Connolly, Ye Li,
	Sam Protsenko, Stefano Babic, Alice Guo, Balaji Selvanathan

> Subject: Re: [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG
> dual-role support
> 
> On 6/30/26 3:48 AM, Peng Fan wrote:
> 
> Hello Peng,
> 
> > Do you have time to give a look at this series?
> 
> No, sorry, I am recently entirely overwhelmed by emails and my
> backlog only grows. Did Sebastian/Jonas provide any feedback on the
> TCPM part?

Not yet. I have other parts depending on this patch set, so hope there
are any feedbacks and we could move forward.

Thanks
Peng


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

end of thread, other threads:[~2026-06-30  8:16 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-21  2:06 [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support Peng Fan (OSS)
2026-06-21  2:06 ` [PATCH v2 01/10] usb: tcpm: avoid hard reset for sink-only ports on Source_Cap timeout Peng Fan (OSS)
2026-06-21  2:06 ` [PATCH v2 02/10] usb: tcpm: continue polling after PD negotiation to handle source messages Peng Fan (OSS)
2026-06-21  2:06 ` [PATCH v2 03/10] usb: tcpm: add lightweight PD negotiation tracing Peng Fan (OSS)
2026-06-21  2:06 ` [PATCH v2 04/10] usb: tcpm: add TCPCI (Type-C Port Controller Interface) driver Peng Fan (OSS)
2026-06-21  2:06 ` [PATCH v2 05/10] usb: tcpm: add setup_host_mode/device_mode/disable_src_vbus APIs Peng Fan (OSS)
2026-06-21  2:06 ` [PATCH v2 06/10] usb: tcpm: skip state machine for pd-disable ports Peng Fan (OSS)
2026-06-21  2:06 ` [PATCH v2 07/10] usb: dwc3: generic: add board_usb_init/cleanup hooks Peng Fan (OSS)
2026-06-21  2:06 ` [PATCH v2 08/10] usb: dwc3: generic: support OTG dual-bind for runtime host/device switching Peng Fan (OSS)
2026-06-21  2:06 ` [PATCH v2 09/10] board: imx8mp_evk: enable USB PD sink via TCPCI Peng Fan (OSS)
2026-06-21  2:06 ` [PATCH v2 10/10] board: imx8mp_evk: add USB Type-C OTG support with TCPCI Peng Fan (OSS)
2026-06-30  1:48 ` [PATCH v2 00/10] usb: Add TCPCI driver and DWC3 OTG dual-role support Peng Fan
2026-06-30  6:58   ` Marek Vasut
2026-06-30  8:16     ` Peng Fan (OSS)

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.