USB-C 设备协议栈

USB-C 设备协议栈是 Type-C 端口控制器(TCPC)与客户应用之间的硬件无关接口。它移植自 Google ChromeOS 的 Type-C 端口管理器(TCPM)协议栈。它提供以下功能:

  • 使用 Type-C 端口控制器驱动提供的 API 与 Type-C 端口控制器交互。

  • 提供供客户应用使用的编程接口。API 说明见 include/zephyr/usb_c/usbc.h。

配置选项

USB-C 设备协议栈支持仅受电端(Sink)、仅供电端(Source)以及双角色电源(DRP)设备的实现。

列表 中提供了用于不同目的的示例。

实现受电端 Type-C 与 Power Delivery USB-C 设备

USB-C 设备的配置在协议栈层和设备树中完成。

需要定义以下设备树内容、结构和回调:

  • 引用 TCPC 的设备树 usb-c-connector 节点

  • 引用 VBUS 测量设备的设备树 vbus 节点

  • 封装应用特定数据的用户定义结构

  • 策略回调

例如,对于 USB-C 受电端示例应用:

每个物理 Type-C 端口在设备树中由 usb-c-connector 兼容节点表示:

1		port1: usbc-port@1 {
2			compatible = "usb-c-connector";
3			reg = <1>;
4			tcpc = <&ucpd1>;
5			vbus = <&vbus1>;
6			power-role = "sink";
7			sink-pdos = <PDO_FIXED(5000, 100, 0)>;
8		};

VBUS 由设备树中通过 usb-c-vbus-adc 兼容节点引用的设备测量:

1	vbus1: vbus {
2		compatible = "zephyr,usb-c-vbus-adc";
3		io-channels = <&adc2 8>;
4		output-ohms = <49900>;
5		full-ohms = <(330000 + 49900)>;
6	};

定义用户定义的结构,稍后向子系统注册,并可通过 API 从回调中访问:

 1/**
 2 * @brief A structure that encapsulates Port data.
 3 */
 4static struct port0_data_t {
 5	/** Sink Capabilities */
 6	uint32_t snk_caps[DT_PROP_LEN(USBC_PORT0_NODE, sink_pdos)];
 7	/** Number of Sink Capabilities */
 8	int snk_cap_cnt;
 9	/** Source Capabilities */
10	uint32_t src_caps[PDO_MAX_DATA_OBJECTS];
11	/** Number of Source Capabilities */
12	int src_cap_cnt;
13	/* Power Supply Ready flag */
14	atomic_t ps_ready;
15} port0_data = {.snk_caps = DT_PROP(USBC_PORT0_NODE, sink_pdos),
16		.snk_cap_cnt = DT_PROP_LEN(USBC_PORT0_NODE, sink_pdos),
17		.src_caps = {0},
18		.src_cap_cnt = 0,
19		.ps_ready = 0};
20

子系统使用这些回调来设置或获取应用特定数据:

 1static int port0_policy_cb_get_snk_cap(const struct device *dev, uint32_t **pdos, int *num_pdos)
 2{
 3	struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
 4
 5	*pdos = dpm_data->snk_caps;
 6	*num_pdos = dpm_data->snk_cap_cnt;
 7
 8	return 0;
 9}
10
11static void port0_policy_cb_set_src_cap(const struct device *dev, const uint32_t *pdos,
12					const int num_pdos)
13{
14	struct port0_data_t *dpm_data;
15	int num;
16	int i;
17
18	dpm_data = usbc_get_dpm_data(dev);
19
20	num = num_pdos;
21	if (num > PDO_MAX_DATA_OBJECTS) {
22		num = PDO_MAX_DATA_OBJECTS;
23	}
24
25	for (i = 0; i < num; i++) {
26		dpm_data->src_caps[i] = *(pdos + i);
27	}
28
29	dpm_data->src_cap_cnt = num;
30}
31
32static uint32_t port0_policy_cb_get_rdo(const struct device *dev)
33{
34	struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
35
36	return build_rdo(dpm_data);
37}

子系统使用此回调查询是否可以执行某项操作:

 1bool port0_policy_check(const struct device *dev, const enum usbc_policy_check_t policy_check)
 2{
 3	switch (policy_check) {
 4	case CHECK_POWER_ROLE_SWAP:
 5		/* Reject power role swaps */
 6		return false;
 7	case CHECK_DATA_ROLE_SWAP_TO_DFP:
 8		/* Reject data role swap to DFP */
 9		return false;
10	case CHECK_DATA_ROLE_SWAP_TO_UFP:
11		/* Accept data role swap to UFP */
12		return true;
13	case CHECK_SNK_AT_DEFAULT_LEVEL:
14		/* This device is always at the default power level */
15		return true;
16	default:
17		/* Reject all other policy checks */
18		return false;
19	}
20}

