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