1 /*
2  * Copyright (c) 2022 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 #define USBC_PORT0_NODE		DT_ALIAS(usbc_port0)
16 #define USBC_PORT0_POWER_ROLE	DT_ENUM_IDX(USBC_PORT0_NODE, power_role)
17 
18 #if (USBC_PORT0_POWER_ROLE != TC_ROLE_SINK)
19 #error "Unsupported board: Only Sink device supported"
20 #endif
21 
22 #define SINK_PDO(node_id, prop, idx)	(DT_PROP_BY_IDX(node_id, prop, idx)),
23 
24 /* usbc.rst port data object start */
25 /**
26  * @brief A structure that encapsulates Port data.
27  */
28 static struct port0_data_t {
29 	/** Sink Capabilities */
30 	uint32_t snk_caps[DT_PROP_LEN(USBC_PORT0_NODE, sink_pdos)];
31 	/** Number of Sink Capabilities */
32 	int snk_cap_cnt;
33 	/** Source Capabilities */
34 	uint32_t src_caps[PDO_MAX_DATA_OBJECTS];
35 	/** Number of Source Capabilities */
36 	int src_cap_cnt;
37 	/* Power Supply Ready flag */
38 	atomic_t ps_ready;
39 } port0_data = {
40 	.snk_caps = {DT_FOREACH_PROP_ELEM(USBC_PORT0_NODE, sink_pdos, SINK_PDO)},
41 	.snk_cap_cnt = DT_PROP_LEN(USBC_PORT0_NODE, sink_pdos),
42 	.src_caps = {0},
43 	.src_cap_cnt = 0,
44 	.ps_ready = 0
45 };
46 
47 /* usbc.rst port data object end */
48 
49 /**
50  * @brief Builds a Request Data Object (RDO) with the following properties:
51  *		- Maximum operating current 100mA
52  *		- Operating current is 100mA
53  *		- Unchunked Extended Messages Not Supported
54  *		- No USB Suspend
55  *		- Not USB Communications Capable
56  *		- No capability mismatch
57  *		- Does not Giveback
58  *		- Select object position 1 (5V Power Data Object (PDO))
59  *
60  * @note Generally a sink application would build an RDO from the
61  *	 Source Capabilities stored in the dpm_data object
62  */
build_rdo(const struct port0_data_t * dpm_data)63 static uint32_t build_rdo(const struct port0_data_t *dpm_data)
64 {
65 	union pd_rdo rdo;
66 
67 	/* Maximum operating current 100mA (GIVEBACK = 0) */
68 	rdo.fixed.min_or_max_operating_current = PD_CONVERT_MA_TO_FIXED_PDO_CURRENT(100);
69 	/* Operating current 100mA */
70 	rdo.fixed.operating_current = PD_CONVERT_MA_TO_FIXED_PDO_CURRENT(100);
71 	/* Unchunked Extended Messages Not Supported */
72 	rdo.fixed.unchunked_ext_msg_supported = 0;
73 	/* No USB Suspend */
74 	rdo.fixed.no_usb_suspend = 1;
75 	/* Not USB Communications Capable */
76 	rdo.fixed.usb_comm_capable = 0;
77 	/* No capability mismatch */
78 	rdo.fixed.cap_mismatch = 0;
79 	/* Don't giveback */
80 	rdo.fixed.giveback = 0;
81 	/* Object position 1 (5V PDO) */
82 	rdo.fixed.object_pos = 1;
83 
84 	return rdo.raw_value;
85 }
86 
display_pdo(const int idx,const uint32_t pdo_value)87 static void display_pdo(const int idx,
88 			const uint32_t pdo_value)
89 {
90 	union pd_fixed_supply_pdo_source pdo;
91 
92 	/* Default to fixed supply pdo source until type is detected */
93 	pdo.raw_value = pdo_value;
94 
95 	LOG_INF("PDO %d:", idx);
96 	switch (pdo.type) {
97 	case PDO_FIXED: {
98 		LOG_INF("\tType:              FIXED");
99 		LOG_INF("\tCurrent:           %d",
100 		       PD_CONVERT_FIXED_PDO_CURRENT_TO_MA(pdo.max_current));
101 		LOG_INF("\tVoltage:           %d",
102 		       PD_CONVERT_FIXED_PDO_VOLTAGE_TO_MV(pdo.voltage));
103 		LOG_INF("\tPeak Current:      %d", pdo.peak_current);
104 		LOG_INF("\tUchunked Support:  %d",
105 		       pdo.unchunked_ext_msg_supported);
106 		LOG_INF("\tDual Role Data:    %d",
107 		       pdo.dual_role_data);
108 		LOG_INF("\tUSB Comms:         %d",
109 		       pdo.usb_comms_capable);
110 		LOG_INF("\tUnconstrained Pwr: %d",
111 		       pdo.unconstrained_power);
112 		LOG_INF("\tUSB Suspend:       %d",
113 		       pdo.usb_suspend_supported);
114 		LOG_INF("\tDual Role Power:   %d",
115 		       pdo.dual_role_power);
116 	}
117 	break;
118 	case PDO_BATTERY: {
119 		union pd_battery_supply_pdo_source batt_pdo;
120 
121 		batt_pdo.raw_value = pdo_value;
122 		LOG_INF("\tType:              BATTERY");
123 		LOG_INF("\tMin Voltage: %d",
124 			PD_CONVERT_BATTERY_PDO_VOLTAGE_TO_MV(batt_pdo.min_voltage));
125 		LOG_INF("\tMax Voltage: %d",
126 			PD_CONVERT_BATTERY_PDO_VOLTAGE_TO_MV(batt_pdo.max_voltage));
127 		LOG_INF("\tMax Power:   %d",
128 			PD_CONVERT_BATTERY_PDO_POWER_TO_MW(batt_pdo.max_power));
129 	}
130 	break;
131 	case PDO_VARIABLE: {
132 		union pd_variable_supply_pdo_source var_pdo;
133 
134 		var_pdo.raw_value = pdo_value;
135 		LOG_INF("\tType:        VARIABLE");
136 		LOG_INF("\tMin Voltage: %d",
137 			PD_CONVERT_VARIABLE_PDO_VOLTAGE_TO_MV(var_pdo.min_voltage));
138 		LOG_INF("\tMax Voltage: %d",
139 			PD_CONVERT_VARIABLE_PDO_VOLTAGE_TO_MV(var_pdo.max_voltage));
140 		LOG_INF("\tMax Current: %d",
141 			PD_CONVERT_VARIABLE_PDO_CURRENT_TO_MA(var_pdo.max_current));
142 	}
143 	break;
144 	case PDO_AUGMENTED: {
145 		union pd_augmented_supply_pdo_source aug_pdo;
146 
147 		aug_pdo.raw_value = pdo_value;
148 		LOG_INF("\tType:              AUGMENTED");
149 		LOG_INF("\tMin Voltage:       %d",
150 			PD_CONVERT_AUGMENTED_PDO_VOLTAGE_TO_MV(aug_pdo.min_voltage));
151 		LOG_INF("\tMax Voltage:       %d",
152 			PD_CONVERT_AUGMENTED_PDO_VOLTAGE_TO_MV(aug_pdo.max_voltage));
153 		LOG_INF("\tMax Current:       %d",
154 			PD_CONVERT_AUGMENTED_PDO_CURRENT_TO_MA(aug_pdo.max_current));
155 		LOG_INF("\tPPS Power Limited: %d", aug_pdo.pps_power_limited);
156 	}
157 	break;
158 	}
159 }
160 
display_source_caps(const struct device * dev)161 static void display_source_caps(const struct device *dev)
162 {
163 	struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
164 
165 	LOG_INF("Source Caps:");
166 	for (int i = 0; i < dpm_data->src_cap_cnt; i++) {
167 		display_pdo(i, dpm_data->src_caps[i]);
168 		k_msleep(50);
169 	}
170 }
171 
172 /* usbc.rst callbacks start */
port0_policy_cb_get_snk_cap(const struct device * dev,uint32_t ** pdos,int * num_pdos)173 static int port0_policy_cb_get_snk_cap(const struct device *dev,
174 					    uint32_t **pdos,
175 					    int *num_pdos)
176 {
177 	struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
178 
179 	*pdos = dpm_data->snk_caps;
180 	*num_pdos = dpm_data->snk_cap_cnt;
181 
182 	return 0;
183 }
184 
port0_policy_cb_set_src_cap(const struct device * dev,const uint32_t * pdos,const int num_pdos)185 static void port0_policy_cb_set_src_cap(const struct device *dev,
186 					     const uint32_t *pdos,
187 					     const int num_pdos)
188 {
189 	struct port0_data_t *dpm_data;
190 	int num;
191 	int i;
192 
193 	dpm_data = usbc_get_dpm_data(dev);
194 
195 	num = num_pdos;
196 	if (num > PDO_MAX_DATA_OBJECTS) {
197 		num = PDO_MAX_DATA_OBJECTS;
198 	}
199 
200 	for (i = 0; i < num; i++) {
201 		dpm_data->src_caps[i] = *(pdos + i);
202 	}
203 
204 	dpm_data->src_cap_cnt = num;
205 }
206 
port0_policy_cb_get_rdo(const struct device * dev)207 static uint32_t port0_policy_cb_get_rdo(const struct device *dev)
208 {
209 	struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
210 
211 	return build_rdo(dpm_data);
212 }
213 /* usbc.rst callbacks end */
214 
215 /* usbc.rst notify start */
port0_notify(const struct device * dev,const enum usbc_policy_notify_t policy_notify)216 static void port0_notify(const struct device *dev,
217 			      const enum usbc_policy_notify_t policy_notify)
218 {
219 	struct port0_data_t *dpm_data = usbc_get_dpm_data(dev);
220 
221 	switch (policy_notify) {
222 	case PROTOCOL_ERROR:
223 		break;
224 	case MSG_DISCARDED:
225 		break;
226 	case MSG_ACCEPT_RECEIVED:
227 		break;
228 	case MSG_REJECTED_RECEIVED:
229 		break;
230 	case MSG_NOT_SUPPORTED_RECEIVED:
231 		break;
232 	case TRANSITION_PS:
233 		atomic_set_bit(&dpm_data->ps_ready, 0);
234 		break;
235 	case PD_CONNECTED:
236 		break;
237 	case NOT_PD_CONNECTED:
238 		break;
239 	case POWER_CHANGE_0A0:
240 		LOG_INF("PWR 0A");
241 		break;
242 	case POWER_CHANGE_DEF:
243 		LOG_INF("PWR DEF");
244 		break;
245 	case POWER_CHANGE_1A5:
246 		LOG_INF("PWR 1A5");
247 		break;
248 	case POWER_CHANGE_3A0:
249 		LOG_INF("PWR 3A0");
250 		break;
251 	case DATA_ROLE_IS_UFP:
252 		break;
253 	case DATA_ROLE_IS_DFP:
254 		break;
255 	case PORT_PARTNER_NOT_RESPONSIVE:
256 		LOG_INF("Port Partner not PD Capable");
257 		break;
258 	case SNK_TRANSITION_TO_DEFAULT:
259 		break;
260 	case HARD_RESET_RECEIVED:
261 		break;
262 	case SENDER_RESPONSE_TIMEOUT:
263 		break;
264 	case SOURCE_CAPABILITIES_RECEIVED:
265 		break;
266 	}
267 }
268 /* usbc.rst notify end */
269 
270 /* usbc.rst check start */
port0_policy_check(const struct device * dev,const enum usbc_policy_check_t policy_check)271 bool port0_policy_check(const struct device *dev,
272 			const enum usbc_policy_check_t policy_check)
273 {
274 	switch (policy_check) {
275 	case CHECK_POWER_ROLE_SWAP:
276 		/* Reject power role swaps */
277 		return false;
278 	case CHECK_DATA_ROLE_SWAP_TO_DFP:
279 		/* Reject data role swap to DFP */
280 		return false;
281 	case CHECK_DATA_ROLE_SWAP_TO_UFP:
282 		/* Accept data role swap to UFP */
283 		return true;
284 	case CHECK_SNK_AT_DEFAULT_LEVEL:
285 		/* This device is always at the default power level */
286 		return true;
287 	default:
288 		/* Reject all other policy checks */
289 		return false;
290 
291 	}
292 }
293 /* usbc.rst check end */
294 
main(void)295 int main(void)
296 {
297 	const struct device *usbc_port0;
298 
299 	/* Get the device for this port */
300 	usbc_port0 = DEVICE_DT_GET(USBC_PORT0_NODE);
301 	if (!device_is_ready(usbc_port0)) {
302 		LOG_ERR("PORT0 device not ready");
303 		return 0;
304 	}
305 
306 	/* usbc.rst register start */
307 	/* Register USB-C Callbacks */
308 
309 	/* Register Policy Check callback */
310 	usbc_set_policy_cb_check(usbc_port0, port0_policy_check);
311 	/* Register Policy Notify callback */
312 	usbc_set_policy_cb_notify(usbc_port0, port0_notify);
313 	/* Register Policy Get Sink Capabilities callback */
314 	usbc_set_policy_cb_get_snk_cap(usbc_port0, port0_policy_cb_get_snk_cap);
315 	/* Register Policy Set Source Capabilities callback */
316 	usbc_set_policy_cb_set_src_cap(usbc_port0, port0_policy_cb_set_src_cap);
317 	/* Register Policy Get Request Data Object callback */
318 	usbc_set_policy_cb_get_rdo(usbc_port0, port0_policy_cb_get_rdo);
319 	/* usbc.rst register end */
320 
321 	/* usbc.rst user data start */
322 	/* Set Application port data object. This object is passed to the policy callbacks */
323 	port0_data.ps_ready = ATOMIC_INIT(0);
324 	usbc_set_dpm_data(usbc_port0, &port0_data);
325 	/* usbc.rst user data end */
326 
327 	/* usbc.rst usbc start */
328 	/* Start the USB-C Subsystem */
329 	usbc_start(usbc_port0);
330 	/* usbc.rst usbc end */
331 
332 	while (1) {
333 		/* Perform Application Specific functions */
334 		if (atomic_test_and_clear_bit(&port0_data.ps_ready, 0)) {
335 			/* Display the Source Capabilities */
336 			display_source_caps(usbc_port0);
337 		}
338 
339 		/* Arbitrary delay */
340 		k_msleep(1000);
341 	}
342 	return 0;
343 }
344