子系统使用此回调向应用通知事件:

 1static void port0_notify(const struct device *dev, const enum usbc_policy_notify_t policy_notify)
 2{
 3	struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
 4
 5	switch (policy_notify) {
 6	case PROTOCOL_ERROR:
 7		break;
 8	case MSG_DISCARDED:
 9		break;
10	case MSG_ACCEPT_RECEIVED:
11		break;
12	case MSG_REJECTED_RECEIVED:
13		break;
14	case MSG_NOT_SUPPORTED_RECEIVED:
15		break;
16	case TRANSITION_PS:
17		atomic_set_bit(&dpm_data->ps_ready, 0);
18		break;
19	case PD_CONNECTED:
20		break;
21	case NOT_PD_CONNECTED:
22		break;
23	case POWER_CHANGE_0A0:
24		LOG_INF("PWR 0A");
25		break;
26	case POWER_CHANGE_DEF:
27		LOG_INF("PWR DEF");
28		break;
29	case POWER_CHANGE_1A5:
30		LOG_INF("PWR 1A5");
31		break;
32	case POWER_CHANGE_3A0:
33		LOG_INF("PWR 3A0");
34		break;
35	case DATA_ROLE_IS_UFP:
36		break;
37	case DATA_ROLE_IS_DFP:
38		break;
39	case PORT_PARTNER_NOT_RESPONSIVE:
40		LOG_INF("Port Partner not PD Capable");
41		break;
42	case SNK_TRANSITION_TO_DEFAULT:
43		break;
44	case HARD_RESET_RECEIVED:
45		break;
46	case SENDER_RESPONSE_TIMEOUT:
47		break;
48	case SOURCE_CAPABILITIES_RECEIVED:
49		break;
50	default:
51	}
52}

注册回调:

 1	/* Register USB-C Callbacks */
 2
 3	/* Register Policy Check callback */
 4	usbc_set_policy_cb_check(usbc_port0, port0_policy_check);
 5	/* Register Policy Notify callback */
 6	usbc_set_policy_cb_notify(usbc_port0, port0_notify);
 7	/* Register Policy Get Sink Capabilities callback */
 8	usbc_set_policy_cb_get_snk_cap(usbc_port0, port0_policy_cb_get_snk_cap);
 9	/* Register Policy Set Source Capabilities callback */
10	usbc_set_policy_cb_set_src_cap(usbc_port0, port0_policy_cb_set_src_cap);
11	/* Register Policy Get Request Data Object callback */
12	usbc_set_policy_cb_get_rdo(usbc_port0, port0_policy_cb_get_rdo);

注册用户定义的结构:

1	/* Set Application port data object. This object is passed to the policy callbacks */
2	port0_data.ps_ready = ATOMIC_INIT(0);
3	usbc_set_dpm_data(usbc_port0, &port0_data);

启动 USB-C 子系统:

1	/* Start the USB-C Subsystem */
2	usbc_start(usbc_port0);

实现供电端 Type-C 与 Power Delivery USB-C 设备

USB-C 设备的配置在协议栈层和设备树中完成。

定义以下设备树内容、结构和回调:

  • 引用 TCPC 的设备树 usb-c-connector 节点

  • 引用 VBUS 测量设备的设备树 vbus 节点

  • 用于 VBUS 和 VCONN 电源控制的设备树 pwrctrl 节点

  • 封装应用特定数据的用户定义结构

  • 策略回调

例如,对于 USB-C 供电端示例应用:

每个物理 Type-C 端口在设备树中由 usb-c-connector 兼容节点表示:

 1		port1: usbc-port@1 {
 2			compatible = "usb-c-connector";
 3			reg = <1>;
 4			tcpc = <&ucpd1>;
 5			vbus = <&vbus1>;
 6			power-role = "source";
 7			typec-power-opmode = "3.0A";
 8			source-pdos = <PDO_FIXED(5000, 100, 0) PDO_FIXED(9000, 100, 0)
 9				       PDO_FIXED(15000, 100, 0)>;
10		};

VBUS 由设备树中通过 usb-c-vbus-adc 兼容节点引用的设备测量:

