1 /*
2 * Copyright (c) 2023 The Chromium OS Authors.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/kernel.h>
8 #include <zephyr/device.h>
9 #include <zephyr/devicetree.h>
10 #include <zephyr/usb_c/usbc.h>
11
12 #include <zephyr/logging/log.h>
13 LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG);
14
15 #include "power_ctrl.h"
16
17 #define USBC_PORT0_NODE DT_ALIAS(usbc_port0)
18 #define USBC_PORT0_POWER_ROLE DT_ENUM_IDX(USBC_PORT0_NODE, power_role)
19
20 /* A Source power role evauates to 1. See usbc_tc.h: TC_ROLE_SOURCE */
21 #if (USBC_PORT0_POWER_ROLE != 1)
22 #error "Unsupported board: Only Source device supported"
23 #endif
24
25 #define SOURCE_PDO(node_id, prop, idx) (DT_PROP_BY_IDX(node_id, prop, idx)),
26
27 /* usbc.rst port data object start */
28 /**
29 * @brief A structure that encapsulates Port data.
30 */
31 static struct port0_data_t {
32 /** Source Capabilities */
33 uint32_t src_caps[DT_PROP_LEN(USBC_PORT0_NODE, source_pdos)];
34 /** Number of Source Capabilities */
35 int src_cap_cnt;
36 /** CC Rp value */
37 int rp;
38 /** Sink Request RDO */
39 union pd_rdo sink_request;
40 /** Requested Object Pos */
41 int obj_pos;
42 /** VCONN CC line*/
43 enum tc_cc_polarity vconn_pol;
44 /** True if power supply is ready */
45 bool ps_ready;
46 /** True if power supply should transition to a new level */
47 bool ps_tran_start;
48 /** Log Sink Requested RDO to console */
49 atomic_t show_sink_request;
50 } port0_data = {
51 .rp = DT_ENUM_IDX(USBC_PORT0_NODE, typec_power_opmode),
52 .src_caps = {DT_FOREACH_PROP_ELEM(USBC_PORT0_NODE, source_pdos, SOURCE_PDO)},
53 .src_cap_cnt = DT_PROP_LEN(USBC_PORT0_NODE, source_pdos),
54 };
55
56 /* usbc.rst port data object end */
57
dump_sink_request_rdo(const uint32_t rdo)58 static void dump_sink_request_rdo(const uint32_t rdo)
59 {
60 union pd_rdo request;
61
62 request.raw_value = rdo;
63
64 LOG_INF("REQUEST RDO: %08x", rdo);
65 LOG_INF("\tObject Position:\t %d", request.fixed.object_pos);
66 LOG_INF("\tGiveback:\t\t %d", request.fixed.giveback);
67 LOG_INF("\tCapability Mismatch:\t %d", request.fixed.cap_mismatch);
68 LOG_INF("\tUSB Comm Capable:\t %d", request.fixed.usb_comm_capable);
69 LOG_INF("\tNo USB Suspend:\t\t %d", request.fixed.no_usb_suspend);
70 LOG_INF("\tUnchunk Ext MSG Support: %d", request.fixed.unchunked_ext_msg_supported);
71 LOG_INF("\tOperating Current:\t %d mA",
72 PD_CONVERT_FIXED_PDO_CURRENT_TO_MA(request.fixed.operating_current));
73 if (request.fixed.giveback) {
74 LOG_INF("\tMax Operating Current:\t %d mA",
75 PD_CONVERT_FIXED_PDO_CURRENT_TO_MA(request.fixed.min_or_max_operating_current));
76 } else {
77 LOG_INF("\tMin Operating Current:\t %d mA",
78 PD_CONVERT_FIXED_PDO_CURRENT_TO_MA(request.fixed.min_or_max_operating_current));
79 }
80 }
81
82 /* usbc.rst callbacks start */
83 /**
84 * @brief PE calls this function when it needs to set the Rp on CC
85 */
port0_policy_cb_get_src_rp(const struct device * dev,enum tc_rp_value * rp)86 int port0_policy_cb_get_src_rp(const struct device *dev,
87 enum tc_rp_value *rp)
88 {
89 struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
90
91 *rp = dpm_data->rp;
92
93 return 0;
94 }
95
96 /**
97 * @brief PE calls this function to Enable (5V) or Disable (0V) the
98 * Power Supply
99 */
port0_policy_cb_src_en(const struct device * dev,bool en)100 int port0_policy_cb_src_en(const struct device *dev, bool en)
101 {
102 source_ctrl_set(en ? SOURCE_5V : SOURCE_0V);
103
104 return 0;
105 }
106
107 /**
108 * @brief PE calls this function to Enable or Disable VCONN
109 */
port0_policy_cb_vconn_en(const struct device * dev,enum tc_cc_polarity pol,bool en)110 int port0_policy_cb_vconn_en(const struct device *dev, enum tc_cc_polarity pol, bool en)
111 {
112 struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
113
114 dpm_data->vconn_pol = pol;
115
116 if (en == false) {
117 /* Disable VCONN on CC1 and CC2 */
118 vconn_ctrl_set(VCONN_OFF);
119 } else if (pol == TC_POLARITY_CC1) {
120 /* set VCONN on CC1 */
121 vconn_ctrl_set(VCONN1_ON);
122 } else {
123 /* set VCONN on CC2 */
124 vconn_ctrl_set(VCONN2_ON);
125 }
126
127 return 0;
128 }
129
130 /**
131 * @brief PE calls this function to get the Source Caps that will be sent
132 * to the Sink
133 */
port0_policy_cb_get_src_caps(const struct device * dev,const uint32_t ** pdos,uint32_t * num_pdos)134 int port0_policy_cb_get_src_caps(const struct device *dev,
135 const uint32_t **pdos, uint32_t *num_pdos)
136 {
137 struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
138
139 *pdos = dpm_data->src_caps;
140 *num_pdos = dpm_data->src_cap_cnt;
141
142 return 0;
143 }
144
145 /**
146 * @brief PE calls this function to verify that a Sink's request if valid
147 */
port0_policy_cb_check_sink_request(const struct device * dev,const uint32_t request_msg)148 static enum usbc_snk_req_reply_t port0_policy_cb_check_sink_request(const struct device *dev,
149 const uint32_t request_msg)
150 {
151 struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
152 union pd_fixed_supply_pdo_source pdo;
153 uint32_t obj_pos;
154 uint32_t op_current;
155
156 dpm_data->sink_request.raw_value = request_msg;
157 obj_pos = dpm_data->sink_request.fixed.object_pos;
158 op_current =
159 PD_CONVERT_FIXED_PDO_CURRENT_TO_MA(dpm_data->sink_request.fixed.operating_current);
160
161 if (obj_pos == 0 || obj_pos > dpm_data->src_cap_cnt) {
162 return SNK_REQUEST_REJECT;
163 }
164
165 pdo.raw_value = dpm_data->src_caps[obj_pos - 1];
166
167 if (dpm_data->sink_request.fixed.operating_current > pdo.max_current) {
168 return SNK_REQUEST_REJECT;
169 }
170
171 dpm_data->obj_pos = obj_pos;
172
173 atomic_set_bit(&port0_data.show_sink_request, 0);
174
175 /*
176 * Clear PS ready. This will be set to true after PS is ready after
177 * it transitions to the new level.
178 */
179 port0_data.ps_ready = false;
180
181 return SNK_REQUEST_VALID;
182 }
183
184 /**
185 * @brief PE calls this function to check if the Power Supply is at the requested
186 * level
187 */
port0_policy_cb_is_ps_ready(const struct device * dev)188 static bool port0_policy_cb_is_ps_ready(const struct device *dev)
189 {
190 struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
191
192
193 /* Return true to inform that the Power Supply is ready */
194 return dpm_data->ps_ready;
195 }
196
197 /**
198 * @brief PE calls this function to check if the Present Contract is still
199 * valid
200 */
port0_policy_cb_present_contract_is_valid(const struct device * dev,const uint32_t present_contract)201 static bool port0_policy_cb_present_contract_is_valid(const struct device *dev,
202 const uint32_t present_contract)
203 {
204 struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
205 union pd_fixed_supply_pdo_source pdo;
206 union pd_rdo request;
207 uint32_t obj_pos;
208 uint32_t op_current;
209
210 request.raw_value = present_contract;
211 obj_pos = request.fixed.object_pos;
212 op_current = PD_CONVERT_FIXED_PDO_CURRENT_TO_MA(request.fixed.operating_current);
213
214 if (obj_pos == 0 || obj_pos > dpm_data->src_cap_cnt) {
215 return false;
216 }
217
218 pdo.raw_value = dpm_data->src_caps[obj_pos - 1];
219
220 if (request.fixed.operating_current > pdo.max_current) {
221 return false;
222 }
223
224 return true;
225 }
226
227 /* usbc.rst callbacks end */
228
229 /* usbc.rst notify start */
port0_notify(const struct device * dev,const enum usbc_policy_notify_t policy_notify)230 static void port0_notify(const struct device *dev,
231 const enum usbc_policy_notify_t policy_notify)
232 {
233 struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
234
235 switch (policy_notify) {
236 case PROTOCOL_ERROR:
237 break;
238 case MSG_DISCARDED:
239 break;
240 case MSG_ACCEPT_RECEIVED:
241 break;
242 case MSG_REJECTED_RECEIVED:
243 break;
244 case MSG_NOT_SUPPORTED_RECEIVED:
245 break;
246 case TRANSITION_PS:
247 dpm_data->ps_tran_start = true;
248 break;
249 case PD_CONNECTED:
250 break;
251 case NOT_PD_CONNECTED:
252 break;
253 case DATA_ROLE_IS_UFP:
254 break;
255 case DATA_ROLE_IS_DFP:
256 break;
257 case PORT_PARTNER_NOT_RESPONSIVE:
258 LOG_INF("Port Partner not PD Capable");
259 break;
260 case HARD_RESET_RECEIVED:
261 /*
262 * This notification is sent from the PE_SRC_Transition_to_default
263 * state and requires the following:
264 * 1: Vconn should be turned OFF
265 * 2: Reset of the local hardware
266 */
267
268 /* Power off VCONN */
269 vconn_ctrl_set(VCONN_OFF);
270 /* Transition PS to Default level */
271 source_ctrl_set(SOURCE_5V);
272 break;
273 default:
274 }
275 }
276 /* usbc.rst notify end */
277
278 /* usbc.rst check start */
port0_policy_check(const struct device * dev,const enum usbc_policy_check_t policy_check)279 bool port0_policy_check(const struct device *dev,
280 const enum usbc_policy_check_t policy_check)
281 {
282 struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
283
284 switch (policy_check) {
285 case CHECK_POWER_ROLE_SWAP:
286 /* Reject power role swaps */
287 return false;
288 case CHECK_DATA_ROLE_SWAP_TO_DFP:
289 /* Accept data role swap to DFP */
290 return true;
291 case CHECK_DATA_ROLE_SWAP_TO_UFP:
292 /* Reject data role swap to UFP */
293 return false;
294 case CHECK_SRC_PS_AT_DEFAULT_LEVEL:
295 /*
296 * This check is sent from the PE_SRC_Transition_to_default
297 * state and requires the following:
298 * 1: Vconn should be turned ON
299 * 2: Return TRUE when Power Supply is at default level
300 */
301
302 /* Power on VCONN */
303 vconn_ctrl_set(dpm_data->vconn_pol);
304
305 /* PS should be at default level after receiving a Hard Reset */
306 return true;
307 default:
308 /* Reject all other policy checks */
309 return false;
310
311 }
312 }
313 /* usbc.rst check end */
314
main(void)315 int main(void)
316 {
317 const struct device *usbc_port0;
318
319 /* Get the device for this port */
320 usbc_port0 = DEVICE_DT_GET(USBC_PORT0_NODE);
321 if (!device_is_ready(usbc_port0)) {
322 LOG_ERR("PORT0 device not ready");
323 return 0;
324 }
325
326 /* usbc.rst register start */
327 /* Register USB-C Callbacks */
328
329 /* Register Policy Check callback */
330 usbc_set_policy_cb_check(usbc_port0, port0_policy_check);
331 /* Register Policy Notify callback */
332 usbc_set_policy_cb_notify(usbc_port0, port0_notify);
333 /* Register Policy callback to set the Rp on CC lines */
334 usbc_set_policy_cb_get_src_rp(usbc_port0, port0_policy_cb_get_src_rp);
335 /* Register Policy callback to enable or disable power supply */
336 usbc_set_policy_cb_src_en(usbc_port0, port0_policy_cb_src_en);
337 /* Register Policy callback to enable or disable vconn */
338 usbc_set_vconn_control_cb(usbc_port0, port0_policy_cb_vconn_en);
339 /* Register Policy callback to send the source caps to the sink */
340 usbc_set_policy_cb_get_src_caps(usbc_port0, port0_policy_cb_get_src_caps);
341 /* Register Policy callback to check if the sink request is valid */
342 usbc_set_policy_cb_check_sink_request(usbc_port0, port0_policy_cb_check_sink_request);
343 /* Register Policy callback to check if the power supply is ready */
344 usbc_set_policy_cb_is_ps_ready(usbc_port0, port0_policy_cb_is_ps_ready);
345 /* Register Policy callback to check if Present Contract is still valid */
346 usbc_set_policy_cb_present_contract_is_valid(usbc_port0,
347 port0_policy_cb_present_contract_is_valid);
348
349 /* usbc.rst register end */
350
351 /* usbc.rst user data start */
352 /* Set Application port data object. This object is passed to the policy callbacks */
353 usbc_set_dpm_data(usbc_port0, &port0_data);
354 /* usbc.rst user data end */
355
356 /* Flag to show sink request */
357 port0_data.show_sink_request = ATOMIC_INIT(0);
358
359 /* Init power supply transition start */
360 port0_data.ps_tran_start = false;
361 /* Init power supply ready */
362 port0_data.ps_ready = false;
363
364 /* usbc.rst usbc start */
365 /* Start the USB-C Subsystem */
366 usbc_start(usbc_port0);
367 /* usbc.rst usbc end */
368
369 while (1) {
370 /* Perform Application Specific functions */
371
372 /* Transition PS to new level */
373 if (port0_data.ps_tran_start) {
374 /*
375 * Transition Power Supply to new voltage.
376 * Okay if this blocks.
377 */
378 source_ctrl_set(port0_data.obj_pos);
379 port0_data.ps_ready = true;
380 port0_data.ps_tran_start = false;
381 }
382
383 /* Display Sink Requests */
384 if (atomic_test_and_clear_bit(&port0_data.show_sink_request, 0)) {
385 /* Display the Sink request */
386 dump_sink_request_rdo(port0_data.sink_request.raw_value);
387 }
388 /* Arbitrary delay */
389 k_msleep(10);
390 }
391
392 return 0;
393 }
394