1 /*
2 * Copyright (c) 2022 The Chromium OS Authors
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/logging/log.h>
8 LOG_MODULE_DECLARE(usbc_stack, CONFIG_USBC_STACK_LOG_LEVEL);
9
10 #include "usbc_stack.h"
11 #include "usbc_tc_snk_states_internal.h"
12 #include "usbc_tc_common_internal.h"
13 #include <zephyr/drivers/usb_c/usbc_ppc.h>
14
15 /**
16 * @brief Sink power sub states. Only called if a PD contract is not in place
17 */
sink_power_sub_states(const struct device * dev)18 static void sink_power_sub_states(const struct device *dev)
19 {
20 struct usbc_port_data *data = dev->data;
21 enum tc_cc_voltage_state cc;
22 enum tc_cc_voltage_state new_cc_voltage;
23 enum usbc_policy_check_t dpm_pwr_change_notify;
24 struct tc_sm_t *tc = data->tc;
25
26 /* Get the active CC line */
27 cc = tc->cc_polarity ? tc->cc2 : tc->cc1;
28
29 if (cc == TC_CC_VOLT_RP_DEF) {
30 /*
31 * This sub-state supports Sinks consuming current within the
32 * lowest range (default) of Source-supplied current.
33 */
34 new_cc_voltage = TC_CC_VOLT_RP_DEF;
35 dpm_pwr_change_notify = POWER_CHANGE_DEF;
36 } else if (cc == TC_CC_VOLT_RP_1A5) {
37 /*
38 * This sub-state supports Sinks consuming current within the
39 * two lower ranges (default and 1.5 A) of Source-supplied
40 * current.
41 */
42 new_cc_voltage = TC_CC_VOLT_RP_1A5;
43 dpm_pwr_change_notify = POWER_CHANGE_1A5;
44 } else if (cc == TC_CC_VOLT_RP_3A0) {
45 /*
46 * This sub-state supports Sinks consuming current within all
47 * three ranges (default, 1.5 A and 3.0 A) of Source-supplied
48 * current.
49 */
50 new_cc_voltage = TC_CC_VOLT_RP_3A0;
51 dpm_pwr_change_notify = POWER_CHANGE_3A0;
52 } else {
53 /* Disconnect detected */
54 new_cc_voltage = TC_CC_VOLT_OPEN;
55 dpm_pwr_change_notify = POWER_CHANGE_0A0;
56 }
57
58 /* Debounce the Rp state */
59 if (new_cc_voltage != tc->cc_voltage) {
60 tc->cc_voltage = new_cc_voltage;
61 atomic_set_bit(&tc->flags, TC_FLAGS_RP_SUBSTATE_CHANGE);
62 usbc_timer_start(&tc->tc_t_rp_value_change);
63 }
64
65 /* Wait for Rp debounce */
66 if (usbc_timer_expired(&tc->tc_t_rp_value_change) == false) {
67 return;
68 }
69
70 /* Notify DPM of sink sub-state power change */
71 if (atomic_test_and_clear_bit(&tc->flags, TC_FLAGS_RP_SUBSTATE_CHANGE)) {
72 if (data->policy_cb_notify) {
73 data->policy_cb_notify(dev, dpm_pwr_change_notify);
74 }
75 }
76 }
77
78 /**
79 * @brief Unattached.SNK Entry
80 */
tc_unattached_snk_entry(void * obj)81 void tc_unattached_snk_entry(void *obj)
82 {
83 struct tc_sm_t *tc = (struct tc_sm_t *)obj;
84
85 LOG_INF("Unattached.SNK");
86
87 /*
88 * Allow the state machine to immediately check the state of CC lines and go into
89 * Attach.Wait state in case the Rp value is detected on the CC lines
90 */
91 usbc_bypass_next_sleep(tc->dev);
92 }
93
94 /**
95 * @brief Unattached.SNK Run
96 */
tc_unattached_snk_run(void * obj)97 enum smf_state_result tc_unattached_snk_run(void *obj)
98 {
99 struct tc_sm_t *tc = (struct tc_sm_t *)obj;
100 const struct device *dev = tc->dev;
101 struct usbc_port_data *data = dev->data;
102 const struct device *vbus = data->vbus;
103
104 /*
105 * Transition to AttachWait.SNK when the SNK.Rp state is present
106 * on at least one of its CC pins.
107 */
108 if (tcpc_is_cc_rp(tc->cc1) || tcpc_is_cc_rp(tc->cc2)) {
109 usbc_vbus_enable(vbus, true);
110 tc_set_state(dev, TC_ATTACH_WAIT_SNK_STATE);
111 }
112 return SMF_EVENT_PROPAGATE;
113 }
114
115 /**
116 * @brief AttachWait.SNK Entry
117 */
tc_attach_wait_snk_entry(void * obj)118 void tc_attach_wait_snk_entry(void *obj)
119 {
120 struct tc_sm_t *tc = (struct tc_sm_t *)obj;
121
122 LOG_INF("AttachWait.SNK");
123
124 tc->cc_state = TC_CC_NONE;
125
126 /*
127 * Allow the debounce timers to start immediately without additional delay added
128 * by going into sleep
129 */
130 usbc_bypass_next_sleep(tc->dev);
131 }
132
133 /**
134 * @brief AttachWait.SNK Run
135 */
tc_attach_wait_snk_run(void * obj)136 enum smf_state_result tc_attach_wait_snk_run(void *obj)
137 {
138 struct tc_sm_t *tc = (struct tc_sm_t *)obj;
139 const struct device *dev = tc->dev;
140 struct usbc_port_data *data = dev->data;
141 const struct device *vbus = data->vbus;
142 enum tc_cc_states new_cc_state;
143 bool vbus_present;
144
145 if (tcpc_is_cc_rp(tc->cc1) || tcpc_is_cc_rp(tc->cc2)) {
146 new_cc_state = TC_CC_DFP_ATTACHED;
147 } else {
148 new_cc_state = TC_CC_NONE;
149 }
150
151 /* Debounce the cc state */
152 if (new_cc_state != tc->cc_state) {
153 usbc_timer_start(&tc->tc_t_cc_debounce);
154 tc->cc_state = new_cc_state;
155 }
156
157 /* Wait for CC debounce */
158 if (usbc_timer_running(&tc->tc_t_cc_debounce) &&
159 usbc_timer_expired(&tc->tc_t_cc_debounce) == false) {
160 if (CONFIG_USBC_STATE_MACHINE_CYCLE_TIME >= TC_T_CC_DEBOUNCE_MIN_MS) {
161 /* Make sure the debounce time won't be longer than specified */
162 usbc_bypass_next_sleep(tc->dev);
163 }
164
165 return SMF_EVENT_PROPAGATE;
166 }
167
168 /* Transition to UnAttached.SNK if CC lines are open */
169 if (new_cc_state == TC_CC_NONE) {
170 tc_set_state(dev, TC_UNATTACHED_SNK_STATE);
171 }
172
173 /*
174 * The port shall transition to Attached.SNK after the state of only
175 * one of the CC1 or CC2 pins is SNK.Rp for at least tCCDebounce and
176 * VBUS is detected.
177 */
178 vbus_present = usbc_vbus_check_level(vbus, TC_VBUS_PRESENT);
179
180 if (vbus_present) {
181 tc_set_state(dev, TC_ATTACHED_SNK_STATE);
182 }
183
184 /*
185 * In case of no VBUS present, this call prevents going into the sleep and allows for
186 * faster VBUS detection. In case of VBUS present, allows for immediate execution of logic
187 * from new state.
188 */
189 usbc_bypass_next_sleep(tc->dev);
190 return SMF_EVENT_PROPAGATE;
191 }
192
tc_attach_wait_snk_exit(void * obj)193 void tc_attach_wait_snk_exit(void *obj)
194 {
195 struct tc_sm_t *tc = (struct tc_sm_t *)obj;
196
197 usbc_timer_stop(&tc->tc_t_cc_debounce);
198 }
199
200 /**
201 * @brief Attached.SNK Entry
202 */
tc_attached_snk_entry(void * obj)203 void tc_attached_snk_entry(void *obj)
204 {
205 struct tc_sm_t *tc = (struct tc_sm_t *)obj;
206 const struct device *dev = tc->dev;
207 struct usbc_port_data *data = dev->data;
208 const struct device *tcpc = data->tcpc;
209 int ret;
210
211 LOG_INF("Attached.SNK");
212
213 /* Clear cached CC voltage */
214 tc->cc_voltage = TC_CC_VOLT_OPEN;
215
216 /* Set CC polarity */
217 ret = tcpc_set_cc_polarity(tcpc, tc->cc_polarity);
218 if (ret != 0) {
219 LOG_ERR("Couldn't set CC polarity to %d: %d", tc->cc_polarity, ret);
220 tc_set_state(dev, TC_ERROR_RECOVERY_STATE);
221 return;
222 }
223
224 /* Enable PD */
225 tc_pd_enable(dev, true);
226
227 /* Enable sink path for the PPC */
228 if (data->ppc != NULL) {
229 ret = ppc_set_snk_ctrl(data->ppc, true);
230 if (ret != 0 && ret != -ENOTSUP) {
231 LOG_ERR("Couldn't enable PPC sink path: %d", ret);
232 }
233 }
234 }
235
236 /**
237 * @brief Attached.SNK and DebugAccessory.SNK Run
238 */
tc_attached_snk_run(void * obj)239 enum smf_state_result tc_attached_snk_run(void *obj)
240 {
241 struct tc_sm_t *tc = (struct tc_sm_t *)obj;
242 const struct device *dev = tc->dev;
243 struct usbc_port_data *data = dev->data;
244 const struct device *vbus = data->vbus;
245
246 /* Detach detection */
247 if (usbc_vbus_check_level(vbus, TC_VBUS_PRESENT) == false) {
248 usbc_vbus_enable(vbus, false);
249 tc_set_state(dev, TC_UNATTACHED_SNK_STATE);
250 return SMF_EVENT_PROPAGATE;
251 }
252
253 /* Run Sink Power Sub-State if not in an explicit contract */
254 if (pe_is_explicit_contract(dev) == false) {
255 sink_power_sub_states(dev);
256 }
257 return SMF_EVENT_PROPAGATE;
258 }
259
260 /**
261 * @brief Attached.SNK and DebugAccessory.SNK Exit
262 */
tc_attached_snk_exit(void * obj)263 void tc_attached_snk_exit(void *obj)
264 {
265 struct tc_sm_t *tc = (struct tc_sm_t *)obj;
266 const struct device *dev = tc->dev;
267 struct usbc_port_data *data = dev->data;
268 int ret;
269
270 /* Disable PD */
271 tc_pd_enable(dev, false);
272
273 /* Disable sink path for the PPC */
274 if (data->ppc != NULL) {
275 ret = ppc_set_snk_ctrl(data->ppc, false);
276 if (ret != 0 && ret != -ENOTSUP) {
277 LOG_ERR("Couldn't disable PPC sink path: %d", ret);
278 }
279 }
280 }
281
282 /**
283 * @brief Rd on CC lines Entry
284 */
tc_cc_rd_entry(void * obj)285 void tc_cc_rd_entry(void *obj)
286 {
287 struct tc_sm_t *tc = (struct tc_sm_t *)obj;
288 const struct device *dev = tc->dev;
289 struct usbc_port_data *data = dev->data;
290 const struct device *tcpc = data->tcpc;
291 int ret;
292
293 ret = tcpc_set_cc(tcpc, TC_CC_RD);
294 if (ret != 0) {
295 LOG_ERR("Couldn't set CC lines to Rd: %d", ret);
296 tc_set_state(dev, TC_ERROR_RECOVERY_STATE);
297 }
298 }
299