1	vbus1: vbus {
2		compatible = "zephyr,usb-c-vbus-adc";
3		io-channels = <&adc1 9>;
4		output-ohms = <49900>;
5		full-ohms = <(330000 + 49900)>;
6
7		/* Pin B13 is used to control VBUS Discharge for Port1 */
8		discharge-gpios = <&gpiob 13 GPIO_ACTIVE_HIGH>;
9	};

VBUS 和 VCONN 的电源控制可以由设备树中通过 zephyr,usb-c-pwrctrl 兼容节点引用的设备管理:

 1	pwrctrl1: pwrctrl {
 2		compatible = "zephyr,usb-c-pwrctrl";
 3
 4		/* Pin D3 is used to enable VBUS Source */
 5		source-en-gpios = <&gpiod 3 GPIO_ACTIVE_HIGH>;
 6
 7		/* Pin A1 is used for DCDC_EN */
 8		dcdc-en-gpios = <&gpioa 1 GPIO_ACTIVE_HIGH>;
 9
10		/* Pin D4 enables VCONN on CC1 */
11		vconn1-en-gpios = <&gpiod 4 GPIO_ACTIVE_LOW>;
12
13		/* Pin B9 enables VCONN on CC2 */
14		vconn2-en-gpios = <&gpiob 9 GPIO_ACTIVE_LOW>;
15
16		/* PWM for voltage selection */
17		pwms = <&pwm15 1 PWM_USEC(50) PWM_POLARITY_INVERTED>;
18	};

定义用户定义的结构,稍后向子系统注册,并可通过 API 从回调中访问:

 1/**
 2 * @brief A structure that encapsulates Port data.
 3 */
 4static struct port0_data_t {
 5	/** Power controller device */
 6	const struct device *pwrctrl;
 7	/** Source Capabilities */
 8	uint32_t src_caps[DT_PROP_LEN(USBC_PORT0_NODE, source_pdos)];
 9	/** Number of Source Capabilities */
10	int src_cap_cnt;
11	/** CC Rp value */
12	int rp;
13	/** Sink Request RDO */
14	union pd_rdo sink_request;
15	/** Requested Object Pos */
16	int obj_pos;
17	/** VCONN CC line*/
18	enum tc_cc_polarity vconn_pol;
19	/** True if power supply is ready */
20	bool ps_ready;
21	/** True if power supply should transition to a new level */
22	bool ps_tran_start;
23	/** Log Sink Requested RDO to console */
24	atomic_t show_sink_request;
25} port0_data = {
26	.rp = DT_ENUM_IDX(USBC_PORT0_NODE, typec_power_opmode),
27	.src_caps = DT_PROP(USBC_PORT0_NODE, source_pdos),
28	.src_cap_cnt = DT_PROP_LEN(USBC_PORT0_NODE, source_pdos),
29};
30

