[PATCH 04/11] usb: tcpm: add lightweight PD negotiation tracing

Peng Fan (OSS) peng.fan at oss.nxp.com
Wed Jun 17 10:23:03 CEST 2026


From: Peng Fan <peng.fan at 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 at 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



More information about the U-Boot mailing list