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)设备的实现。
CONFIG_USBC_CSM_SINK_ONLY:受电端 USB-C 连接状态机CONFIG_USBC_CSM_SOURCE_ONLY:供电端 USBC 连接状态机CONFIG_USBC_CSM_DRP:双角色电源(DRP)USB-C 连接状态机
列表 中提供了用于不同目的的示例。
实现受电端 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 切换行为:
CONFIG_USBC_DRP_PERIOD_MS:切换周期(50-100 毫秒,默认 75 毫秒)CONFIG_USBC_DRP_DUTY_CYCLE:作为供电端的时间百分比(30-70%,默认 50%)
完整示例请参见 Basic USB-C DRP。