子系统使用这些回调来设置或获取应用特定数据:

  1/**
  2 * @brief PE calls this function when it needs to set the Rp on CC
  3 */
  4int port0_policy_cb_get_src_rp(const struct device *dev, enum tc_rp_value *rp)
  5{
  6	struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
  7
  8	*rp = dpm_data->rp;
  9
 10	return 0;
 11}
 12
 13/**
 14 * @brief PE calls this function to Enable (5V) or Disable (0V) the
 15 *	  Power Supply
 16 */
 17int port0_policy_cb_src_en(const struct device *dev, bool en)
 18{
 19	struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
 20
 21	source_ctrl_set(dpm_data->pwrctrl, en ? SOURCE_5V : SOURCE_0V);
 22
 23	return 0;
 24}
 25
 26/**
 27 * @brief PE calls this function to Enable or Disable VCONN
 28 */
 29int port0_policy_cb_vconn_en(const struct device *tcpc_dev, const struct device *usbc_dev,
 30			     enum tc_cc_polarity pol, bool en)
 31{
 32	struct port0_data_t *dpm_data = usbc_get_dpm_data(usbc_dev);
 33
 34	dpm_data->vconn_pol = pol;
 35
 36	if (en == false) {
 37		/* Disable VCONN on CC1 and CC2 */
 38		vconn_ctrl_set(dpm_data->pwrctrl, VCONN_OFF);
 39	} else if (pol == TC_POLARITY_CC1) {
 40		/* set VCONN on CC1 */
 41		vconn_ctrl_set(dpm_data->pwrctrl, VCONN1_ON);
 42	} else {
 43		/* set VCONN on CC2 */
 44		vconn_ctrl_set(dpm_data->pwrctrl, VCONN2_ON);
 45	}
 46
 47	return 0;
 48}
 49
 50/**
 51 * @brief PE calls this function to get the Source Caps that will be sent
 52 *	  to the Sink
 53 */
 54int port0_policy_cb_get_src_caps(const struct device *dev, const uint32_t **pdos,
 55				 uint32_t *num_pdos)
 56{
 57	struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
 58
 59	*pdos = dpm_data->src_caps;
 60	*num_pdos = dpm_data->src_cap_cnt;
 61
 62	return 0;
 63}
 64
 65/**
 66 * @brief PE calls this function to verify that a Sink's request if valid
 67 */
 68static enum usbc_snk_req_reply_t port0_policy_cb_check_sink_request(const struct device *dev,
 69								    const uint32_t request_msg)
 70{
 71	struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
 72	union pd_fixed_supply_pdo_source pdo;
 73	uint32_t obj_pos;
 74	uint32_t op_current;
 75
 76	dpm_data->sink_request.raw_value = request_msg;
 77	obj_pos = dpm_data->sink_request.fixed.object_pos;
 78	op_current =
 79		PD_CONVERT_FIXED_PDO_CURRENT_TO_MA(dpm_data->sink_request.fixed.operating_current);
 80
 81	if (obj_pos == 0 || obj_pos > dpm_data->src_cap_cnt) {
 82		return SNK_REQUEST_REJECT;
 83	}
 84
 85	pdo.raw_value = dpm_data->src_caps[obj_pos - 1];
 86
 87	if (dpm_data->sink_request.fixed.operating_current > pdo.max_current) {
 88		return SNK_REQUEST_REJECT;
 89	}
 90
 91	dpm_data->obj_pos = obj_pos;
 92
 93	atomic_set_bit(&port0_data.show_sink_request, 0);
 94
 95	/*
 96	 * Clear PS ready. This will be set to true after PS is ready after
 97	 * it transitions to the new level.
 98	 */
 99	port0_data.ps_ready = false;
100
101	return SNK_REQUEST_VALID;
102}
103
104/**
105 * @brief PE calls this function to check if the Power Supply is at the requested
106 *	  level
107 */
108static bool port0_policy_cb_is_ps_ready(const struct device *dev)
109{
110	struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
111
112	/* Return true to inform that the Power Supply is ready */
113	return dpm_data->ps_ready;
114}
115
116/**
117 * @brief PE calls this function to check if the Present Contract is still
118 *	  valid
119 */
120static bool port0_policy_cb_present_contract_is_valid(const struct device *dev,
121						      const uint32_t present_contract)
122{
123	struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
124	union pd_fixed_supply_pdo_source pdo;
125	union pd_rdo request;
126	uint32_t obj_pos;
127	uint32_t op_current;
128
129	request.raw_value = present_contract;
130	obj_pos = request.fixed.object_pos;
131	op_current = PD_CONVERT_FIXED_PDO_CURRENT_TO_MA(request.fixed.operating_current);
132
133	if (obj_pos == 0 || obj_pos > dpm_data->src_cap_cnt) {
134		return false;
135	}
136
137	pdo.raw_value = dpm_data->src_caps[obj_pos - 1];
138
139	if (request.fixed.operating_current > pdo.max_current) {
140		return false;
141	}
142
143	return true;
144}
145

子系统使用此回调查询是否可以执行某项操作:

 1bool port0_policy_check(const struct device *dev, const enum usbc_policy_check_t policy_check)
 2{
 3	struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
 4
 5	switch (policy_check) {
 6	case CHECK_POWER_ROLE_SWAP:
 7		/* Reject power role swaps */
 8		return false;
 9	case CHECK_DATA_ROLE_SWAP_TO_DFP:
10		/* Accept data role swap to DFP */
11		return true;
12	case CHECK_DATA_ROLE_SWAP_TO_UFP:
13		/* Reject data role swap to UFP */
14		return false;
15	case CHECK_SRC_PS_AT_DEFAULT_LEVEL:
16		/*
17		 * This check is sent from the PE_SRC_Transition_to_default
18		 * state and requires the following:
19		 *	1: Vconn should be turned ON
20		 *	2: Return TRUE when Power Supply is at default level
21		 */
22
23		/* Power on VCONN */
24		vconn_ctrl_set(dpm_data->pwrctrl, dpm_data->vconn_pol);
25
26		/* PS should be at default level after receiving a Hard Reset */
27		return true;
28	default:
29		/* Reject all other policy checks */
30		return false;
31	}
32}

