* [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 *)®);
+ 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 *)®);
+ 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, ®);
+ 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, ®);
+ 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.