子系统使用此回调向应用通知事件:

 1static void port0_notify(const struct device *dev, const enum usbc_policy_notify_t policy_notify)
 2{
 3	struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
 4
 5	switch (policy_notify) {
 6	case PROTOCOL_ERROR:
 7		break;
 8	case MSG_DISCARDED:
 9		break;
10	case MSG_ACCEPT_RECEIVED:
11		break;
12	case MSG_REJECTED_RECEIVED:
13		break;
14	case MSG_NOT_SUPPORTED_RECEIVED:
15		break;
16	case TRANSITION_PS:
17		dpm_data->ps_tran_start = true;
18		break;
19	case PD_CONNECTED:
20		break;
21	case NOT_PD_CONNECTED:
22		break;
23	case DATA_ROLE_IS_UFP:
24		break;
25	case DATA_ROLE_IS_DFP:
26		break;
27	case PORT_PARTNER_NOT_RESPONSIVE:
28		LOG_INF("Port Partner not PD Capable");
29		break;
30	case HARD_RESET_RECEIVED:
31		/*
32		 * This notification is sent from the PE_SRC_Transition_to_default
33		 * state and requires the following:
34		 *	1: Vconn should be turned OFF
35		 *	2: Reset of the local hardware
36		 */
37
38		/* Power off VCONN */
39		vconn_ctrl_set(dpm_data->pwrctrl, VCONN_OFF);
40		/* Transition PS to Default level */
41		source_ctrl_set(dpm_data->pwrctrl, SOURCE_5V);
42		break;
43	default:
44	}
45}

注册回调:

 1	/* Register USB-C Callbacks */
 2
 3	/* Register Policy Check callback */
 4	usbc_set_policy_cb_check(usbc_port0, port0_policy_check);
 5	/* Register Policy Notify callback */
 6	usbc_set_policy_cb_notify(usbc_port0, port0_notify);
 7	/* Register Policy callback to set the Rp on CC lines */
 8	usbc_set_policy_cb_get_src_rp(usbc_port0, port0_policy_cb_get_src_rp);
 9	/* Register Policy callback to enable or disable power supply */
10	usbc_set_policy_cb_src_en(usbc_port0, port0_policy_cb_src_en);
11	/* Register Policy callback to enable or disable vconn */
12	usbc_set_vconn_control_cb(usbc_port0, port0_policy_cb_vconn_en);
13	/* Register Policy callback to send the source caps to the sink */
14	usbc_set_policy_cb_get_src_caps(usbc_port0, port0_policy_cb_get_src_caps);
15	/* Register Policy callback to check if the sink request is valid */
16	usbc_set_policy_cb_check_sink_request(usbc_port0, port0_policy_cb_check_sink_request);
17	/* Register Policy callback to check if the power supply is ready */
18	usbc_set_policy_cb_is_ps_ready(usbc_port0, port0_policy_cb_is_ps_ready);
19	/* Register Policy callback to check if Present Contract is still valid */
20	usbc_set_policy_cb_present_contract_is_valid(usbc_port0,
21						     port0_policy_cb_present_contract_is_valid);
22

注册用户定义的结构:

1	/* Set Application port data object. This object is passed to the policy callbacks */
2	usbc_set_dpm_data(usbc_port0, &port0_data);

启动 USB-C 子系统:

1	/* Start the USB-C Subsystem */
2	usbc_start(usbc_port0);

实现双角色电源(DRP)USB-C 设备

DRP 设备既可以作为供电端,也可以作为受电端运行,并自动与端口对端协商合适的角色。未连接时,设备会在供电端(Rp)和受电端(Rd)的 CC 线通告之间切换,以检测并连接任何类型的对端。检测到连接后,设备会进入相应的已连接状态(Attached.SRC 或 Attached.SNK),并启动对应的策略引擎状态机(PE_SRC 或 PE_SNK)来协商电源传输。

配置与供电端和受电端设备类似,但有以下关键区别:

  • 在设备树 usb-c-connector 节点中设置 power-role = "dual"

  • 为供电端和受电端操作实现回调

可以通过 Kconfig 配置 DRP 切换行为:

完整示例请参见 Basic USB-C DRP。

API 参考

USB-C Device API

受电端(SINK)回调参考

Sink_callbacks

供电端(SOURCE)回调参考

Source_callbacks