/* Copyright 2019 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "charge_manager.h" #include "charge_state.h" #include "common.h" #include "console.h" #include "hooks.h" #include "system.h" #include "task.h" #include "tcpm/tcpm.h" #include "usb_common.h" #include "usb_mux.h" #include "usb_pd.h" #include "usb_pd_dpm.h" #include "usb_pd_tcpm.h" #include "usb_pd_timer.h" #include "usb_pe_sm.h" #include "usb_prl_sm.h" #include "usb_sm.h" #include "usb_tc_sm.h" #include "usbc_ocp.h" #include "usbc_ppc.h" #include "vboot.h" /* * USB Type-C DRP with Accessory and Try.SRC module * See Figure 4-16 in Release 1.4 of USB Type-C Spec. */ #ifdef CONFIG_COMMON_RUNTIME #define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) #define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) #else /* CONFIG_COMMON_RUNTIME */ #define CPRINTF(format, args...) #define CPRINTS(format, args...) #endif #define CPRINTF_LX(x, format, args...) \ do { \ if (tc_debug_level >= x) \ CPRINTF(format, ## args); \ } while (0) #define CPRINTF_L1(format, args...) CPRINTF_LX(1, format, ## args) #define CPRINTF_L2(format, args...) CPRINTF_LX(2, format, ## args) #define CPRINTF_L3(format, args...) CPRINTF_LX(3, format, ## args) #define CPRINTS_LX(x, format, args...) \ do { \ if (tc_debug_level >= x) \ CPRINTS(format, ## args); \ } while (0) #define CPRINTS_L1(format, args...) CPRINTS_LX(1, format, ## args) #define CPRINTS_L2(format, args...) CPRINTS_LX(2, format, ## args) #define CPRINTS_L3(format, args...) CPRINTS_LX(3, format, ## args) /* * Define DEBUG_PRINT_FLAG_AND_EVENT_NAMES to print flag names when set and * cleared, and event names when handled by tc_event_check(). */ #undef DEBUG_PRINT_FLAG_AND_EVENT_NAMES #ifdef DEBUG_PRINT_FLAG_AND_EVENT_NAMES void print_flag(int port, int set_or_clear, int flag); #define TC_SET_FLAG(port, flag) \ do { \ print_flag(port, 1, flag); \ atomic_or(&tc[port].flags, (flag)); \ } while (0) #define TC_CLR_FLAG(port, flag) \ do { \ print_flag(port, 0, flag); \ atomic_clear_bits(&tc[port].flags, (flag)); \ } while (0) #else #define TC_SET_FLAG(port, flag) atomic_or(&tc[port].flags, (flag)) #define TC_CLR_FLAG(port, flag) atomic_clear_bits(&tc[port].flags, (flag)) #endif #define TC_CHK_FLAG(port, flag) (tc[port].flags & (flag)) /* Type-C Layer Flags */ /* Flag to note we are sourcing VCONN */ #define TC_FLAGS_VCONN_ON BIT(0) /* Flag to note port partner has Rp/Rp or Rd/Rd */ #define TC_FLAGS_TS_DTS_PARTNER BIT(1) /* Flag to note VBus input has never been low */ #define TC_FLAGS_VBUS_NEVER_LOW BIT(2) /* Flag to note Low Power Mode transition is currently happening */ #define TC_FLAGS_LPM_TRANSITION BIT(3) /* Flag to note Low Power Mode is currently on */ #define TC_FLAGS_LPM_ENGAGED BIT(4) /* Flag to note CVTPD has been detected */ #define TC_FLAGS_CTVPD_DETECTED BIT(5) /* Flag to note request to swap to VCONN on */ #define TC_FLAGS_REQUEST_VC_SWAP_ON BIT(6) /* Flag to note request to swap to VCONN off */ #define TC_FLAGS_REQUEST_VC_SWAP_OFF BIT(7) /* Flag to note request to swap VCONN is being rejected */ #define TC_FLAGS_REJECT_VCONN_SWAP BIT(8) /* Flag to note request to power role swap */ #define TC_FLAGS_REQUEST_PR_SWAP BIT(9) /* Flag to note request to data role swap */ #define TC_FLAGS_REQUEST_DR_SWAP BIT(10) /* Flag to note request to power off sink */ #define TC_FLAGS_POWER_OFF_SNK BIT(11) /* Flag to note port partner is Power Delivery capable */ #define TC_FLAGS_PARTNER_PD_CAPABLE BIT(12) /* Flag to note hard reset has been requested */ #define TC_FLAGS_HARD_RESET_REQUESTED BIT(13) /* Flag to note we are currently performing PR Swap */ #define TC_FLAGS_PR_SWAP_IN_PROGRESS BIT(14) /* Flag to note we should check for connection */ #define TC_FLAGS_CHECK_CONNECTION BIT(15) /* Flag to note request from pd_set_suspend to enter TC_DISABLED state */ #define TC_FLAGS_REQUEST_SUSPEND BIT(16) /* Flag to note we are in TC_DISABLED state */ #define TC_FLAGS_SUSPENDED BIT(17) /* Flag to indicate the port current limit has changed */ #define TC_FLAGS_UPDATE_CURRENT BIT(18) /* Flag to indicate USB mux should be updated */ #define TC_FLAGS_UPDATE_USB_MUX BIT(19) /* Flag for retimer firmware update */ #define TC_FLAGS_USB_RETIMER_FW_UPDATE_RUN BIT(20) #define TC_FLAGS_USB_RETIMER_FW_UPDATE_LTD_RUN BIT(21) /* Flag for asynchronous call to request Error Recovery */ #define TC_FLAGS_REQUEST_ERROR_RECOVERY BIT(22) /* For checking flag_bit_names[] array */ #define TC_FLAGS_COUNT 23 /* On disconnect, clear most of the flags. */ #define CLR_FLAGS_ON_DISCONNECT(port) TC_CLR_FLAG(port, \ ~(TC_FLAGS_LPM_ENGAGED | TC_FLAGS_REQUEST_SUSPEND | TC_FLAGS_SUSPENDED)) /* * 10 ms is enough time for any TCPC transaction to complete * * This value must be below ~39.7 ms to put ANX7447 into LPM due to bug in * silicon (see b/77544959 and b/149761477 for more details). */ #define PD_LPM_DEBOUNCE_US (10 * MSEC) /* * This delay is not part of the USB Type-C specification or the USB port * controller specification. Some TCPCs require extra time before the CC_STATUS * register is updated when exiting low power mode. * * This delay can be possibly shortened or removed by checking VBUS state * before trying to re-enter LPM. * * TODO(b/162347811): TCPMv2: Wait for debounce on Vbus and CC lines */ #define PD_LPM_EXIT_DEBOUNCE_US CONFIG_USB_PD_TCPC_LPM_EXIT_DEBOUNCE /* * The TypeC state machine uses this bit to disable/enable PD * This bit corresponds to bit-0 of pd_disabled_mask */ #define PD_DISABLED_NO_CONNECTION BIT(0) /* * Console and Host commands use this bit to override the * PD_DISABLED_NO_CONNECTION bit that was set by the TypeC * state machine. * This bit corresponds to bit-1 of pd_disabled_mask */ #define PD_DISABLED_BY_POLICY BIT(1) /* Unreachable time in future */ #define TIMER_DISABLED 0xffffffffffffffff enum ps_reset_sequence { PS_STATE0, PS_STATE1, PS_STATE2, }; /* List of all TypeC-level states */ enum usb_tc_state { /* Super States */ TC_CC_OPEN, TC_CC_RD, TC_CC_RP, /* Normal States */ TC_DISABLED, TC_ERROR_RECOVERY, TC_UNATTACHED_SNK, TC_ATTACH_WAIT_SNK, TC_ATTACHED_SNK, TC_UNATTACHED_SRC, TC_ATTACH_WAIT_SRC, TC_ATTACHED_SRC, TC_TRY_SRC, TC_TRY_WAIT_SNK, TC_DRP_AUTO_TOGGLE, TC_LOW_POWER_MODE, TC_CT_UNATTACHED_SNK, TC_CT_ATTACHED_SNK, TC_STATE_COUNT, }; /* Forward declare the full list of states. This is indexed by usb_tc_state */ static const struct usb_state tc_states[]; /* * Remove all of the states that aren't support at link time. This allows * IS_ENABLED to work. */ #ifndef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE GEN_NOT_SUPPORTED(TC_DRP_AUTO_TOGGLE); #define TC_DRP_AUTO_TOGGLE TC_DRP_AUTO_TOGGLE_NOT_SUPPORTED #endif /* CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE */ #ifndef CONFIG_USB_PD_TCPC_LOW_POWER GEN_NOT_SUPPORTED(TC_LOW_POWER_MODE); #define TC_LOW_POWER_MODE TC_LOW_POWER_MODE_NOT_SUPPORTED #endif /* CONFIG_USB_PD_TCPC_LOW_POWER */ #ifndef CONFIG_USB_PE_SM GEN_NOT_SUPPORTED(TC_CT_UNATTACHED_SNK); #define TC_CT_UNATTACHED_SNK TC_CT_UNATTACHED_SNK_NOT_SUPPORTED GEN_NOT_SUPPORTED(TC_CT_ATTACHED_SNK); #define TC_CT_ATTACHED_SNK TC_CT_ATTACHED_SNK_NOT_SUPPORTED #endif /* CONFIG_USB_PE_SM */ /* * If CONFIG_ASSERT_CCD_MODE_ON_DTS_CONNECT is not defined then * _GPIO_CCD_MODE_ODL is not needed. Declare as extern so IS_ENABLED will work. */ #ifndef CONFIG_ASSERT_CCD_MODE_ON_DTS_CONNECT extern int _GPIO_CCD_MODE_ODL; #else #define _GPIO_CCD_MODE_ODL GPIO_CCD_MODE_ODL #endif /* CONFIG_ASSERT_CCD_MODE_ON_DTS_CONNECT */ /* * We will use DEBUG LABELS if we will be able to print (COMMON RUNTIME) * and either CONFIG_USB_PD_DEBUG_LEVEL is not defined (no override) or * we are overriding and the level is not DISABLED. * * If we can't print or the CONFIG_USB_PD_DEBUG_LEVEL is defined to be 0 * then the DEBUG LABELS will be removed from the build. */ #if defined(CONFIG_COMMON_RUNTIME) && \ (!defined(CONFIG_USB_PD_DEBUG_LEVEL) || \ (CONFIG_USB_PD_DEBUG_LEVEL > 0)) #define USB_PD_DEBUG_LABELS #endif /* * Helper Macro to determine if the machine is in state * TC_ATTACHED_SRC */ #define IS_ATTACHED_SRC(port) (get_state_tc(port) == TC_ATTACHED_SRC) /* * Helper Macro to determine if the machine is in state * TC_ATTACHED_SNK */ #define IS_ATTACHED_SNK(port) (get_state_tc(port) == TC_ATTACHED_SNK) /* List of human readable state names for console debugging */ __maybe_unused static __const_data const char * const tc_state_names[] = { #ifdef USB_PD_DEBUG_LABELS [TC_DISABLED] = "Disabled", [TC_ERROR_RECOVERY] = "ErrorRecovery", [TC_UNATTACHED_SNK] = "Unattached.SNK", [TC_ATTACH_WAIT_SNK] = "AttachWait.SNK", [TC_ATTACHED_SNK] = "Attached.SNK", [TC_UNATTACHED_SRC] = "Unattached.SRC", [TC_ATTACH_WAIT_SRC] = "AttachWait.SRC", [TC_ATTACHED_SRC] = "Attached.SRC", [TC_TRY_SRC] = "Try.SRC", [TC_TRY_WAIT_SNK] = "TryWait.SNK", #ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE [TC_DRP_AUTO_TOGGLE] = "DRPAutoToggle", #endif #ifdef CONFIG_USB_PD_TCPC_LOW_POWER [TC_LOW_POWER_MODE] = "LowPowerMode", #endif #ifdef CONFIG_USB_PE_SM [TC_CT_UNATTACHED_SNK] = "CTUnattached.SNK", [TC_CT_ATTACHED_SNK] = "CTAttached.SNK", #endif /* Super States */ [TC_CC_OPEN] = "SS:CC_OPEN", [TC_CC_RD] = "SS:CC_RD", [TC_CC_RP] = "SS:CC_RP", [TC_STATE_COUNT] = "", #endif }; /* Debug log level - higher number == more log */ #ifdef CONFIG_USB_PD_DEBUG_LEVEL static const enum debug_level tc_debug_level = CONFIG_USB_PD_DEBUG_LEVEL; #else static enum debug_level tc_debug_level = DEBUG_LEVEL_1; #endif #ifdef DEBUG_PRINT_FLAG_AND_EVENT_NAMES struct bit_name { int value; const char *name; }; static struct bit_name flag_bit_names[] = { { TC_FLAGS_VCONN_ON, "VCONN_ON" }, { TC_FLAGS_TS_DTS_PARTNER, "TS_DTS_PARTNER" }, { TC_FLAGS_VBUS_NEVER_LOW, "VBUS_NEVER_LOW" }, { TC_FLAGS_LPM_TRANSITION, "LPM_TRANSITION" }, { TC_FLAGS_LPM_ENGAGED, "LPM_ENGAGED" }, { TC_FLAGS_CTVPD_DETECTED, "CTVPD_DETECTED" }, { TC_FLAGS_REQUEST_VC_SWAP_ON, "REQUEST_VC_SWAP_ON" }, { TC_FLAGS_REQUEST_VC_SWAP_OFF, "REQUEST_VC_SWAP_OFF" }, { TC_FLAGS_REJECT_VCONN_SWAP, "REJECT_VCONN_SWAP" }, { TC_FLAGS_REQUEST_PR_SWAP, "REQUEST_PR_SWAP" }, { TC_FLAGS_REQUEST_DR_SWAP, "REQUEST_DR_SWAP" }, { TC_FLAGS_POWER_OFF_SNK, "POWER_OFF_SNK" }, { TC_FLAGS_PARTNER_PD_CAPABLE, "PARTNER_PD_CAPABLE" }, { TC_FLAGS_HARD_RESET_REQUESTED, "HARD_RESET_REQUESTED" }, { TC_FLAGS_PR_SWAP_IN_PROGRESS, "PR_SWAP_IN_PROGRESS" }, { TC_FLAGS_CHECK_CONNECTION, "CHECK_CONNECTION" }, { TC_FLAGS_REQUEST_SUSPEND, "REQUEST_SUSPEND" }, { TC_FLAGS_SUSPENDED, "SUSPENDED" }, { TC_FLAGS_UPDATE_CURRENT, "UPDATE_CURRENT" }, { TC_FLAGS_UPDATE_USB_MUX, "UPDATE_USB_MUX" }, { TC_FLAGS_USB_RETIMER_FW_UPDATE_RUN, "USB_RETIMER_FW_UPDATE_RUN" }, { TC_FLAGS_USB_RETIMER_FW_UPDATE_LTD_RUN, "USB_RETIMER_FW_UPDATE_LTD_RUN" }, { TC_FLAGS_REQUEST_ERROR_RECOVERY, "REQUEST_ERROR_RECOCVERY"}, }; BUILD_ASSERT(ARRAY_SIZE(flag_bit_names) == TC_FLAGS_COUNT); static struct bit_name event_bit_names[] = { { TASK_EVENT_SYSJUMP_READY, "SYSJUMP_READY" }, { TASK_EVENT_IPC_READY, "IPC_READY" }, { TASK_EVENT_PD_AWAKE, "PD_AWAKE" }, { TASK_EVENT_PECI_DONE, "PECI_DONE" }, { TASK_EVENT_I2C_IDLE, "I2C_IDLE" }, #ifdef TASK_EVENT_PS2_DONE { TASK_EVENT_PS2_DONE, "PS2_DONE" }, #endif { TASK_EVENT_DMA_TC, "DMA_TC" }, { TASK_EVENT_ADC_DONE, "ADC_DONE" }, { TASK_EVENT_RESET_DONE, "RESET_DONE" }, { TASK_EVENT_WAKE, "WAKE" }, { TASK_EVENT_MUTEX, "MUTEX" }, { TASK_EVENT_TIMER, "TIMER" }, { PD_EVENT_TX, "TX" }, { PD_EVENT_CC, "CC" }, { PD_EVENT_TCPC_RESET, "TCPC_RESET" }, { PD_EVENT_UPDATE_DUAL_ROLE, "UPDATE_DUAL_ROLE" }, { PD_EVENT_DEVICE_ACCESSED, "DEVICE_ACCESSED" }, { PD_EVENT_POWER_STATE_CHANGE, "POWER_STATE_CHANGE" }, { PD_EVENT_SEND_HARD_RESET, "SEND_HARD_RESET" }, { PD_EVENT_SYSJUMP, "SYSJUMP" }, }; static void print_bits(int port, const char *desc, int value, struct bit_name *names, int names_size) { int i; CPRINTF("C%d: %s 0x%x : ", port, desc, value); for (i = 0; i < names_size; i++) { if (value & names[i].value) CPRINTF("%s | ", names[i].name); value &= ~names[i].value; } if (value != 0) CPRINTF("0x%x", value); CPRINTF("\n"); } void print_flag(int port, int set_or_clear, int flag) { print_bits(port, set_or_clear ? "Set" : "Clr", flag, flag_bit_names, ARRAY_SIZE(flag_bit_names)); } #endif /* DEBUG_PRINT_FLAG_AND_EVENT_NAMES */ #ifndef CONFIG_USB_PD_TRY_SRC extern int TC_TRY_SRC_UNDEFINED; extern int TC_TRY_WAIT_SNK_UNDEFINED; #define TC_TRY_SRC TC_TRY_SRC_UNDEFINED #define TC_TRY_WAIT_SNK TC_TRY_WAIT_SNK_UNDEFINED #endif static struct type_c { /* state machine context */ struct sm_ctx ctx; /* current port power role (SOURCE or SINK) */ enum pd_power_role power_role; /* current port data role (DFP or UFP) */ enum pd_data_role data_role; /* * Higher-level power deliver state machines are enabled if false, * else they're disabled if bits PD_DISABLED_NO_CONNECTION or * PD_DISABLED_BY_POLICY are set. */ uint32_t pd_disabled_mask; /* * Timer for handling TOGGLE_OFF/FORCE_SINK mode when auto-toggle * enabled. See drp_auto_toggle_next_state() for details. */ uint64_t drp_sink_time; #ifdef CONFIG_USB_PE_SM /* Power supply reset sequence during a hard reset */ enum ps_reset_sequence ps_reset_state; #endif /* Port polarity */ enum tcpc_cc_polarity polarity; /* port flags, see TC_FLAGS_* */ uint32_t flags; /* The cc state */ enum pd_cc_states cc_state; /* Tasks to notify after TCPC has been reset */ int tasks_waiting_on_reset; /* Tasks preventing TCPC from entering low power mode */ int tasks_preventing_lpm; /* Voltage on CC pin */ enum tcpc_cc_voltage_status cc_voltage; /* Type-C current */ typec_current_t typec_curr; /* Type-C current change */ typec_current_t typec_curr_change; /* Selected TCPC CC/Rp values */ enum tcpc_cc_pull select_cc_pull; enum tcpc_rp_value select_current_limit_rp; enum tcpc_rp_value select_collision_rp; } tc[CONFIG_USB_PD_PORT_MAX_COUNT]; /* Port dual-role state */ static volatile __maybe_unused enum pd_dual_role_states drp_state[CONFIG_USB_PD_PORT_MAX_COUNT] = { [0 ... (CONFIG_USB_PD_PORT_MAX_COUNT - 1)] = CONFIG_USB_PD_INITIAL_DRP_STATE}; static void set_vconn(int port, int enable); /* Forward declare common, private functions */ static __maybe_unused int reset_device_and_notify(int port); static __maybe_unused void check_drp_connection(const int port); static void sink_power_sub_states(int port); static void set_ccd_mode(int port, bool enable); __maybe_unused static void handle_new_power_state(int port); static void pd_update_dual_role_config(int port); /* Forward declare common, private functions */ static void set_state_tc(const int port, const enum usb_tc_state new_state); test_export_static enum usb_tc_state get_state_tc(const int port); /* Enable variable for Try.SRC states */ static uint32_t pd_try_src; static volatile enum try_src_override_t pd_try_src_override; static void pd_update_try_source(void); static void sink_stop_drawing_current(int port); __maybe_unused static bool is_try_src_enabled(int port) { if (!IS_ENABLED(CONFIG_USB_PD_TRY_SRC)) assert(0); return ((pd_try_src_override == TRY_SRC_OVERRIDE_ON) || (pd_try_src_override == TRY_SRC_NO_OVERRIDE && pd_try_src)); } /* * Public Functions * * NOTE: Functions prefixed with pd_ are defined in usb_pd.h * Functions prefixed with tc_ are defined int usb_tc_sm.h */ #ifndef CONFIG_USB_PRL_SM /* * These pd_ functions are implemented in common/usb_prl_sm.c */ void pd_transmit_complete(int port, int status) { /* DO NOTHING */ } void pd_execute_hard_reset(int port) { /* DO NOTHING */ } __overridable void pd_set_vbus_discharge(int port, int enable) { /* DO NOTHING */ } #endif /* !CONFIG_USB_PRL_SM */ #ifndef CONFIG_USB_PE_SM /* * These pd_ functions are implemented in the PE layer */ const uint32_t * const pd_get_src_caps(int port) { return NULL; } uint8_t pd_get_src_cap_cnt(int port) { return 0; } const uint32_t * const pd_get_snk_caps(int port) { return NULL; } uint8_t pd_get_snk_cap_cnt(int port) { return 0; } void pd_set_src_caps(int port, int cnt, uint32_t *src_caps) { } int pd_get_rev(int port, enum tcpci_msg_type type) { return PD_REV30; } #endif /* !CONFIG_USB_PR_SM */ #ifndef HAS_TASK_CHIPSET __overridable enum pd_dual_role_states board_tc_get_initial_drp_mode(int port) { /* * DRP state is typically adjusted as the chipset state is changed. For * projects which don't include an AP this function can be used for to * specify what the starting DRP state should be. */ return PD_DRP_FORCE_SINK; } #endif void pd_update_contract(int port) { if (IS_ENABLED(CONFIG_USB_PE_SM)) { if (IS_ATTACHED_SRC(port)) pd_dpm_request(port, DPM_REQUEST_SRC_CAP_CHANGE); } } void pd_request_source_voltage(int port, int mv) { if (IS_ENABLED(CONFIG_USB_PE_SM)) { pd_set_max_voltage(mv); if (IS_ATTACHED_SNK(port)) pd_dpm_request(port, DPM_REQUEST_NEW_POWER_LEVEL); else pd_dpm_request(port, DPM_REQUEST_PR_SWAP); task_wake(PD_PORT_TO_TASK_ID(port)); } } void pd_set_external_voltage_limit(int port, int mv) { if (IS_ENABLED(CONFIG_USB_PE_SM)) { pd_set_max_voltage(mv); /* Must be in Attached.SNK when this function is called */ if (get_state_tc(port) == TC_ATTACHED_SNK) pd_dpm_request(port, DPM_REQUEST_NEW_POWER_LEVEL); task_wake(PD_PORT_TO_TASK_ID(port)); } } void pd_set_new_power_request(int port) { if (IS_ENABLED(CONFIG_USB_PE_SM)) { /* Must be in Attached.SNK when this function is called */ if (get_state_tc(port) == TC_ATTACHED_SNK) pd_dpm_request(port, DPM_REQUEST_NEW_POWER_LEVEL); } } void tc_request_power_swap(int port) { if (IS_ENABLED(CONFIG_USB_PE_SM)) { /* * Must be in Attached.SRC or Attached.SNK */ if (IS_ATTACHED_SRC(port) || IS_ATTACHED_SNK(port)) { TC_SET_FLAG(port, TC_FLAGS_PR_SWAP_IN_PROGRESS); /* Let tc_pr_swap_complete start the Vbus debounce */ pd_timer_disable(port, TC_TIMER_VBUS_DEBOUNCE); } /* * TCPCI Rev2 V1.1 4.4.5.4.4 * Disconnect Detection by the Sink TCPC during a Connection * * Upon reception of or prior to transmitting a PR_Swap * message, the TCPM acting as a Sink shall disable the Sink * disconnect detection to retain PD message delivery when * Power Role Swap happens. Disable AutoDischargeDisconnect. */ if (IS_ATTACHED_SNK(port)) tcpm_enable_auto_discharge_disconnect(port, 0); } } /* Flag to indicate PD comm is disabled on init */ static int pd_disabled_on_init; static void pd_update_pd_comm(void) { int i; /* * Some batteries take much longer time to report its SOC. * The init function disabled PD comm on startup. Need this * hook to enable PD comm when the battery level is enough. */ if (pd_disabled_on_init && pd_is_battery_capable()) { for (i = 0; i < CONFIG_USB_PD_PORT_MAX_COUNT; i++) pd_comm_enable(i, 1); pd_disabled_on_init = 0; } } DECLARE_HOOK(HOOK_BATTERY_SOC_CHANGE, pd_update_pd_comm, HOOK_PRIO_DEFAULT); static bool pd_comm_allowed_by_policy(void) { if (system_is_in_rw()) return true; if (vboot_allow_usb_pd()) return true; /* * If enable PD in RO on a non-EFS2 device, a hard reset will be issued * when sysjump to RW that makes the device brownout on the dead-battery * case. Disable PD for this special case as a workaround. */ if (!system_is_locked()) { if (IS_ENABLED(CONFIG_VBOOT_EFS2)) return true; if (pd_is_battery_capable()) return true; pd_disabled_on_init = 1; } return false; } static void tc_policy_pd_enable(int port, int en) { if (en) atomic_clear_bits(&tc[port].pd_disabled_mask, PD_DISABLED_BY_POLICY); else atomic_or(&tc[port].pd_disabled_mask, PD_DISABLED_BY_POLICY); CPRINTS("C%d: PD comm policy %sabled", port, en ? "en" : "dis"); } static void tc_enable_pd(int port, int en) { if (en) atomic_clear_bits(&tc[port].pd_disabled_mask, PD_DISABLED_NO_CONNECTION); else atomic_or(&tc[port].pd_disabled_mask, PD_DISABLED_NO_CONNECTION); } __maybe_unused static void tc_enable_try_src(int en) { if (!IS_ENABLED(CONFIG_USB_PD_TRY_SRC)) assert(0); if (en) atomic_or(&pd_try_src, 1); else atomic_clear_bits(&pd_try_src, 1); } /* * Exit all modes due to a detach event * Note: this skips the ExitMode VDM steps in the PE because it is assumed the * partner is not present to receive them, and the PE will no longer be running. */ static void tc_set_modes_exit(int port) { if (IS_ENABLED(CONFIG_USB_PE_SM) && IS_ENABLED(CONFIG_USB_PD_ALT_MODE_DFP)) { pd_dfp_exit_mode(port, TCPCI_MSG_SOP, 0, 0); pd_dfp_exit_mode(port, TCPCI_MSG_SOP_PRIME, 0, 0); pd_dfp_exit_mode(port, TCPCI_MSG_SOP_PRIME_PRIME, 0, 0); } } static void tc_detached(int port) { TC_CLR_FLAG(port, TC_FLAGS_TS_DTS_PARTNER); hook_notify(HOOK_USB_PD_DISCONNECT); tc_pd_connection(port, 0); tcpm_debug_accessory(port, 0); set_ccd_mode(port, 0); tc_set_modes_exit(port); if (IS_ENABLED(CONFIG_USB_PRL_SM)) prl_set_default_pd_revision(port); /* Clear any mux connection on detach */ if (IS_ENABLED(CONFIG_USBC_SS_MUX)) usb_mux_set(port, USB_PD_MUX_NONE, USB_SWITCH_DISCONNECT, tc[port].polarity); } static inline void pd_set_dual_role_and_event(int port, enum pd_dual_role_states state, uint32_t event) { drp_state[port] = state; if (IS_ENABLED(CONFIG_USB_PD_TRY_SRC)) pd_update_try_source(); if (event != 0) task_set_event(PD_PORT_TO_TASK_ID(port), event); } void pd_set_dual_role(int port, enum pd_dual_role_states state) { pd_set_dual_role_and_event(port, state, PD_EVENT_UPDATE_DUAL_ROLE); } int pd_comm_is_enabled(int port) { return tc_get_pd_enabled(port); } void pd_request_data_swap(int port) { /* * Must be in Attached.SRC, Attached.SNK, DebugAccessory.SNK, * or UnorientedDebugAccessory.SRC when this function * is called */ if (IS_ATTACHED_SRC(port) || IS_ATTACHED_SNK(port)) { TC_SET_FLAG(port, TC_FLAGS_REQUEST_DR_SWAP); task_wake(PD_PORT_TO_TASK_ID(port)); } } /* Return true if partner port is known to be PD capable. */ bool pd_capable(int port) { return !!TC_CHK_FLAG(port, TC_FLAGS_PARTNER_PD_CAPABLE); } /* * Return true if we transition through Unattached.SNK, but we're still waiting * to receive source caps from the partner. This indicates that the PD * capabilities are not yet known. */ bool pd_waiting_on_partner_src_caps(int port) { return !pd_get_src_cap_cnt(port); } enum pd_dual_role_states pd_get_dual_role(int port) { return drp_state[port]; } #ifdef CONFIG_CMD_PD_DEV_DUMP_INFO static inline void pd_dev_dump_info(uint16_t dev_id, uint32_t *hash) { int j; ccprintf("DevId:%d.%d Hash:", HW_DEV_ID_MAJ(dev_id), HW_DEV_ID_MIN(dev_id)); for (j = 0; j < PD_RW_HASH_SIZE / 4; j++) ccprintf(" %08x ", hash[j]); ccprintf("\n"); } #endif /* CONFIG_CMD_PD_DEV_DUMP_INFO */ const char *tc_get_current_state(int port) { if (IS_ENABLED(USB_PD_DEBUG_LABELS)) return tc_state_names[get_state_tc(port)]; else return ""; } uint32_t tc_get_flags(int port) { return tc[port].flags; } int tc_is_attached_src(int port) { return IS_ATTACHED_SRC(port); } int tc_is_attached_snk(int port) { return IS_ATTACHED_SNK(port); } void tc_pd_connection(int port, int en) { if (en) { bool new_pd_capable = false; if (!TC_CHK_FLAG(port, TC_FLAGS_PARTNER_PD_CAPABLE)) new_pd_capable = true; TC_SET_FLAG(port, TC_FLAGS_PARTNER_PD_CAPABLE); /* If a PD device is attached then disable deep sleep */ if (IS_ENABLED(CONFIG_LOW_POWER_IDLE) && !IS_ENABLED(CONFIG_USB_PD_TCPC_ON_CHIP)) { disable_sleep(SLEEP_MASK_USB_PD); } /* * Update the mux state, only when the PD capable flag * transitions from 0 to 1. This ensures that PD charger * devices, without data capability are not marked as having * USB. */ if (new_pd_capable) set_usb_mux_with_current_data_role(port); } else { TC_CLR_FLAG(port, TC_FLAGS_PARTNER_PD_CAPABLE); /* If a PD device isn't attached then enable deep sleep */ if (IS_ENABLED(CONFIG_LOW_POWER_IDLE) && !IS_ENABLED(CONFIG_USB_PD_TCPC_ON_CHIP)) { int i; /* If all ports are not connected, allow the sleep */ for (i = 0; i < board_get_usb_pd_port_count(); i++) { if (pd_capable(i)) break; } if (i == board_get_usb_pd_port_count()) enable_sleep(SLEEP_MASK_USB_PD); } } } void tc_ctvpd_detected(int port) { TC_SET_FLAG(port, TC_FLAGS_CTVPD_DETECTED); } void pd_try_vconn_src(int port) { set_vconn(port, 1); } int tc_check_vconn_swap(int port) { if (IS_ENABLED(CONFIG_USBC_VCONN)) { if (TC_CHK_FLAG(port, TC_FLAGS_REJECT_VCONN_SWAP)) return 0; return pd_check_vconn_swap(port); } else return 0; } void tc_pr_swap_complete(int port, bool success) { if (IS_ATTACHED_SNK(port)) { /* * Give the ADCs in the TCPC or PPC time to react following * a PS_RDY message received during a SRC to SNK swap. * Note: This is empirically determined, not strictly * part of the USB PD spec. * Note: Swap in progress should not be cleared until the * debounce is completed. */ pd_timer_enable(port, TC_TIMER_VBUS_DEBOUNCE, PD_T_DEBOUNCE); } else { /* PR Swap is no longer in progress */ TC_CLR_FLAG(port, TC_FLAGS_PR_SWAP_IN_PROGRESS); /* * AutoDischargeDisconnect was turned off near the SNK->SRC * PR-Swap message. If the swap was a success, Vbus should be * valid, so re-enable AutoDischargeDisconnect */ if (success) tcpm_enable_auto_discharge_disconnect(port, 1); } } void tc_prs_src_snk_assert_rd(int port) { /* * Must be in Attached.SRC or UnorientedDebugAccessory.SRC * when this function is called */ if (IS_ATTACHED_SRC(port)) { /* * Transition to Attached.SNK to * DebugAccessory.SNK assert Rd */ TC_SET_FLAG(port, TC_FLAGS_REQUEST_PR_SWAP); task_wake(PD_PORT_TO_TASK_ID(port)); } } void tc_prs_snk_src_assert_rp(int port) { /* * Must be in Attached.SNK or DebugAccessory.SNK * when this function is called */ if (IS_ATTACHED_SNK(port)) { /* * Transition to Attached.SRC or * UnorientedDebugAccessory.SRC to assert Rp */ TC_SET_FLAG(port, TC_FLAGS_REQUEST_PR_SWAP); task_wake(PD_PORT_TO_TASK_ID(port)); } } /* * Hard Reset is being requested. This should not allow a TC connection * to go to an unattached state until the connection is recovered from * the hard reset. It is possible for a Hard Reset to cause a timeout * in trying to recover and an additional Hard Reset would be issued. * During this entire process it is important that the TC is not allowed * to go to an unattached state. * * Type-C Spec Rev 2.0 section 4.5.2.2.5.2 * Exiting from Attached.SNK State * A port that is not a V CONN-Powered USB Device and is not in the * process of a USB PD PR_Swap or a USB PD Hard Reset or a USB PD * FR_Swap shall transition to Unattached.SNK */ void tc_hard_reset_request(int port) { TC_SET_FLAG(port, TC_FLAGS_HARD_RESET_REQUESTED); task_wake(PD_PORT_TO_TASK_ID(port)); } void tc_try_src_override(enum try_src_override_t ov) { if (!IS_ENABLED(CONFIG_USB_PD_TRY_SRC)) assert(0); if (IS_ENABLED(CONFIG_USB_PD_TRY_SRC)) { switch (ov) { case TRY_SRC_OVERRIDE_OFF: /* 0 */ pd_try_src_override = TRY_SRC_OVERRIDE_OFF; break; case TRY_SRC_OVERRIDE_ON: /* 1 */ pd_try_src_override = TRY_SRC_OVERRIDE_ON; break; default: pd_try_src_override = TRY_SRC_NO_OVERRIDE; } } } enum try_src_override_t tc_get_try_src_override(void) { if (!IS_ENABLED(CONFIG_USB_PD_TRY_SRC)) assert(0); return pd_try_src_override; } void tc_snk_power_off(int port) { if (IS_ATTACHED_SNK(port)) { TC_SET_FLAG(port, TC_FLAGS_POWER_OFF_SNK); sink_stop_drawing_current(port); } } int tc_src_power_on(int port) { /* * Check our OC event counter. If we've exceeded our threshold, then * let's latch our source path off to prevent continuous cycling. When * the PD state machine detects a disconnection on the CC lines, we will * reset our OC event counter. */ if (IS_ENABLED(CONFIG_USBC_OCP) && usbc_ocp_is_port_latched_off(port)) return EC_ERROR_ACCESS_DENIED; if (IS_ATTACHED_SRC(port)) return pd_set_power_supply_ready(port); return 0; } void tc_src_power_off(int port) { /* Remove VBUS */ pd_power_supply_reset(port); if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) charge_manager_set_ceil(port, CEIL_REQUESTOR_PD, CHARGE_CEIL_NONE); } /* Set what role the partner is right now, for the PPC and OCP module */ static void tc_set_partner_role(int port, enum ppc_device_role role) { if (IS_ENABLED(CONFIG_USBC_PPC)) ppc_dev_is_connected(port, role); if (IS_ENABLED(CONFIG_USBC_OCP)) { usbc_ocp_snk_is_connected(port, role == PPC_DEV_SNK); /* * Clear the overcurrent event counter * since we've detected a disconnect. */ if (role == PPC_DEV_DISCONNECTED) usbc_ocp_clear_event_counter(port); } } /* * Depending on the load on the processor and the tasks running * it can take a while for the task associated with this port * to run. So build in 1ms delays, for up to 300ms, to wait for * the suspend to actually happen. */ #define SUSPEND_SLEEP_DELAY 1 #define SUSPEND_SLEEP_RETRIES 300 void pd_set_suspend(int port, int suspend) { if (pd_is_port_enabled(port) == !suspend) return; /* Track if we are suspended or not */ if (suspend) { int wait = 0; TC_SET_FLAG(port, TC_FLAGS_REQUEST_SUSPEND); /* * Avoid deadlock when running from task * which we are going to suspend */ if (PD_PORT_TO_TASK_ID(port) == task_get_current()) return; task_wake(PD_PORT_TO_TASK_ID(port)); /* Sleep this task if we are not suspended */ while (pd_is_port_enabled(port)) { if (++wait > SUSPEND_SLEEP_RETRIES) { CPRINTS("C%d: NOT SUSPENDED after %dms", port, wait * SUSPEND_SLEEP_DELAY); return; } msleep(SUSPEND_SLEEP_DELAY); } } else { TC_CLR_FLAG(port, TC_FLAGS_REQUEST_SUSPEND); task_wake(PD_PORT_TO_TASK_ID(port)); } } void pd_set_error_recovery(int port) { TC_SET_FLAG(port, TC_FLAGS_REQUEST_ERROR_RECOVERY); } int pd_is_port_enabled(int port) { /* * Checking get_state_tc(port) from another task isn't safe since it * can return TC_DISABLED before tc_cc_open_entry and tc_disabled_entry * are complete. So check TC_FLAGS_SUSPENDED instead. */ return !TC_CHK_FLAG(port, TC_FLAGS_SUSPENDED); } int pd_fetch_acc_log_entry(int port) { if (IS_ENABLED(CONFIG_USB_PE_SM)) pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_GET_LOG, NULL, 0); return EC_RES_SUCCESS; } enum tcpc_cc_polarity pd_get_polarity(int port) { return tc[port].polarity; } enum pd_data_role pd_get_data_role(int port) { return tc[port].data_role; } enum pd_power_role pd_get_power_role(int port) { return tc[port].power_role; } enum pd_cc_states pd_get_task_cc_state(int port) { return tc[port].cc_state; } uint8_t pd_get_task_state(int port) { return get_state_tc(port); } bool pd_get_vconn_state(int port) { return !!TC_CHK_FLAG(port, TC_FLAGS_VCONN_ON); } const char *pd_get_task_state_name(int port) { return tc_get_current_state(port); } void pd_vbus_low(int port) { TC_CLR_FLAG(port, TC_FLAGS_VBUS_NEVER_LOW); } int pd_is_connected(int port) { return (IS_ATTACHED_SRC(port) || (IS_ENABLED(CONFIG_USB_PE_SM) && ((get_state_tc(port) == TC_CT_UNATTACHED_SNK) || (get_state_tc(port) == TC_CT_ATTACHED_SNK))) || IS_ATTACHED_SNK(port)); } bool pd_is_disconnected(int port) { return !pd_is_connected(port); } /* * PD functions which query our fixed PDO flags. Both the source and sink * capabilities can present these values, and they should match between the two * for compliant partners. */ static bool pd_check_fixed_flag(int port, uint32_t flag) { uint32_t fixed_pdo; if (pd_get_src_cap_cnt(port) != 0) fixed_pdo = *pd_get_src_caps(port); else if (pd_get_snk_cap_cnt(port) != 0) fixed_pdo = *pd_get_snk_caps(port); else return false; /* * Error check that first PDO is fixed, as 6.4.1 Capabilities requires * in the Power Delivery Specification. * "The vSafe5V Fixed Supply Object Shall always be the first object" */ if ((fixed_pdo & PDO_TYPE_MASK) != PDO_TYPE_FIXED) return false; return fixed_pdo & flag; } bool pd_get_partner_data_swap_capable(int port) { return pd_check_fixed_flag(port, PDO_FIXED_DATA_SWAP); } bool pd_get_partner_usb_comm_capable(int port) { return pd_check_fixed_flag(port, PDO_FIXED_COMM_CAP); } bool pd_get_partner_dual_role_power(int port) { return pd_check_fixed_flag(port, PDO_FIXED_DUAL_ROLE); } bool pd_get_partner_unconstr_power(int port) { return pd_check_fixed_flag(port, PDO_FIXED_UNCONSTRAINED); } static void bc12_role_change_handler(int port, enum pd_data_role prev_data_role, enum pd_data_role data_role) { int event = 0; int task_id = USB_CHG_PORT_TO_TASK_ID(port); bool role_changed = (data_role != prev_data_role); if (!IS_ENABLED(CONFIG_BC12_DETECT_DATA_ROLE_TRIGGER)) return; /* Get the data role of our device */ switch (data_role) { case PD_ROLE_UFP: /* Only trigger BC12 detection on a role change */ if (role_changed) event = USB_CHG_EVENT_DR_UFP; break; case PD_ROLE_DFP: /* Only trigger BC12 host mode on a role change */ if (role_changed) event = USB_CHG_EVENT_DR_DFP; break; case PD_ROLE_DISCONNECTED: event = USB_CHG_EVENT_CC_OPEN; break; default: return; } if (event) task_set_event(task_id, event); } /* * TCPC CC/Rp management */ static void typec_select_pull(int port, enum tcpc_cc_pull pull) { tc[port].select_cc_pull = pull; } void typec_select_src_current_limit_rp(int port, enum tcpc_rp_value rp) { tc[port].select_current_limit_rp = rp; if (IS_ATTACHED_SRC(port)) TC_SET_FLAG(port, TC_FLAGS_UPDATE_CURRENT); } __overridable int typec_get_default_current_limit_rp(int port) { return CONFIG_USB_PD_PULLUP; } void typec_select_src_collision_rp(int port, enum tcpc_rp_value rp) { tc[port].select_collision_rp = rp; } static enum tcpc_rp_value typec_get_active_select_rp(int port) { /* Explicit contract will use the collision Rp */ if (IS_ENABLED(CONFIG_USB_PD_REV30) && pe_is_explicit_contract(port)) return tc[port].select_collision_rp; return tc[port].select_current_limit_rp; } int typec_update_cc(int port) { int rv; enum tcpc_cc_pull pull = tc[port].select_cc_pull; enum tcpc_rp_value rp = typec_get_active_select_rp(port); rv = tcpm_select_rp_value(port, rp); if (rv) return rv; return tcpm_set_cc(port, pull); } #ifdef CONFIG_USB_PE_SM /* * This function performs a source hard reset. It should be called * repeatedly until a true value is returned, signaling that the * source hard reset is complete. A false value is returned otherwise. */ static bool tc_perform_src_hard_reset(int port) { switch (tc[port].ps_reset_state) { case PS_STATE0: /* Remove VBUS */ tc_src_power_off(port); /* Turn off VCONN */ set_vconn(port, 0); /* Set role to DFP */ tc_set_data_role(port, PD_ROLE_DFP); tc[port].ps_reset_state = PS_STATE1; pd_timer_enable(port, TC_TIMER_TIMEOUT, PD_T_SRC_RECOVER); return false; case PS_STATE1: /* Enable VBUS */ tc_src_power_on(port); /* Update the Rp Value */ typec_update_cc(port); /* Turn off VCONN */ set_vconn(port, 1); tc[port].ps_reset_state = PS_STATE2; pd_timer_enable(port, TC_TIMER_TIMEOUT, PD_POWER_SUPPLY_TURN_ON_DELAY); return false; case PS_STATE2: /* Tell Policy Engine Hard Reset is complete */ pe_ps_reset_complete(port); tc[port].ps_reset_state = PS_STATE0; return true; } /* * This return is added to appease the compiler. It should * never be reached because the switch handles all possible * cases of the enum ps_reset_sequence type. */ return true; } /* * Wait for recovery after a hard reset. Call repeatedly until true is * returned, signaling that the hard reset is complete. */ static bool tc_perform_snk_hard_reset(int port) { switch (tc[port].ps_reset_state) { case PS_STATE0: /* Hard reset sets us back to default data role */ tc_set_data_role(port, PD_ROLE_UFP); /* * When VCONN is supported, the Hard Reset Shall cause * the Port with the Rd resistor asserted to turn off * VCONN. */ if (IS_ENABLED(CONFIG_USBC_VCONN) && TC_CHK_FLAG(port, TC_FLAGS_VCONN_ON)) set_vconn(port, 0); /* Wait up to tVSafe0V for Vbus to disappear */ tc[port].ps_reset_state = PS_STATE1; pd_timer_enable(port, TC_TIMER_TIMEOUT, PD_T_SAFE_0V); return false; case PS_STATE1: if (pd_check_vbus_level(port, VBUS_SAFE0V)) { /* * Partner dropped Vbus, reduce our current consumption * and await its return. */ sink_stop_drawing_current(port); tcpm_enable_auto_discharge_disconnect(port, 0); /* Move on to waiting for the return of Vbus */ tc[port].ps_reset_state = PS_STATE2; pd_timer_enable(port, TC_TIMER_TIMEOUT, PD_T_SRC_RECOVER_MAX + PD_T_SRC_TURN_ON); } if (pd_timer_is_expired(port, TC_TIMER_TIMEOUT)) { /* * No Vbus drop likely indicates a non-PD port partner, * move to the next stage anyway. */ tc[port].ps_reset_state = PS_STATE2; pd_timer_enable(port, TC_TIMER_TIMEOUT, PD_T_SRC_RECOVER_MAX + PD_T_SRC_TURN_ON); } return false; case PS_STATE2: /* * Look for the voltage to be above disconnect. Since we didn't * drop our draw on non-PD partners, they may have dipped below * vSafe5V but still be in a valid connected voltage. */ if (!pd_check_vbus_level(port, VBUS_REMOVED)) { /* * Inform policy engine that power supply * reset is complete */ tc[port].ps_reset_state = PS_STATE0; pe_ps_reset_complete(port); /* * Now that VBUS is back, let's notify charge manager * regarding the source's current capabilities. * sink_power_sub_states() reacts to changes in CC * terminations, however during a HardReset, the * terminations of a non-PD port partner will not * change. Therefore, set the debounce time to right * now, such that we'll actually reset the correct input * current limit. */ pd_timer_enable(port, TC_TIMER_CC_DEBOUNCE, 0); sink_power_sub_states(port); /* Power is back, Enable AutoDischargeDisconnect */ tcpm_enable_auto_discharge_disconnect(port, 1); return true; } /* * If Vbus isn't back after wait + tSrcTurnOn, go unattached */ if (pd_timer_is_expired(port, TC_TIMER_TIMEOUT)) { tc[port].ps_reset_state = PS_STATE0; set_state_tc(port, TC_UNATTACHED_SNK); return true; } } return false; } #endif /* CONFIG_USB_PE_SM */ void tc_start_error_recovery(int port) { assert(port == TASK_ID_TO_PD_PORT(task_get_current())); /* * The port should transition to the ErrorRecovery state * from any other state when directed. */ set_state_tc(port, TC_ERROR_RECOVERY); } static void restart_tc_sm(int port, enum usb_tc_state start_state) { int res; /* Clear flags before we transitions states */ tc[port].flags = 0; res = tcpm_init(port); CPRINTS("C%d: TCPC init %s", port, res ? "failed" : "ready"); /* * Update the Rp Value. We don't need to update CC lines though as that * happens in below set_state transition. */ typec_select_src_current_limit_rp(port, typec_get_default_current_limit_rp(port)); /* Disable if restart failed, otherwise start in default state. */ set_state_tc(port, res ? TC_DISABLED : start_state); if (IS_ENABLED(CONFIG_USBC_SS_MUX)) /* Initialize USB mux to its default state */ usb_mux_init(port); if (IS_ENABLED(CONFIG_USBC_PPC)) { /* * Wait to initialize the PPC after tcpc, which sets * the correct Rd values; otherwise the TCPC might * not be pulling the CC lines down when the PPC connects the * CC lines from the USB connector to the TCPC cause the source * to drop Vbus causing a brown out. */ ppc_init(port); } if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) { /* * Only initialize PD supplier current limit to 0. * Defer initializing type-C supplier current limit * to Unattached.SNK or Attached.SNK. */ pd_set_input_current_limit(port, 0, 0); charge_manager_update_dualrole(port, CAP_UNKNOWN); } /* * PD r3.0 v2.0, ss6.2.1.1.5: * After a physical or logical (USB Type-C Error Recovery) Attach, a * Port discovers the common Specification Revision level between itself * and its Port Partner and/or the Cable Plug(s), and uses this * Specification Revision level until a Detach, Hard Reset or Error * Recovery happens. * * This covers the Error Recovery case, because TC_ERROR_RECOVERY * reinitializes the TC state machine. This also covers the implicit * case when PD is suspended and resumed or when the state machine is * first initialized. */ if (IS_ENABLED(CONFIG_USB_PRL_SM)) prl_set_default_pd_revision(port); #ifdef CONFIG_USB_PE_SM tc_enable_pd(port, 0); tc[port].ps_reset_state = PS_STATE0; #endif } void tc_state_init(int port) { enum usb_tc_state first_state; /* For test builds, replicate static initialization */ if (IS_ENABLED(TEST_BUILD)) { int i; for (i = 0; i < CONFIG_USB_PD_PORT_MAX_COUNT; ++i) { memset(&tc[i], 0, sizeof(tc[i])); drp_state[i] = CONFIG_USB_PD_INITIAL_DRP_STATE; } } /* If port is not available, there is nothing to initialize */ if (port >= board_get_usb_pd_port_count()) { tc_enable_pd(port, 0); TC_SET_FLAG(port, TC_FLAGS_REQUEST_SUSPEND); return; } /* Allow system to set try src enable */ if (IS_ENABLED(CONFIG_USB_PD_TRY_SRC)) tc_try_src_override(TRY_SRC_NO_OVERRIDE); /* * Set initial PD communication policy. */ tc_policy_pd_enable(port, pd_comm_allowed_by_policy()); #ifdef HAS_TASK_CHIPSET /* Set dual-role state based on chipset power state */ if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) pd_set_dual_role_and_event(port, PD_DRP_FORCE_SINK, 0); else if (chipset_in_state(CHIPSET_STATE_ANY_SUSPEND)) pd_set_dual_role_and_event(port, pd_get_drp_state_in_suspend(), 0); else /* CHIPSET_STATE_ON */ pd_set_dual_role_and_event(port, PD_DRP_TOGGLE_ON, 0); #else pd_set_dual_role_and_event(port, board_tc_get_initial_drp_mode(port), 0); #endif /* * We are going to apply CC open (start with ErrorRecovery state) * unless there is something which forbids us to do that (one of * conditions below is true) */ first_state = TC_ERROR_RECOVERY; /* * If we just lost power, don't apply CC open. Otherwise we would boot * loop, and if this is a fresh power on, then we know there isn't any * stale PD state as well. */ if (system_get_reset_flags() & (EC_RESET_FLAG_BROWNOUT | EC_RESET_FLAG_POWER_ON)) { first_state = TC_UNATTACHED_SNK; } /* * If this is non-EFS2 device, battery is not present and EC RO doesn't * keep power-on reset flag after reset caused by H1, then don't apply * CC open because it will cause brown out. * * Please note that we are checking if CONFIG_BOARD_RESET_AFTER_POWER_ON * is defined now, but actually we need to know if it was enabled in * EC RO! It was assumed that if CONFIG_BOARD_RESET_AFTER_POWER_ON is * defined now it was defined in EC RO too. */ if (!IS_ENABLED(CONFIG_BOARD_RESET_AFTER_POWER_ON) && !IS_ENABLED(CONFIG_VBOOT_EFS2) && IS_ENABLED(CONFIG_BATTERY) && (battery_is_present() == BP_NO)) { first_state = TC_UNATTACHED_SNK; } if (first_state == TC_UNATTACHED_SNK) { /* Turn off any previous sourcing */ tc_src_power_off(port); set_vconn(port, 0); } #ifdef CONFIG_USB_PD_TCPC_BOARD_INIT /* Board specific TCPC init */ board_tcpc_init(); #endif /* * Start with ErrorRecovery state if we can to put us in * a clean state from any previous boots. */ restart_tc_sm(port, first_state); } enum pd_cable_plug tc_get_cable_plug(int port) { /* * Messages sent by this state machine are always from a DFP/UFP, * i.e. the chromebook. */ return PD_PLUG_FROM_DFP_UFP; } void pd_comm_enable(int port, int en) { tc_policy_pd_enable(port, en); } uint8_t tc_get_polarity(int port) { return tc[port].polarity; } uint8_t tc_get_pd_enabled(int port) { return !tc[port].pd_disabled_mask; } bool pd_alt_mode_capable(int port) { return IS_ENABLED(CONFIG_USB_PE_SM) && tc_get_pd_enabled(port); } void tc_set_power_role(int port, enum pd_power_role role) { tc[port].power_role = role; } /* * Private Functions */ /* Set GPIO_CCD_MODE_ODL gpio */ static void set_ccd_mode(const int port, const bool enable) { if (IS_ENABLED(CONFIG_ASSERT_CCD_MODE_ON_DTS_CONNECT) && port == CONFIG_CCD_USBC_PORT_NUMBER) { if (enable) CPRINTS("Asserting GPIO_CCD_MODE_ODL"); gpio_set_level(_GPIO_CCD_MODE_ODL, !enable); } } /* Set the TypeC state machine to a new state. */ static void set_state_tc(const int port, const enum usb_tc_state new_state) { assert(port == TASK_ID_TO_PD_PORT(task_get_current())); set_state(port, &tc[port].ctx, &tc_states[new_state]); } /* Get the current TypeC state. */ test_export_static enum usb_tc_state get_state_tc(const int port) { /* Default to returning TC_STATE_COUNT if no state has been set */ if (tc[port].ctx.current == NULL) return TC_STATE_COUNT; else return tc[port].ctx.current - &tc_states[0]; } /* Get the previous TypeC state. */ static enum usb_tc_state get_last_state_tc(const int port) { return tc[port].ctx.previous - &tc_states[0]; } static void print_current_state(const int port) { if (IS_ENABLED(USB_PD_DEBUG_LABELS)) CPRINTS_L1("C%d: %s", port, tc_state_names[get_state_tc(port)]); else CPRINTS("C%d: tc-st%d", port, get_state_tc(port)); } static void handle_device_access(int port) { if (IS_ENABLED(CONFIG_USB_PD_TCPC_LOW_POWER) && get_state_tc(port) == TC_LOW_POWER_MODE) { tc_start_event_loop(port); pd_timer_enable(port, TC_TIMER_LOW_POWER_TIME, PD_LPM_DEBOUNCE_US); } } void tc_event_check(int port, int evt) { #ifdef DEBUG_PRINT_FLAG_AND_EVENT_NAMES if (evt != TASK_EVENT_TIMER) print_bits(port, "Event", evt, event_bit_names, ARRAY_SIZE(event_bit_names)); #endif if (evt & PD_EXIT_LOW_POWER_EVENT_MASK) TC_SET_FLAG(port, TC_FLAGS_CHECK_CONNECTION); if (evt & PD_EVENT_DEVICE_ACCESSED) handle_device_access(port); if (evt & PD_EVENT_TCPC_RESET) reset_device_and_notify(port); if (evt & PD_EVENT_RX_HARD_RESET) pd_execute_hard_reset(port); if (evt & PD_EVENT_SEND_HARD_RESET) { /* Pass Hard Reset request to PE layer if available */ if (IS_ENABLED(CONFIG_USB_PE_SM) && tc_get_pd_enabled(port)) pd_dpm_request(port, DPM_REQUEST_HARD_RESET_SEND); } #ifdef CONFIG_POWER_COMMON if (IS_ENABLED(CONFIG_POWER_COMMON)) { if (evt & PD_EVENT_POWER_STATE_CHANGE) handle_new_power_state(port); } #endif /* CONFIG_POWER_COMMON */ if (IS_ENABLED(CONFIG_USB_PD_ALT_MODE_DFP)) { int i; /* * Notify all ports of sysjump */ if (evt & PD_EVENT_SYSJUMP) { for (i = 0; i < CONFIG_USB_PD_PORT_MAX_COUNT; i++) dpm_set_mode_exit_request(i); notify_sysjump_ready(); } } if (evt & PD_EVENT_UPDATE_DUAL_ROLE) pd_update_dual_role_config(port); } /* * CC values for regular sources and Debug sources (aka DTS) * * Source type Mode of Operation CC1 CC2 * --------------------------------------------- * Regular Default USB Power RpUSB Open * Regular USB-C @ 1.5 A Rp1A5 Open * Regular USB-C @ 3 A Rp3A0 Open * DTS Default USB Power Rp3A0 Rp1A5 * DTS USB-C @ 1.5 A Rp1A5 RpUSB * DTS USB-C @ 3 A Rp3A0 RpUSB */ void tc_set_data_role(int port, enum pd_data_role role) { enum pd_data_role prev_data_role; prev_data_role = tc[port].data_role; tc[port].data_role = role; if (IS_ENABLED(CONFIG_USBC_SS_MUX)) set_usb_mux_with_current_data_role(port); /* * Run any board-specific code for role swap (e.g. setting OTG signals * to SoC). */ pd_execute_data_swap(port, role); /* * For BC1.2 detection that is triggered on data role change events * instead of VBUS changes, need to set an event to wake up the USB_CHG * task and indicate the current data role. */ bc12_role_change_handler(port, prev_data_role, tc[port].data_role); /* Notify TCPC of role update */ tcpm_set_msg_header(port, tc[port].power_role, tc[port].data_role); } static void sink_stop_drawing_current(int port) { pd_set_input_current_limit(port, 0, 0); if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) { typec_set_input_current_limit(port, 0, 0); charge_manager_set_ceil(port, CEIL_REQUESTOR_PD, CHARGE_CEIL_NONE); } } static void pd_update_try_source(void) { #ifdef CONFIG_USB_PD_TRY_SRC tc_enable_try_src(pd_is_try_source_capable()); #endif } DECLARE_HOOK(HOOK_BATTERY_SOC_CHANGE, pd_update_try_source, HOOK_PRIO_DEFAULT); static void set_vconn(int port, int enable) { if (enable) TC_SET_FLAG(port, TC_FLAGS_VCONN_ON); else TC_CLR_FLAG(port, TC_FLAGS_VCONN_ON); /* * Check our OC event counter. If we've exceeded our threshold, then * let's latch our source path off to prevent continuous cycling. When * the PD state machine detects a disconnection on the CC lines, we will * reset our OC event counter. */ if (IS_ENABLED(CONFIG_USBC_OCP) && enable && usbc_ocp_is_port_latched_off(port)) return; /* * Disable PPC Vconn first then TCPC in case the voltage feeds back * to TCPC and damages. */ if (IS_ENABLED(CONFIG_USBC_PPC_VCONN) && !enable) ppc_set_vconn(port, 0); /* * Some TCPCs/PPC combinations can trigger OVP if the TCPC doesn't * source VCONN. This happens if the TCPC will trip OVP with 5V, and the * PPC doesn't isolate the TCPC from VCONN when sourcing. But, some PPCs * which do isolate the TCPC can't handle 5V on its host-side CC pins, * so the TCPC shouldn't source VCONN in those cases. * * In the first case, both TCPC and PPC will potentially source Vconn, * but that should be okay since Vconn has "make before break" * electrical requirements when swapping anyway. * * See b/72961003 and b/180973460 */ tcpm_set_vconn(port, enable); if (IS_ENABLED(CONFIG_USBC_PPC_VCONN) && enable) ppc_set_vconn(port, 1); } /* This must only be called from the PD task */ static void pd_update_dual_role_config(int port) { if (tc[port].power_role == PD_ROLE_SOURCE && (drp_state[port] == PD_DRP_FORCE_SINK || (drp_state[port] == PD_DRP_TOGGLE_OFF && get_state_tc(port) == TC_UNATTACHED_SRC))) { /* * Change to sink if port is currently a source AND (new DRP * state is force sink OR new DRP state is toggle off and we are * in the source disconnected state). */ set_state_tc(port, TC_UNATTACHED_SNK); } else if (tc[port].power_role == PD_ROLE_SINK && drp_state[port] == PD_DRP_FORCE_SOURCE) { /* * Change to source if port is currently a sink and the * new DRP state is force source. */ set_state_tc(port, TC_UNATTACHED_SRC); } } __maybe_unused static void handle_new_power_state(int port) { if (!IS_ENABLED(CONFIG_POWER_COMMON)) assert(0); if (IS_ENABLED(CONFIG_POWER_COMMON) && IS_ENABLED(CONFIG_USB_PE_SM)) { if (chipset_in_or_transitioning_to_state( CHIPSET_STATE_ANY_OFF)) { /* * The SoC will negotiate alternate mode again when it * boots up */ dpm_set_mode_exit_request(port); } } /* * If the sink port was sourcing Vconn, and can no longer, request a * hard reset on this port to restore Vconn to the source. */ if (IS_ENABLED(CONFIG_USB_PE_SM)) { if (tc_is_vconn_src(port) && tc_is_attached_snk(port) && !pd_check_vconn_swap(port)) pd_dpm_request(port, DPM_REQUEST_HARD_RESET_SEND); } /* * TC_FLAGS_UPDATE_USB_MUX is set on chipset startup and shutdown. * Set the USB mux according to the new power state. If the chipset * is transitioning to OFF, this disconnects USB and DP mux. * * Transitions to and from suspend states do not change the USB mux * or the alternate mode configuration. */ if (TC_CHK_FLAG(port, TC_FLAGS_UPDATE_USB_MUX)) { TC_CLR_FLAG(port, TC_FLAGS_UPDATE_USB_MUX); set_usb_mux_with_current_data_role(port); } } #ifdef CONFIG_USBC_VCONN_SWAP void pd_request_vconn_swap_off(int port) { if (get_state_tc(port) == TC_ATTACHED_SRC || get_state_tc(port) == TC_ATTACHED_SNK) { TC_SET_FLAG(port, TC_FLAGS_REQUEST_VC_SWAP_OFF); task_wake(PD_PORT_TO_TASK_ID(port)); } } void pd_request_vconn_swap_on(int port) { if (get_state_tc(port) == TC_ATTACHED_SRC || get_state_tc(port) == TC_ATTACHED_SNK) { TC_SET_FLAG(port, TC_FLAGS_REQUEST_VC_SWAP_ON); task_wake(PD_PORT_TO_TASK_ID(port)); } } void pd_request_vconn_swap(int port) { pd_dpm_request(port, DPM_REQUEST_VCONN_SWAP); } #endif int tc_is_vconn_src(int port) { if (IS_ENABLED(CONFIG_USBC_VCONN)) return TC_CHK_FLAG(port, TC_FLAGS_VCONN_ON); else return 0; } static __maybe_unused int reset_device_and_notify(int port) { int rv; int task, waiting_tasks; /* This should only be called from the PD task */ assert(port == TASK_ID_TO_PD_PORT(task_get_current())); TC_SET_FLAG(port, TC_FLAGS_LPM_TRANSITION); rv = tcpm_init(port); TC_CLR_FLAG(port, TC_FLAGS_LPM_TRANSITION); TC_CLR_FLAG(port, TC_FLAGS_LPM_ENGAGED); tc_start_event_loop(port); CPRINTS("C%d: TCPC init %s", port, rv ? "failed!" : "ready"); /* * Before getting the other tasks that are waiting, clear the reset * event from this PD task to prevent multiple reset/init events * occurring. * * The double reset event happens when the higher priority PD interrupt * task gets an interrupt during the above tcpm_init function. When that * occurs, the higher priority task waits correctly for us to finish * waking the TCPC, but it has also set PD_EVENT_TCPC_RESET again, which * would result in a second, unnecessary init. */ atomic_clear_bits(task_get_event_bitmap(task_get_current()), PD_EVENT_TCPC_RESET); waiting_tasks = atomic_clear(&tc[port].tasks_waiting_on_reset); /* Wake up all waiting tasks. */ while (waiting_tasks) { task = __fls(waiting_tasks); waiting_tasks &= ~BIT(task); task_set_event(task, TASK_EVENT_PD_AWAKE); } return rv; } #ifdef CONFIG_USB_PD_TCPC_LOW_POWER void pd_wait_exit_low_power(int port) { if (!TC_CHK_FLAG(port, TC_FLAGS_LPM_ENGAGED)) return; if (port == TASK_ID_TO_PD_PORT(task_get_current())) { if (!TC_CHK_FLAG(port, TC_FLAGS_LPM_TRANSITION)) reset_device_and_notify(port); } else { /* Otherwise, we need to wait for the TCPC reset to complete */ atomic_or(&tc[port].tasks_waiting_on_reset, 1 << task_get_current()); /* * NOTE: We could be sending the PD task the reset event while * it is already processing the reset event. If that occurs, * then we will reset the TCPC multiple times, which is * undesirable but most likely benign. Empirically, this doesn't * happen much, but it if starts occurring, we can add a guard * to prevent/reduce it. */ task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_TCPC_RESET); task_wait_event_mask(TASK_EVENT_PD_AWAKE, -1); } } /* * This can be called from any task. If we are in the PD task, we can handle * immediately. Otherwise, we need to notify the PD task via event. */ void pd_device_accessed(int port) { if (port == TASK_ID_TO_PD_PORT(task_get_current())) handle_device_access(port); else task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_DEVICE_ACCESSED); } /* * TODO(b/137493121): Move this function to a separate file that's shared * between the this and the original stack. */ void pd_prevent_low_power_mode(int port, int prevent) { const int current_task_mask = (1 << task_get_current()); if (prevent) atomic_or(&tc[port].tasks_preventing_lpm, current_task_mask); else atomic_clear_bits(&tc[port].tasks_preventing_lpm, current_task_mask); } #endif /* CONFIG_USB_PD_TCPC_LOW_POWER */ static void sink_power_sub_states(int port) { enum tcpc_cc_voltage_status cc1, cc2, cc; enum tcpc_cc_voltage_status new_cc_voltage; tcpm_get_cc(port, &cc1, &cc2); cc = polarity_rm_dts(tc[port].polarity) ? cc2 : cc1; if (cc == TYPEC_CC_VOLT_RP_DEF) new_cc_voltage = TYPEC_CC_VOLT_RP_DEF; else if (cc == TYPEC_CC_VOLT_RP_1_5) new_cc_voltage = TYPEC_CC_VOLT_RP_1_5; else if (cc == TYPEC_CC_VOLT_RP_3_0) new_cc_voltage = TYPEC_CC_VOLT_RP_3_0; else new_cc_voltage = TYPEC_CC_VOLT_OPEN; /* Debounce the cc state */ if (new_cc_voltage != tc[port].cc_voltage) { tc[port].cc_voltage = new_cc_voltage; pd_timer_enable(port, TC_TIMER_CC_DEBOUNCE, PD_T_RP_VALUE_CHANGE); return; } if (!pd_timer_is_disabled(port, TC_TIMER_CC_DEBOUNCE)) { if (!pd_timer_is_expired(port, TC_TIMER_CC_DEBOUNCE)) return; pd_timer_disable(port, TC_TIMER_CC_DEBOUNCE); if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) { tc[port].typec_curr = usb_get_typec_current_limit( tc[port].polarity, cc1, cc2); typec_set_input_current_limit(port, tc[port].typec_curr, TYPE_C_VOLTAGE); charge_manager_update_dualrole(port, CAP_DEDICATED); } } } /* * TYPE-C State Implementations */ /** * Disabled * * Super State Entry Actions: * Remove the terminations from CC * Set VBUS and VCONN off */ static void tc_disabled_entry(const int port) { print_current_state(port); /* * We have completed tc_cc_open_entry (our super state), so set flag * to indicate to pd_is_port_enabled that we are now suspended. */ TC_SET_FLAG(port, TC_FLAGS_SUSPENDED); } static void tc_disabled_run(const int port) { /* If pd_set_suspend clears the request, go to TC_UNATTACHED_SNK/SRC. */ if (!TC_CHK_FLAG(port, TC_FLAGS_REQUEST_SUSPEND)) { set_state_tc(port, drp_state[port] == PD_DRP_FORCE_SOURCE ? TC_UNATTACHED_SRC : TC_UNATTACHED_SNK); } else { if (IS_ENABLED(CONFIG_USBC_RETIMER_FW_UPDATE)) { if (TC_CHK_FLAG(port, TC_FLAGS_USB_RETIMER_FW_UPDATE_LTD_RUN)) { TC_CLR_FLAG(port, TC_FLAGS_USB_RETIMER_FW_UPDATE_LTD_RUN); usb_retimer_fw_update_process_op_cb(port); } } tc_pause_event_loop(port); } } static void tc_disabled_exit(const int port) { int rv; tc_start_event_loop(port); TC_CLR_FLAG(port, TC_FLAGS_SUSPENDED); rv = tcpm_init(port); CPRINTS("C%d: TCPC init %s", port, rv ? "failed!" : "ready"); } /** * ErrorRecovery * * Super State Entry Actions: * Remove the terminations from CC * Set's VBUS and VCONN off */ static void tc_error_recovery_entry(const int port) { print_current_state(port); pd_timer_enable(port, TC_TIMER_TIMEOUT, PD_T_ERROR_RECOVERY); TC_CLR_FLAG(port, TC_FLAGS_REQUEST_ERROR_RECOVERY); } static void tc_error_recovery_run(const int port) { enum usb_tc_state start_state; if (!pd_timer_is_expired(port, TC_TIMER_TIMEOUT)) return; /* * If we transitioned to error recovery as the first state and we * didn't brown out, we don't need to reinitialized the tc statemachine * because we just did that. So transition to the state directly. */ if (tc[port].ctx.previous == NULL) { set_state_tc(port, drp_state[port] == PD_DRP_FORCE_SOURCE ? TC_UNATTACHED_SRC : TC_UNATTACHED_SNK); return; } /* * If try src support is active (e.g. in S0). Then try to become the * SRC, otherwise we should try to be the sink. */ start_state = TC_UNATTACHED_SNK; if (IS_ENABLED(CONFIG_USB_PD_TRY_SRC)) if (is_try_src_enabled(port) || drp_state[port] == PD_DRP_FORCE_SOURCE) start_state = TC_UNATTACHED_SRC; restart_tc_sm(port, start_state); } static void tc_error_recovery_exit(const int port) { pd_timer_disable(port, TC_TIMER_TIMEOUT); } /** * Unattached.SNK */ static void tc_unattached_snk_entry(const int port) { enum pd_data_role prev_data_role; if (get_last_state_tc(port) != TC_UNATTACHED_SRC) { tc_detached(port); print_current_state(port); } /* * We are in an unattached state and considering to be a SNK * searching for a SRC partner. We set the CC pull value to * to indicate our intent to be SNK in hopes a partner SRC * will is there to attach to. * * Both CC1 and CC2 pins shall be independently terminated to * ground through Rd. * * Restore default current limit Rp in case we swap to source * * Run any debug detaches needed before setting CC, as some TCPCs may * require we set CC Open before changing power roles with a debug * accessory. */ tcpm_debug_detach(port); typec_select_pull(port, TYPEC_CC_RD); typec_select_src_current_limit_rp(port, typec_get_default_current_limit_rp(port)); typec_update_cc(port); prev_data_role = tc[port].data_role; tc[port].data_role = PD_ROLE_DISCONNECTED; /* * When data role set events are used to enable BC1.2, then CC * detach events are used to notify BC1.2 that it can be powered * down. */ bc12_role_change_handler(port, prev_data_role, tc[port].data_role); if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) charge_manager_update_dualrole(port, CAP_UNKNOWN); tc_set_partner_role(port, PPC_DEV_DISCONNECTED); /* * Indicate that the port is disconnected so the board * can restore state from any previous data swap. */ pd_execute_data_swap(port, PD_ROLE_DISCONNECTED); pd_timer_enable(port, TC_TIMER_NEXT_ROLE_SWAP, PD_T_DRP_SNK); if (IS_ENABLED(CONFIG_USB_PE_SM)) { CLR_FLAGS_ON_DISCONNECT(port); tc_enable_pd(port, 0); } } static void tc_unattached_snk_run(const int port) { enum tcpc_cc_voltage_status cc1, cc2; /* * TODO(b/137498392): Add wait before sampling the CC * status after role changes */ if (IS_ENABLED(CONFIG_USB_PE_SM)) { if (TC_CHK_FLAG(port, TC_FLAGS_HARD_RESET_REQUESTED)) { TC_CLR_FLAG(port, TC_FLAGS_HARD_RESET_REQUESTED); tc_set_data_role(port, PD_ROLE_UFP); /* Inform Policy Engine that hard reset is complete */ pe_ps_reset_complete(port); } } /* Check for connection */ tcpm_get_cc(port, &cc1, &cc2); /* * The port shall transition to AttachWait.SNK when a Source * connection is detected, as indicated by the SNK.Rp state * on at least one of its CC pins. * * A DRP shall transition to Unattached.SRC within tDRPTransition * after the state of both CC pins is SNK.Open for * tDRP − dcSRC.DRP ∙ tDRP. */ if (cc_is_rp(cc1) || cc_is_rp(cc2)) { /* Connection Detected */ set_state_tc(port, TC_ATTACH_WAIT_SNK); return; } /* * Debounce the CC open status. Some TCPC needs time to get the CC * status valid. Before that, CC open is reported by default. Wait * to make sure the CC is really open. Reuse the role toggle timer. */ if (!pd_timer_is_expired(port, TC_TIMER_NEXT_ROLE_SWAP)) return; /* * Initialize type-C supplier current limits to 0. The charge * manage is now seeded if it was not. */ if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) typec_set_input_current_limit(port, 0, 0); /* * Attempt TCPC auto DRP toggle if it is * not already auto toggling. */ if (IS_ENABLED(CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE) && drp_state[port] == PD_DRP_TOGGLE_ON && tcpm_auto_toggle_supported(port)) { set_state_tc(port, TC_DRP_AUTO_TOGGLE); } else if (drp_state[port] == PD_DRP_TOGGLE_ON) { /* DRP Toggle. The timer was checked above. */ set_state_tc(port, TC_UNATTACHED_SRC); } else if (IS_ENABLED(CONFIG_USB_PD_TCPC_LOW_POWER) && (drp_state[port] == PD_DRP_FORCE_SINK || drp_state[port] == PD_DRP_TOGGLE_OFF)) { set_state_tc(port, TC_LOW_POWER_MODE); } } static void tc_unattached_snk_exit(const int port) { pd_timer_disable(port, TC_TIMER_NEXT_ROLE_SWAP); } /** * AttachWait.SNK * * Super State Entry Actions: * Vconn Off * Place Rd on CC * Set power role to SINK */ static void tc_attach_wait_snk_entry(const int port) { print_current_state(port); tc[port].cc_state = PD_CC_UNSET; } static void tc_attach_wait_snk_run(const int port) { enum tcpc_cc_voltage_status cc1, cc2; enum pd_cc_states new_cc_state; /* Check for connection */ tcpm_get_cc(port, &cc1, &cc2); if (cc_is_rp(cc1) && cc_is_rp(cc2) && board_is_dts_port(port)) new_cc_state = PD_CC_DFP_DEBUG_ACC; else if (cc_is_rp(cc1) || cc_is_rp(cc2)) new_cc_state = PD_CC_DFP_ATTACHED; else new_cc_state = PD_CC_NONE; /* Debounce the cc state */ if (new_cc_state != tc[port].cc_state) { pd_timer_enable(port, TC_TIMER_CC_DEBOUNCE, PD_T_CC_DEBOUNCE); pd_timer_enable(port, TC_TIMER_PD_DEBOUNCE, PD_T_PD_DEBOUNCE); tc[port].cc_state = new_cc_state; return; } /* * A DRP shall transition to Unattached.SRC when the state of both * the CC1 and CC2 pins is SNK.Open for at least tPDDebounce, however * when DRP state prevents switch to SRC the next state should be * Unattached.SNK. */ if (new_cc_state == PD_CC_NONE && pd_timer_is_expired(port, TC_TIMER_PD_DEBOUNCE)) { /* We are detached */ if (drp_state[port] == PD_DRP_TOGGLE_OFF || drp_state[port] == PD_DRP_FREEZE || drp_state[port] == PD_DRP_FORCE_SINK) set_state_tc(port, TC_UNATTACHED_SNK); else set_state_tc(port, TC_UNATTACHED_SRC); return; } /* Wait for CC debounce */ if (!pd_timer_is_expired(port, TC_TIMER_CC_DEBOUNCE)) return; /* * The port shall transition to Attached.SNK after the state of only * one of the CC1 or CC2 pins is SNK.Rp for at least tCCDebounce and * VBUS is detected. * * A DRP that strongly prefers the Source role may optionally * transition to Try.SRC instead of Attached.SNK when the state of only * one CC pin has been SNK.Rp for at least tCCDebounce and VBUS is * detected. * * If the port supports Debug Accessory Mode, the port shall transition * to DebugAccessory.SNK if the state of both the CC1 and CC2 pins is * SNK.Rp for at least tCCDebounce and VBUS is detected. */ if (pd_is_vbus_present(port)) { if (new_cc_state == PD_CC_DFP_ATTACHED) { if (IS_ENABLED(CONFIG_USB_PD_TRY_SRC) && is_try_src_enabled(port)) set_state_tc(port, TC_TRY_SRC); else set_state_tc(port, TC_ATTACHED_SNK); } else { /* new_cc_state is PD_CC_DFP_DEBUG_ACC */ CPRINTS("C%d: Debug accessory detected", port); TC_SET_FLAG(port, TC_FLAGS_TS_DTS_PARTNER); set_state_tc(port, TC_ATTACHED_SNK); } if (IS_ENABLED(CONFIG_USB_PE_SM) && IS_ENABLED(CONFIG_USB_PD_ALT_MODE_DFP)) { hook_call_deferred(&pd_usb_billboard_deferred_data, PD_T_AME); } } } static void tc_attach_wait_snk_exit(const int port) { pd_timer_disable(port, TC_TIMER_CC_DEBOUNCE); pd_timer_disable(port, TC_TIMER_PD_DEBOUNCE); } /** * Attached.SNK, shared with Debug Accessory.SNK */ static void tc_attached_snk_entry(const int port) { enum tcpc_cc_voltage_status cc1, cc2; print_current_state(port); /* * Known state of attach is SNK. We need to apply this pull value * to make it set in hardware at the correct time but set the common * pull here. * * Both CC1 and CC2 pins shall be independently terminated to * ground through Rd. */ typec_select_pull(port, TYPEC_CC_RD); /* Inform the PPC and OCP module that a source is connected */ tc_set_partner_role(port, PPC_DEV_SRC); if (IS_ENABLED(CONFIG_USB_PE_SM) && TC_CHK_FLAG(port, TC_FLAGS_PR_SWAP_IN_PROGRESS)) { /* Flipping power role - Disable AutoDischargeDisconnect */ tcpm_enable_auto_discharge_disconnect(port, 0); /* Apply Rd */ typec_update_cc(port); /* Change role to sink */ tc_set_power_role(port, PD_ROLE_SINK); tcpm_set_msg_header(port, tc[port].power_role, tc[port].data_role); /* * Maintain VCONN supply state, whether ON or OFF, and its * data role / usb mux connections. Do not re-enable * AutoDischargeDisconnect until the swap is completed * and tc_pr_swap_complete is called. */ } else { /* Get connector orientation */ tcpm_get_cc(port, &cc1, &cc2); tc[port].polarity = get_snk_polarity(cc1, cc2); pd_set_polarity(port, tc[port].polarity); tc_set_data_role(port, PD_ROLE_UFP); hook_notify(HOOK_USB_PD_CONNECT); if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) { tc[port].typec_curr = usb_get_typec_current_limit(tc[port].polarity, cc1, cc2); typec_set_input_current_limit(port, tc[port].typec_curr, TYPE_C_VOLTAGE); /* * Start new connections as dedicated until source caps * are received, at which point the PE will update the * flag. */ charge_manager_update_dualrole(port, CAP_DEDICATED); } /* Apply Rd */ typec_update_cc(port); /* * Attached.SNK - enable AutoDischargeDisconnect * Do this after applying Rd to CC lines to avoid * TCPC_REG_FAULT_STATUS_AUTO_DISCHARGE_FAIL (b/171567398) */ tcpm_enable_auto_discharge_disconnect(port, 1); } pd_timer_disable(port, TC_TIMER_CC_DEBOUNCE); /* Enable PD */ if (IS_ENABLED(CONFIG_USB_PE_SM)) tc_enable_pd(port, 1); if (TC_CHK_FLAG(port, TC_FLAGS_TS_DTS_PARTNER)) { tcpm_debug_accessory(port, 1); set_ccd_mode(port, 1); } } /* * Check whether Vbus has been removed on this port, accounting for some Vbus * debounce if FRS is enabled. * * Returns true if a new state was set and the calling run should exit. */ static bool tc_snk_check_vbus_removed(const int port) { if (IS_ENABLED(CONFIG_USB_PD_FRS)) { /* * Debounce Vbus presence when FRS is enabled. Note that we may * lose Vbus before the FRS signal comes in to let us know * we're PR swapping, but we must still transition to unattached * within tSinkDisconnect. * * We may safely re-use the Vbus debounce timer here * since a PR swap would no longer be in progress when Vbus * removal is checked. */ if (pd_check_vbus_level(port, VBUS_REMOVED)) { if (pd_timer_is_disabled(port, TC_TIMER_VBUS_DEBOUNCE)) { pd_timer_enable(port, TC_TIMER_VBUS_DEBOUNCE, PD_T_FRS_VBUS_DEBOUNCE); } else if (pd_timer_is_expired(port, TC_TIMER_VBUS_DEBOUNCE)) { set_state_tc(port, TC_UNATTACHED_SNK); return true; } } else { pd_timer_disable(port, TC_TIMER_VBUS_DEBOUNCE); } } else if (pd_check_vbus_level(port, VBUS_REMOVED)) { set_state_tc(port, TC_UNATTACHED_SNK); return true; } return false; } static void tc_attached_snk_run(const int port) { #ifdef CONFIG_USB_PE_SM /* * Perform Hard Reset */ if (TC_CHK_FLAG(port, TC_FLAGS_HARD_RESET_REQUESTED)) { /* * Wait to clear the hard reset request until Vbus has returned * to default (or, if it didn't return, we transition to * unattached) */ if (tc_perform_snk_hard_reset(port)) TC_CLR_FLAG(port, TC_FLAGS_HARD_RESET_REQUESTED); return; } /* * From 4.5.2.2.5.2 Exiting from Attached.SNK State: * * "A port that is not a Vconn-Powered USB Device and is not in the * process of a USB PD PR_Swap or a USB PD Hard Reset or a USB PD * FR_Swap shall transition to Unattached.SNK within tSinkDisconnect * when Vbus falls below vSinkDisconnect for Vbus operating at or * below 5 V or below vSinkDisconnectPD when negotiated by USB PD * to operate above 5 V." * * TODO(b/149530538): Use vSinkDisconnectPD when above 5V */ /* * Debounce Vbus before we drop that we are doing a PR_Swap */ if (TC_CHK_FLAG(port, TC_FLAGS_PR_SWAP_IN_PROGRESS) && pd_timer_is_expired(port, TC_TIMER_VBUS_DEBOUNCE)) { /* PR Swap is no longer in progress */ TC_CLR_FLAG(port, TC_FLAGS_PR_SWAP_IN_PROGRESS); pd_timer_disable(port, TC_TIMER_VBUS_DEBOUNCE); /* * AutoDischargeDisconnect was turned off when we * hit Safe0V on SRC->SNK PR-Swap. We now are done * with the swap and should have Vbus, so re-enable * AutoDischargeDisconnect. */ if (!pd_check_vbus_level(port, VBUS_REMOVED)) tcpm_enable_auto_discharge_disconnect(port, 1); } /* * The sink will be powered off during a power role swap but we don't * want to trigger a disconnect. */ if (!TC_CHK_FLAG(port, TC_FLAGS_POWER_OFF_SNK) && !TC_CHK_FLAG(port, TC_FLAGS_PR_SWAP_IN_PROGRESS)) { /* * Detach detection */ if (tc_snk_check_vbus_removed(port)) return; if (!pe_is_explicit_contract(port)) sink_power_sub_states(port); } /* * PD swap commands */ if (tc_get_pd_enabled(port) && prl_is_running(port)) { /* * Power Role Swap */ if (TC_CHK_FLAG(port, TC_FLAGS_REQUEST_PR_SWAP)) { /* * We may want to verify partner is applying Rd before * we swap. However, some TCPCs (such as TUSB422) will * not report the correct CC status before VBUS falls to * vSafe0V, so this will be problematic in the FRS case. */ set_state_tc(port, TC_ATTACHED_SRC); return; } /* * Data Role Swap */ if (TC_CHK_FLAG(port, TC_FLAGS_REQUEST_DR_SWAP)) { TC_CLR_FLAG(port, TC_FLAGS_REQUEST_DR_SWAP); /* Perform Data Role Swap */ tc_set_data_role(port, tc[port].data_role == PD_ROLE_UFP ? PD_ROLE_DFP : PD_ROLE_UFP); } /* * VCONN Swap * UnorientedDebugAccessory.SRC shall not drive Vconn */ if (IS_ENABLED(CONFIG_USBC_VCONN) && !TC_CHK_FLAG(port, TC_FLAGS_TS_DTS_PARTNER)) { if (TC_CHK_FLAG(port, TC_FLAGS_REQUEST_VC_SWAP_ON)) { TC_CLR_FLAG(port, TC_FLAGS_REQUEST_VC_SWAP_ON); set_vconn(port, 1); /* * Inform policy engine that vconn swap is * complete */ pe_vconn_swap_complete(port); } else if (TC_CHK_FLAG(port, TC_FLAGS_REQUEST_VC_SWAP_OFF)) { TC_CLR_FLAG(port, TC_FLAGS_REQUEST_VC_SWAP_OFF); set_vconn(port, 0); /* * Inform policy engine that vconn swap is * complete */ pe_vconn_swap_complete(port); } } if (!TC_CHK_FLAG(port, TC_FLAGS_TS_DTS_PARTNER)) { /* * If the port supports Charge-Through VCONN-Powered USB * devices, and an explicit PD contract has failed to be * negotiated, the port shall query the identity of the * cable via USB PD on SOP’ */ if (!pe_is_explicit_contract(port) && TC_CHK_FLAG(port, TC_FLAGS_CTVPD_DETECTED)) { /* * A port that via SOP’ has detected an * attached Charge-Through VCONN-Powered USB * device shall transition to Unattached.SRC * if an explicit PD contract has failed to * be negotiated. */ /* CTVPD detected */ set_state_tc(port, TC_UNATTACHED_SRC); } } } #else /* CONFIG_USB_PE_SM */ /* Detach detection */ if (tc_snk_check_vbus_removed(port)) return; /* Run Sink Power Sub-State */ sink_power_sub_states(port); #endif /* CONFIG_USB_PE_SM */ } static void tc_attached_snk_exit(const int port) { if (!TC_CHK_FLAG(port, TC_FLAGS_REQUEST_PR_SWAP)) { /* * If supplying VCONN, the port shall cease to supply * it within tVCONNOFF of exiting Attached.SNK if not * PR swapping. */ if (TC_CHK_FLAG(port, TC_FLAGS_VCONN_ON)) set_vconn(port, 0); /* * Attached.SNK exit - disable AutoDischargeDisconnect * NOTE: This should not happen if we are suspending. It will * happen in tc_cc_open_entry if that is the path we are * taking. */ if (!TC_CHK_FLAG(port, TC_FLAGS_REQUEST_SUSPEND)) tcpm_enable_auto_discharge_disconnect(port, 0); } /* Clear flags after checking Vconn status */ TC_CLR_FLAG(port, TC_FLAGS_REQUEST_PR_SWAP | TC_FLAGS_POWER_OFF_SNK); /* Stop drawing power */ sink_stop_drawing_current(port); if (TC_CHK_FLAG(port, TC_FLAGS_TS_DTS_PARTNER)) tcpm_debug_detach(port); pd_timer_disable(port, TC_TIMER_CC_DEBOUNCE); pd_timer_disable(port, TC_TIMER_TIMEOUT); pd_timer_disable(port, TC_TIMER_VBUS_DEBOUNCE); } /** * Unattached.SRC */ static void tc_unattached_src_entry(const int port) { enum pd_data_role prev_data_role; if (get_last_state_tc(port) != TC_UNATTACHED_SNK) { tc_detached(port); print_current_state(port); } /* * We are in an unattached state and considering to be a SRC * searching for a SNK partner. We set the CC pull value to * to indicate our intent to be SRC in hopes a partner SNK * will is there to attach to. * * Both CC1 and CC2 pins shall be independently terminated to * ground through Rp. * * Restore default current limit Rp. * * Run any debug detaches needed before setting CC, as some TCPCs may * require we set CC Open before changing power roles with a debug * accessory. */ tcpm_debug_detach(port); typec_select_pull(port, TYPEC_CC_RP); typec_select_src_current_limit_rp(port, typec_get_default_current_limit_rp(port)); typec_update_cc(port); prev_data_role = tc[port].data_role; tc[port].data_role = PD_ROLE_DISCONNECTED; /* * When data role set events are used to enable BC1.2, then CC * detach events are used to notify BC1.2 that it can be powered * down. */ bc12_role_change_handler(port, prev_data_role, tc[port].data_role); tc_set_partner_role(port, PPC_DEV_DISCONNECTED); if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) charge_manager_update_dualrole(port, CAP_UNKNOWN); if (IS_ENABLED(CONFIG_USB_PE_SM)) { CLR_FLAGS_ON_DISCONNECT(port); tc_enable_pd(port, 0); } pd_timer_enable(port, TC_TIMER_NEXT_ROLE_SWAP, PD_T_DRP_SRC); } static void tc_unattached_src_run(const int port) { enum tcpc_cc_voltage_status cc1, cc2; if (IS_ENABLED(CONFIG_USB_PE_SM)) { if (TC_CHK_FLAG(port, TC_FLAGS_HARD_RESET_REQUESTED)) { TC_CLR_FLAG(port, TC_FLAGS_HARD_RESET_REQUESTED); tc_set_data_role(port, PD_ROLE_DFP); /* Inform Policy Engine that hard reset is complete */ pe_ps_reset_complete(port); } } if (IS_ENABLED(CONFIG_USBC_OCP)) { /* * If the port is latched off, just continue to * monitor for a detach. */ if (usbc_ocp_is_port_latched_off(port)) return; } /* Check for connection */ tcpm_get_cc(port, &cc1, &cc2); /* * Transition to AttachWait.SRC when: * 1) The SRC.Rd state is detected on either CC1 or CC2 pin or * 2) The SRC.Ra state is detected on both the CC1 and CC2 pins. * * A DRP shall transition to Unattached.SNK within tDRPTransition * after dcSRC.DRP ∙ tDRP */ if (cc_is_at_least_one_rd(cc1, cc2) || cc_is_audio_acc(cc1, cc2)) set_state_tc(port, TC_ATTACH_WAIT_SRC); else if (pd_timer_is_expired(port, TC_TIMER_NEXT_ROLE_SWAP) && drp_state[port] != PD_DRP_FORCE_SOURCE && drp_state[port] != PD_DRP_FREEZE) set_state_tc(port, TC_UNATTACHED_SNK); /* * Attempt TCPC auto DRP toggle */ else if (IS_ENABLED(CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE) && drp_state[port] == PD_DRP_TOGGLE_ON && tcpm_auto_toggle_supported(port) && cc_is_open(cc1, cc2)) set_state_tc(port, TC_DRP_AUTO_TOGGLE); else if (IS_ENABLED(CONFIG_USB_PD_TCPC_LOW_POWER) && (drp_state[port] == PD_DRP_FORCE_SOURCE || drp_state[port] == PD_DRP_TOGGLE_OFF)) set_state_tc(port, TC_LOW_POWER_MODE); } static void tc_unattached_src_exit(const int port) { pd_timer_disable(port, TC_TIMER_NEXT_ROLE_SWAP); } /** * AttachWait.SRC * * Super State Entry Actions: * Vconn Off * Place Rp on CC * Set power role to SOURCE */ static void tc_attach_wait_src_entry(const int port) { print_current_state(port); tc[port].cc_state = PD_CC_UNSET; } static void tc_attach_wait_src_run(const int port) { enum tcpc_cc_voltage_status cc1, cc2; enum pd_cc_states new_cc_state; /* Check for connection */ tcpm_get_cc(port, &cc1, &cc2); if (cc_is_snk_dbg_acc(cc1, cc2) && board_is_dts_port(port)) { /* * Debug accessory. * A debug accessory in a non-DTS port will be * recognized by at_least_one_rd as UFP attached. */ new_cc_state = PD_CC_UFP_DEBUG_ACC; } else if (cc_is_at_least_one_rd(cc1, cc2)) { /* UFP attached */ new_cc_state = PD_CC_UFP_ATTACHED; } else if (cc_is_audio_acc(cc1, cc2)) { /* AUDIO Accessory not supported. Just ignore */ new_cc_state = PD_CC_UFP_AUDIO_ACC; } else { /* No UFP */ if (drp_state[port] == PD_DRP_FORCE_SOURCE) set_state_tc(port, TC_UNATTACHED_SRC); else set_state_tc(port, TC_UNATTACHED_SNK); return; } /* Debounce the cc state */ if (new_cc_state != tc[port].cc_state) { pd_timer_enable(port, TC_TIMER_CC_DEBOUNCE, PD_T_CC_DEBOUNCE); tc[port].cc_state = new_cc_state; return; } /* Wait for CC debounce */ if (!pd_timer_is_expired(port, TC_TIMER_CC_DEBOUNCE)) return; /* * The port shall transition to Attached.SRC when VBUS is at vSafe0V * and the SRC.Rd state is detected on exactly one of the CC1 or CC2 * pins for at least tCCDebounce. * * If the port supports Debug Accessory Mode, it shall transition to * UnorientedDebugAccessory.SRC when VBUS is at vSafe0V and the SRC.Rd * state is detected on both the CC1 and CC2 pins for at least * tCCDebounce. */ if (pd_check_vbus_level(port, VBUS_SAFE0V)) { if (new_cc_state == PD_CC_UFP_ATTACHED) { set_state_tc(port, TC_ATTACHED_SRC); return; } else if (new_cc_state == PD_CC_UFP_DEBUG_ACC) { CPRINTS("C%d: Debug accessory detected", port); TC_SET_FLAG(port, TC_FLAGS_TS_DTS_PARTNER); set_state_tc(port, TC_ATTACHED_SRC); return; } } } static void tc_attach_wait_src_exit(const int port) { pd_timer_disable(port, TC_TIMER_CC_DEBOUNCE); } /** * Attached.SRC, shared with UnorientedDebugAccessory.SRC */ static void tc_attached_src_entry(const int port) { enum tcpc_cc_voltage_status cc1, cc2; print_current_state(port); pd_timer_disable(port, TC_TIMER_TIMEOUT); /* * Known state of attach is SRC. We need to apply this pull value * to make it set in hardware at the correct time but set the common * pull here. * * Both CC1 and CC2 pins shall be independently terminated to * pulled up through Rp. * * Set selected current limit in the hardware. */ typec_select_pull(port, TYPEC_CC_RP); typec_set_source_current_limit(port, tc[port].select_current_limit_rp); if (IS_ENABLED(CONFIG_USB_PE_SM)) { if (TC_CHK_FLAG(port, TC_FLAGS_PR_SWAP_IN_PROGRESS)) { /* Change role to source */ tc_set_power_role(port, PD_ROLE_SOURCE); tcpm_set_msg_header(port, tc[port].power_role, tc[port].data_role); /* Enable VBUS */ tc_src_power_on(port); /* Apply Rp */ typec_update_cc(port); /* * Maintain VCONN supply state, whether ON or OFF, and * its data role / usb mux connections. Do not * re-enable AutoDischargeDisconnect until the swap is * completed and tc_pr_swap_complete is called. */ } else { /* * Set up CC's, Vconn, and ADD before Vbus, as per * Figure 4-24. DRP Initialization and Connection * Detection in TCPCI r2 v1.2 specification. */ /* Get connector orientation */ tcpm_get_cc(port, &cc1, &cc2); tc[port].polarity = get_src_polarity(cc1, cc2); pd_set_polarity(port, tc[port].polarity); /* Attached.SRC - enable AutoDischargeDisconnect */ tcpm_enable_auto_discharge_disconnect(port, 1); /* Apply Rp */ typec_update_cc(port); /* * Initial data role for sink is DFP * This also sets the usb mux */ tc_set_data_role(port, PD_ROLE_DFP); /* * Start sourcing Vconn before Vbus to ensure * we are within USB Type-C Spec 1.4 tVconnON * * UnorientedDebugAccessory.SRC shall not drive Vconn */ if (IS_ENABLED(CONFIG_USBC_VCONN) && !TC_CHK_FLAG(port, TC_FLAGS_TS_DTS_PARTNER)) set_vconn(port, 1); /* Enable VBUS */ if (tc_src_power_on(port)) { /* Stop sourcing Vconn if Vbus failed */ if (IS_ENABLED(CONFIG_USBC_VCONN)) set_vconn(port, 0); if (IS_ENABLED(CONFIG_USBC_SS_MUX)) usb_mux_set(port, USB_PD_MUX_NONE, USB_SWITCH_DISCONNECT, tc[port].polarity); } tc_enable_pd(port, 0); pd_timer_enable(port, TC_TIMER_TIMEOUT, MAX(PD_POWER_SUPPLY_TURN_ON_DELAY, PD_T_VCONN_STABLE)); } } else { /* * Set up CC's, Vconn, and ADD before Vbus, as per * Figure 4-24. DRP Initialization and Connection * Detection in TCPCI r2 v1.2 specification. */ /* Get connector orientation */ tcpm_get_cc(port, &cc1, &cc2); tc[port].polarity = get_src_polarity(cc1, cc2); pd_set_polarity(port, tc[port].polarity); /* Attached.SRC - enable AutoDischargeDisconnect */ tcpm_enable_auto_discharge_disconnect(port, 1); /* Apply Rp */ typec_update_cc(port); /* * Initial data role for sink is DFP * This also sets the usb mux */ tc_set_data_role(port, PD_ROLE_DFP); /* * Start sourcing Vconn before Vbus to ensure * we are within USB Type-C Spec 1.4 tVconnON * * UnorientedDebugAccessory.SRC shall not drive Vconn */ if (IS_ENABLED(CONFIG_USBC_VCONN) && !TC_CHK_FLAG(port, TC_FLAGS_TS_DTS_PARTNER)) set_vconn(port, 1); /* Enable VBUS */ if (tc_src_power_on(port)) { /* Stop sourcing Vconn if Vbus failed */ if (IS_ENABLED(CONFIG_USBC_VCONN)) set_vconn(port, 0); if (IS_ENABLED(CONFIG_USBC_SS_MUX)) usb_mux_set(port, USB_PD_MUX_NONE, USB_SWITCH_DISCONNECT, tc[port].polarity); } } /* Inform PPC and OCP module that a sink is connected. */ tc_set_partner_role(port, PPC_DEV_SNK); /* Initialize type-C supplier to seed the charge manger */ if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) typec_set_input_current_limit(port, 0, 0); /* * Only notify if we're not performing a power role swap. During a * power role swap, the port partner is not disconnecting/connecting. */ if (!TC_CHK_FLAG(port, TC_FLAGS_PR_SWAP_IN_PROGRESS)) { hook_notify(HOOK_USB_PD_CONNECT); } if (TC_CHK_FLAG(port, TC_FLAGS_TS_DTS_PARTNER)) { tcpm_debug_accessory(port, 1); set_ccd_mode(port, 1); } /* * Some TCPCs require time to correctly return CC status after * changing the ROLE_CONTROL register. Due to that, we have to ignore * CC_NONE state until PD_T_SRC_DISCONNECT delay has elapsed. * From the "Universal Serial Bus Type-C Cable and Connector * Specification" Release 2.0 paragraph 4.5.2.2.9.2: * The Source shall detect the SRC.Open state within tSRCDisconnect, * but should detect it as quickly as possible */ pd_timer_enable(port, TC_TIMER_CC_DEBOUNCE, PD_T_SRC_DISCONNECT); } static void tc_attached_src_run(const int port) { enum tcpc_cc_voltage_status cc1, cc2; /* Check for connection */ tcpm_get_cc(port, &cc1, &cc2); if (polarity_rm_dts(tc[port].polarity)) cc1 = cc2; if (cc1 == TYPEC_CC_VOLT_OPEN) tc[port].cc_state = PD_CC_NONE; else tc[port].cc_state = PD_CC_UFP_ATTACHED; /* * When the SRC.Open state is detected on the monitored CC pin, a DRP * shall transition to Unattached.SNK unless it strongly prefers the * Source role. In that case, it shall transition to TryWait.SNK. * This transition to TryWait.SNK is needed so that two devices that * both prefer the Source role do not loop endlessly between Source * and Sink. In other words, a DRP that would enter Try.SRC from * AttachWait.SNK shall enter TryWait.SNK for a Sink detach from * Attached.SRC. */ if (tc[port].cc_state == PD_CC_NONE && pd_timer_is_expired(port, TC_TIMER_CC_DEBOUNCE)) { bool tryWait; enum usb_tc_state new_tc_state = TC_UNATTACHED_SNK; if (IS_ENABLED(CONFIG_USB_PD_TRY_SRC)) tryWait = is_try_src_enabled(port) && !TC_CHK_FLAG(port, TC_FLAGS_TS_DTS_PARTNER); if (drp_state[port] == PD_DRP_FORCE_SOURCE) new_tc_state = TC_UNATTACHED_SRC; else if(IS_ENABLED(CONFIG_USB_PD_TRY_SRC)) new_tc_state = tryWait ? TC_TRY_WAIT_SNK : TC_UNATTACHED_SNK; set_state_tc(port, new_tc_state); return; } #ifdef CONFIG_USB_PE_SM /* * Enable PD communications after power supply has fully * turned on */ if (pd_timer_is_expired(port, TC_TIMER_TIMEOUT)) { tc_enable_pd(port, 1); pd_timer_disable(port, TC_TIMER_TIMEOUT); } if (!tc_get_pd_enabled(port)) return; /* * Handle Hard Reset from Policy Engine */ if (TC_CHK_FLAG(port, TC_FLAGS_HARD_RESET_REQUESTED)) { /* Ignoring Hard Resets while the power supply is resetting.*/ if (!pd_timer_is_disabled(port, TC_TIMER_TIMEOUT) && !pd_timer_is_expired(port, TC_TIMER_TIMEOUT)) return; if (tc_perform_src_hard_reset(port)) TC_CLR_FLAG(port, TC_FLAGS_HARD_RESET_REQUESTED); return; } /* * PD swap commands */ if (tc_get_pd_enabled(port) && prl_is_running(port)) { /* * Power Role Swap Request */ if (TC_CHK_FLAG(port, TC_FLAGS_REQUEST_PR_SWAP)) { /* Clear TC_FLAGS_REQUEST_PR_SWAP on exit */ return set_state_tc(port, TC_ATTACHED_SNK); } /* * Data Role Swap Request */ if (TC_CHK_FLAG(port, TC_FLAGS_REQUEST_DR_SWAP)) { TC_CLR_FLAG(port, TC_FLAGS_REQUEST_DR_SWAP); /* Perform Data Role Swap */ tc_set_data_role(port, tc[port].data_role == PD_ROLE_DFP ? PD_ROLE_UFP : PD_ROLE_DFP); } /* * Vconn Swap Request * UnorientedDebugAccessory.SRC shall not drive Vconn */ if (IS_ENABLED(CONFIG_USBC_VCONN) && !TC_CHK_FLAG(port, TC_FLAGS_TS_DTS_PARTNER)) { /* * VCONN Swap Request */ if (TC_CHK_FLAG(port, TC_FLAGS_REQUEST_VC_SWAP_ON)) { TC_CLR_FLAG(port, TC_FLAGS_REQUEST_VC_SWAP_ON); set_vconn(port, 1); pe_vconn_swap_complete(port); } else if (TC_CHK_FLAG(port, TC_FLAGS_REQUEST_VC_SWAP_OFF)) { TC_CLR_FLAG(port, TC_FLAGS_REQUEST_VC_SWAP_OFF); set_vconn(port, 0); pe_vconn_swap_complete(port); } } /* * A DRP that supports Charge-Through VCONN-Powered USB Devices * shall transition to CTUnattached.SNK if the connected device * identifies itself as a Charge-Through VCONN-Powered USB * Device in its Discover Identity Command response. */ /* * A DRP that supports Charge-Through VCONN-Powered USB Devices * shall transition to CTUnattached.SNK if the connected device * identifies itself as a Charge-Through VCONN-Powered USB * Device in its Discover Identity Command response. * * If it detects that it is connected to a VCONN-Powered USB * Device, the port may remove VBUS and discharge it to * vSafe0V, while continuing to remain in this state with VCONN * applied. */ if (!TC_CHK_FLAG(port, TC_FLAGS_TS_DTS_PARTNER) && TC_CHK_FLAG(port, TC_FLAGS_CTVPD_DETECTED)) { set_state_tc(port, TC_CT_UNATTACHED_SNK); } } #endif if (TC_CHK_FLAG(port, TC_FLAGS_UPDATE_CURRENT)) { TC_CLR_FLAG(port, TC_FLAGS_UPDATE_CURRENT); typec_set_source_current_limit(port, tc[port].select_current_limit_rp); pd_update_contract(port); /* Update Rp if no contract is present */ if (!IS_ENABLED(CONFIG_USB_PE_SM) || !pe_is_explicit_contract(port)) typec_update_cc(port); } } static void tc_attached_src_exit(const int port) { /* * A port shall cease to supply VBUS within tVBUSOFF of exiting * Attached.SRC. */ tc_src_power_off(port); if (!TC_CHK_FLAG(port, TC_FLAGS_REQUEST_PR_SWAP)) { /* Attached.SRC exit - disable AutoDischargeDisconnect */ tcpm_enable_auto_discharge_disconnect(port, 0); /* * Disable VCONN if not power role swapping and * a CTVPD was not detected */ if (TC_CHK_FLAG(port, TC_FLAGS_VCONN_ON) && !TC_CHK_FLAG(port, TC_FLAGS_CTVPD_DETECTED)) set_vconn(port, 0); } /* Clear CTVPD detected after checking for Vconn */ TC_CLR_FLAG(port, TC_FLAGS_CTVPD_DETECTED); /* Clear PR swap flag after checking for Vconn */ TC_CLR_FLAG(port, TC_FLAGS_REQUEST_PR_SWAP); if (TC_CHK_FLAG(port, TC_FLAGS_TS_DTS_PARTNER)) tcpm_debug_detach(port); pd_timer_disable(port, TC_TIMER_CC_DEBOUNCE); pd_timer_disable(port, TC_TIMER_TIMEOUT); } static __maybe_unused void check_drp_connection(const int port) { enum pd_drp_next_states next_state; enum tcpc_cc_voltage_status cc1, cc2; TC_CLR_FLAG(port, TC_FLAGS_CHECK_CONNECTION); /* Check for connection */ tcpm_get_cc(port, &cc1, &cc2); tc[port].drp_sink_time = get_time().val; /* Get the next toggle state */ next_state = drp_auto_toggle_next_state(&tc[port].drp_sink_time, tc[port].power_role, drp_state[port], cc1, cc2, tcpm_auto_toggle_supported(port)); if (next_state == DRP_TC_DEFAULT) next_state = (PD_ROLE_DEFAULT(port) == PD_ROLE_SOURCE) ? DRP_TC_UNATTACHED_SRC : DRP_TC_UNATTACHED_SNK; switch (next_state) { case DRP_TC_UNATTACHED_SNK: set_state_tc(port, TC_UNATTACHED_SNK); break; case DRP_TC_ATTACHED_WAIT_SNK: set_state_tc(port, TC_ATTACH_WAIT_SNK); break; case DRP_TC_UNATTACHED_SRC: set_state_tc(port, TC_UNATTACHED_SRC); break; case DRP_TC_ATTACHED_WAIT_SRC: set_state_tc(port, TC_ATTACH_WAIT_SRC); break; #ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE case DRP_TC_DRP_AUTO_TOGGLE: set_state_tc(port, TC_DRP_AUTO_TOGGLE); break; #endif default: CPRINTS("C%d: Error: DRP next state %d", port, next_state); break; } } /** * DrpAutoToggle */ __maybe_unused static void tc_drp_auto_toggle_entry(const int port) { if (!IS_ENABLED(CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE)) assert(0); print_current_state(port); /* * We need to ensure that we are waiting in the previous Rd or Rp state * for the minimum of DRP SNK or SRC so the first toggle cause by * transition into auto toggle doesn't violate spec timing. */ pd_timer_enable(port, TC_TIMER_TIMEOUT, MAX(PD_T_DRP_SNK, PD_T_DRP_SRC)); } __maybe_unused static void tc_drp_auto_toggle_run(const int port) { if (!IS_ENABLED(CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE)) assert(0); /* * A timer is running, but if a connection comes in while waiting * then allow that to take higher priority. */ if (TC_CHK_FLAG(port, TC_FLAGS_CHECK_CONNECTION)) check_drp_connection(port); else if (!pd_timer_is_disabled(port, TC_TIMER_TIMEOUT)) { if (!pd_timer_is_expired(port, TC_TIMER_TIMEOUT)) return; pd_timer_disable(port, TC_TIMER_TIMEOUT); tcpm_enable_drp_toggle(port); if (IS_ENABLED(CONFIG_USB_PD_TCPC_LOW_POWER)) { set_state_tc(port, TC_LOW_POWER_MODE); } } } __maybe_unused static void tc_drp_auto_toggle_exit(const int port) { pd_timer_disable(port, TC_TIMER_TIMEOUT); } __maybe_unused static void tc_low_power_mode_entry(const int port) { if (!IS_ENABLED(CONFIG_USB_PD_TCPC_LOW_POWER)) assert(0); print_current_state(port); pd_timer_enable(port, TC_TIMER_LOW_POWER_TIME, PD_LPM_DEBOUNCE_US); } __maybe_unused static void tc_low_power_mode_run(const int port) { if (!IS_ENABLED(CONFIG_USB_PD_TCPC_LOW_POWER)) assert(0); if (TC_CHK_FLAG(port, TC_FLAGS_CHECK_CONNECTION)) { tc_start_event_loop(port); if (pd_timer_is_disabled(port, TC_TIMER_LOW_POWER_EXIT_TIME)) { pd_timer_enable(port, TC_TIMER_LOW_POWER_EXIT_TIME, PD_LPM_EXIT_DEBOUNCE_US); } else if (pd_timer_is_expired(port, TC_TIMER_LOW_POWER_EXIT_TIME)) { CPRINTS("C%d: Exit Low Power Mode", port); check_drp_connection(port); } return; } if (tc[port].tasks_preventing_lpm) pd_timer_enable(port, TC_TIMER_LOW_POWER_TIME, PD_LPM_DEBOUNCE_US); if (pd_timer_is_expired(port, TC_TIMER_LOW_POWER_TIME)) { CPRINTS("C%d: TCPC Enter Low Power Mode", port); TC_SET_FLAG(port, TC_FLAGS_LPM_ENGAGED); TC_SET_FLAG(port, TC_FLAGS_LPM_TRANSITION); tcpm_enter_low_power_mode(port); TC_CLR_FLAG(port, TC_FLAGS_LPM_TRANSITION); tc_pause_event_loop(port); pd_timer_disable(port, TC_TIMER_LOW_POWER_EXIT_TIME); } } __maybe_unused static void tc_low_power_mode_exit(const int port) { pd_timer_disable(port, TC_TIMER_LOW_POWER_TIME); pd_timer_disable(port, TC_TIMER_LOW_POWER_EXIT_TIME); } /** * Try.SRC * * Super State Entry Actions: * Vconn Off * Place Rp on CC * Set power role to SOURCE */ #ifdef CONFIG_USB_PD_TRY_SRC static void tc_try_src_entry(const int port) { print_current_state(port); tc[port].cc_state = PD_CC_UNSET; pd_timer_enable(port, TC_TIMER_TRY_WAIT_DEBOUNCE, PD_T_DRP_TRY); pd_timer_enable(port, TC_TIMER_TIMEOUT, PD_T_TRY_TIMEOUT); /* * We are a SNK but would prefer to be a SRC. Set the pull to * indicate we want to be a SRC and looking for a SNK. * * Both CC1 and CC2 pins shall be independently terminated to * ground through Rp. */ typec_select_pull(port, TYPEC_CC_RP); typec_select_src_current_limit_rp(port, typec_get_default_current_limit_rp(port)); /* Apply Rp */ typec_update_cc(port); } static void tc_try_src_run(const int port) { enum tcpc_cc_voltage_status cc1, cc2; enum pd_cc_states new_cc_state; /* Check for connection */ tcpm_get_cc(port, &cc1, &cc2); if ((cc1 == TYPEC_CC_VOLT_RD && cc2 != TYPEC_CC_VOLT_RD) || (cc1 != TYPEC_CC_VOLT_RD && cc2 == TYPEC_CC_VOLT_RD)) new_cc_state = PD_CC_UFP_ATTACHED; else new_cc_state = PD_CC_NONE; /* Debounce the cc state */ if (new_cc_state != tc[port].cc_state) { tc[port].cc_state = new_cc_state; pd_timer_enable(port, TC_TIMER_CC_DEBOUNCE, PD_T_CC_DEBOUNCE); } /* * The port shall transition to Attached.SRC when the SRC.Rd state is * detected on exactly one of the CC1 or CC2 pins for at least * tTryCCDebounce. */ if (new_cc_state == PD_CC_UFP_ATTACHED && pd_timer_is_expired(port, TC_TIMER_CC_DEBOUNCE)) set_state_tc(port, TC_ATTACHED_SRC); /* * The port shall transition to TryWait.SNK after tDRPTry and the * SRC.Rd state has not been detected and VBUS is within vSafe0V, * or after tTryTimeout and the SRC.Rd state has not been detected. */ if (new_cc_state == PD_CC_NONE) { if ((pd_timer_is_expired(port, TC_TIMER_TRY_WAIT_DEBOUNCE) && pd_check_vbus_level(port, VBUS_SAFE0V)) || pd_timer_is_expired(port, TC_TIMER_TIMEOUT)) { set_state_tc(port, TC_TRY_WAIT_SNK); } } } static void tc_try_src_exit(const int port) { pd_timer_disable(port, TC_TIMER_CC_DEBOUNCE); pd_timer_disable(port, TC_TIMER_TIMEOUT); pd_timer_disable(port, TC_TIMER_TRY_WAIT_DEBOUNCE); } /** * TryWait.SNK * * Super State Entry Actions: * Vconn Off * Place Rd on CC * Set power role to SINK */ static void tc_try_wait_snk_entry(const int port) { print_current_state(port); tc_enable_pd(port, 0); tc[port].cc_state = PD_CC_UNSET; pd_timer_enable(port, TC_TIMER_TRY_WAIT_DEBOUNCE, PD_T_CC_DEBOUNCE); /* * We were a SNK, tried to be a SRC and it didn't work out. Try to * go back to being a SNK. Set the pull to indicate we want to be * a SNK and looking for a SRC. * * Both CC1 and CC2 pins shall be independently terminated to * ground through Rd. */ typec_select_pull(port, TYPEC_CC_RD); /* Apply Rd */ typec_update_cc(port); } static void tc_try_wait_snk_run(const int port) { enum tcpc_cc_voltage_status cc1, cc2; enum pd_cc_states new_cc_state; /* Check for connection */ tcpm_get_cc(port, &cc1, &cc2); /* We only care about CCs being open */ if (cc1 == TYPEC_CC_VOLT_OPEN && cc2 == TYPEC_CC_VOLT_OPEN) new_cc_state = PD_CC_NONE; else new_cc_state = PD_CC_UNSET; /* Debounce the cc state */ if (new_cc_state != tc[port].cc_state) { tc[port].cc_state = new_cc_state; pd_timer_enable(port, TC_TIMER_PD_DEBOUNCE, PD_T_PD_DEBOUNCE); } /* * The port shall transition to Unattached.SNK when the state of both * of the CC1 and CC2 pins is SNK.Open for at least tPDDebounce. */ if (new_cc_state == PD_CC_NONE && pd_timer_is_expired(port, TC_TIMER_PD_DEBOUNCE)) { set_state_tc(port, TC_UNATTACHED_SNK); return; } /* * The port shall transition to Attached.SNK after tCCDebounce if or * when VBUS is detected. */ if (pd_timer_is_expired(port, TC_TIMER_TRY_WAIT_DEBOUNCE) && pd_is_vbus_present(port)) set_state_tc(port, TC_ATTACHED_SNK); } static void tc_try_wait_snk_exit(const int port) { pd_timer_disable(port, TC_TIMER_PD_DEBOUNCE); pd_timer_disable(port, TC_TIMER_TRY_WAIT_DEBOUNCE); } #endif /* * CTUnattached.SNK */ __maybe_unused static void tc_ct_unattached_snk_entry(int port) { if (!IS_ENABLED(CONFIG_USB_PE_SM)) assert(0); print_current_state(port); /* * Both CC1 and CC2 pins shall be independently terminated to * ground through Rd. */ typec_select_pull(port, TYPEC_CC_RD); typec_update_cc(port); tc[port].cc_state = PD_CC_UNSET; /* Set power role to sink */ tc_set_power_role(port, PD_ROLE_SINK); tcpm_set_msg_header(port, tc[port].power_role, tc[port].data_role); /* * The policy engine is in the disabled state. Disable PD and * re-enable it */ tc_enable_pd(port, 0); pd_timer_enable(port, TC_TIMER_TIMEOUT, PD_POWER_SUPPLY_TURN_ON_DELAY); } __maybe_unused static void tc_ct_unattached_snk_run(int port) { enum tcpc_cc_voltage_status cc1; enum tcpc_cc_voltage_status cc2; enum pd_cc_states new_cc_state; if (!IS_ENABLED(CONFIG_USB_PE_SM)) assert(0); if (!pd_timer_is_disabled(port, TC_TIMER_TIMEOUT)) { if (pd_timer_is_expired(port, TC_TIMER_TIMEOUT)) { tc_enable_pd(port, 1); pd_timer_disable(port, TC_TIMER_TIMEOUT); } else { return; } } /* Wait until Protocol Layer is ready */ if (!prl_is_running(port)) return; /* * Hard Reset is sent when the PE layer is disabled due to a * CTVPD connection. */ if (TC_CHK_FLAG(port, TC_FLAGS_HARD_RESET_REQUESTED)) { TC_CLR_FLAG(port, TC_FLAGS_HARD_RESET_REQUESTED); /* Nothing to do. Just signal hard reset completion */ pe_ps_reset_complete(port); } /* Check for connection */ tcpm_get_cc(port, &cc1, &cc2); /* We only care about CCs being open */ if (cc1 == TYPEC_CC_VOLT_OPEN && cc2 == TYPEC_CC_VOLT_OPEN) new_cc_state = PD_CC_NONE; else new_cc_state = PD_CC_UNSET; /* Debounce the cc state */ if (new_cc_state != tc[port].cc_state) { tc[port].cc_state = new_cc_state; pd_timer_enable(port, TC_TIMER_CC_DEBOUNCE, PD_T_VPDDETACH); } /* * The port shall transition to Unattached.SNK if the state of * the CC pin is SNK.Open for tVPDDetach after VBUS is vSafe0V. */ else if (pd_timer_is_expired(port, TC_TIMER_CC_DEBOUNCE)) { if (new_cc_state == PD_CC_NONE && pd_check_vbus_level(port, VBUS_SAFE0V)) { set_state_tc(port, TC_UNATTACHED_SNK); return; } } /* * The port shall transition to CTAttached.SNK when VBUS is detected. */ if (pd_is_vbus_present(port)) set_state_tc(port, TC_CT_ATTACHED_SNK); } __maybe_unused static void tc_ct_unattached_snk_exit(int port) { pd_timer_disable(port, TC_TIMER_CC_DEBOUNCE); pd_timer_disable(port, TC_TIMER_TIMEOUT); } /** * CTAttached.SNK */ __maybe_unused static void tc_ct_attached_snk_entry(int port) { if (!IS_ENABLED(CONFIG_USB_PE_SM)) assert(0); print_current_state(port); /* The port shall reject a VCONN swap request. */ TC_SET_FLAG(port, TC_FLAGS_REJECT_VCONN_SWAP); } __maybe_unused static void tc_ct_attached_snk_run(int port) { if (!IS_ENABLED(CONFIG_USB_PE_SM)) assert(0); /* * Hard Reset is sent when the PE layer is disabled due to a * CTVPD connection. */ if (TC_CHK_FLAG(port, TC_FLAGS_HARD_RESET_REQUESTED)) { TC_CLR_FLAG(port, TC_FLAGS_HARD_RESET_REQUESTED); /* Nothing to do. Just signal hard reset completion */ pe_ps_reset_complete(port); } /* * A port that is not in the process of a USB PD Hard Reset shall * transition to CTUnattached.SNK within tSinkDisconnect when VBUS * falls below vSinkDisconnect */ if (pd_check_vbus_level(port, VBUS_REMOVED)) { set_state_tc(port, TC_CT_UNATTACHED_SNK); return; } /* * The port shall operate in one of the Sink Power Sub-States * and remain within the Sink Power Sub-States, until either VBUS is * removed or a USB PD contract is established with the source. */ if (!pe_is_explicit_contract(port)) sink_power_sub_states(port); } __maybe_unused static void tc_ct_attached_snk_exit(int port) { if (!IS_ENABLED(CONFIG_USB_PE_SM)) assert(0); /* Stop drawing power */ sink_stop_drawing_current(port); TC_CLR_FLAG(port, TC_FLAGS_REJECT_VCONN_SWAP); } /** * Super State CC_RD */ static void tc_cc_rd_entry(const int port) { /* Disable VCONN */ if (IS_ENABLED(CONFIG_USBC_VCONN)) set_vconn(port, 0); /* Set power role to sink */ tc_set_power_role(port, PD_ROLE_SINK); tcpm_set_msg_header(port, tc[port].power_role, tc[port].data_role); } /** * Super State CC_RP */ static void tc_cc_rp_entry(const int port) { /* Disable VCONN */ if (IS_ENABLED(CONFIG_USBC_VCONN)) set_vconn(port, 0); /* Set power role to source */ tc_set_power_role(port, PD_ROLE_SOURCE); tcpm_set_msg_header(port, tc[port].power_role, tc[port].data_role); } /** * Super State CC_OPEN */ static void tc_cc_open_entry(const int port) { /* Ensure we are not sourcing Vbus */ tc_src_power_off(port); /* Disable VCONN */ set_vconn(port, 0); /* * Ensure we disable discharging before setting CC lines to open. * If we were sourcing above, then we already drained Vbus. If partner * is sourcing Vbus they will drain Vbus if they are PD-capable. This * should only be done if a battery is present as a batteryless * device will brown out when AutoDischargeDisconnect is disabled and * we do not want this to happen until the set_cc open/open to make * sure the TCPC has managed its internal states for disconnecting * the only source of power it has. */ if (battery_is_present()) tcpm_enable_auto_discharge_disconnect(port, 0); /* * We may brown out after applying CC open, so flush console first. * Console flush can take a long time, so if we aren't in danger of * browning out, don't do it so we can meet certain compliance timing * requirements. */ CPRINTS("C%d: Applying CC Open!", port); if (!battery_is_present()) cflush(); /* Remove terminations from CC */ typec_select_pull(port, TYPEC_CC_OPEN); typec_update_cc(port); tc_set_partner_role(port, PPC_DEV_DISCONNECTED); tc_detached(port); } void tc_set_debug_level(enum debug_level debug_level) { #ifndef CONFIG_USB_PD_DEBUG_LEVEL tc_debug_level = debug_level; #endif } void tc_usb_firmware_fw_update_limited_run(int port) { TC_SET_FLAG(port, TC_FLAGS_USB_RETIMER_FW_UPDATE_LTD_RUN); task_wake(PD_PORT_TO_TASK_ID(port)); } void tc_usb_firmware_fw_update_run(int port) { TC_SET_FLAG(port, TC_FLAGS_USB_RETIMER_FW_UPDATE_RUN); task_wake(PD_PORT_TO_TASK_ID(port)); } void tc_run(const int port) { /* * If pd_set_suspend set TC_FLAGS_REQUEST_SUSPEND, go directly to * TC_DISABLED. */ if (get_state_tc(port) != TC_DISABLED && TC_CHK_FLAG(port, TC_FLAGS_REQUEST_SUSPEND)) { /* Invalidate a contract, if there is one */ if (IS_ENABLED(CONFIG_USB_PE_SM)) pe_invalidate_explicit_contract(port); set_state_tc(port, TC_DISABLED); } /* If error recovery has been requested, transition now */ if (TC_CHK_FLAG(port, TC_FLAGS_REQUEST_ERROR_RECOVERY)) { if (IS_ENABLED(CONFIG_USB_PE_SM)) pe_invalidate_explicit_contract(port); set_state_tc(port, TC_ERROR_RECOVERY); } if (IS_ENABLED(CONFIG_USBC_RETIMER_FW_UPDATE)) { if (TC_CHK_FLAG(port, TC_FLAGS_USB_RETIMER_FW_UPDATE_RUN)) { TC_CLR_FLAG(port, TC_FLAGS_USB_RETIMER_FW_UPDATE_RUN); usb_retimer_fw_update_process_op_cb(port); } } run_state(port, &tc[port].ctx); } static void pd_chipset_resume(void) { int i; for (i = 0; i < CONFIG_USB_PD_PORT_MAX_COUNT; i++) { if(IS_ENABLED(CONFIG_USB_PE_SM)) pd_resume_check_pr_swap_needed(i); pd_set_dual_role_and_event(i, PD_DRP_TOGGLE_ON, PD_EVENT_UPDATE_DUAL_ROLE | PD_EVENT_POWER_STATE_CHANGE); } CPRINTS("PD:S3->S0"); } DECLARE_HOOK(HOOK_CHIPSET_RESUME, pd_chipset_resume, HOOK_PRIO_DEFAULT); static void pd_chipset_suspend(void) { int i; for (i = 0; i < CONFIG_USB_PD_PORT_MAX_COUNT; i++) { pd_set_dual_role_and_event(i, pd_get_drp_state_in_suspend(), PD_EVENT_UPDATE_DUAL_ROLE | PD_EVENT_POWER_STATE_CHANGE); } CPRINTS("PD:S0->S3"); } DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, pd_chipset_suspend, HOOK_PRIO_DEFAULT); static void pd_chipset_reset(void) { int i; if (!IS_ENABLED(CONFIG_USB_PE_SM)) return; for (i = 0; i < board_get_usb_pd_port_count(); i++) { enum tcpci_msg_type tx; /* Do not notify the AP of irrelevant past Hard Resets. */ pd_clear_events(i, PD_STATUS_EVENT_HARD_RESET); /* * Re-set events for SOP and SOP' discovery complete so the * kernel knows to consume discovery information for them. */ for (tx = TCPCI_MSG_SOP; tx <= TCPCI_MSG_SOP_PRIME; tx++) { if (pd_get_identity_discovery(i, tx) != PD_DISC_NEEDED && pd_get_svids_discovery(i, tx) != PD_DISC_NEEDED && pd_get_modes_discovery(i, tx) != PD_DISC_NEEDED) pd_notify_event(i, tx == TCPCI_MSG_SOP ? PD_STATUS_EVENT_SOP_DISC_DONE : PD_STATUS_EVENT_SOP_PRIME_DISC_DONE); } /* Exit mode so AP can enter mode again after reset */ if (IS_ENABLED(CONFIG_USB_PD_REQUIRE_AP_MODE_ENTRY)) dpm_set_mode_exit_request(i); } } DECLARE_HOOK(HOOK_CHIPSET_RESET, pd_chipset_reset, HOOK_PRIO_DEFAULT); static void pd_chipset_startup(void) { int i; for (i = 0; i < CONFIG_USB_PD_PORT_MAX_COUNT; i++) { TC_SET_FLAG(i, TC_FLAGS_UPDATE_USB_MUX); pd_set_dual_role_and_event(i, pd_get_drp_state_in_suspend(), PD_EVENT_UPDATE_DUAL_ROLE | PD_EVENT_POWER_STATE_CHANGE); /* * Request port discovery to restore any * alt modes. * TODO(b/158042116): Do not start port discovery if there * is an existing connection. */ if (IS_ENABLED(CONFIG_USB_PE_SM)) pd_dpm_request(i, DPM_REQUEST_PORT_DISCOVERY); } CPRINTS("PD:S5->S3"); } DECLARE_HOOK(HOOK_CHIPSET_STARTUP, pd_chipset_startup, HOOK_PRIO_DEFAULT); static void pd_chipset_shutdown(void) { int i; for (i = 0; i < CONFIG_USB_PD_PORT_MAX_COUNT; i++) { TC_SET_FLAG(i, TC_FLAGS_UPDATE_USB_MUX); pd_set_dual_role_and_event(i, PD_DRP_FORCE_SINK, PD_EVENT_UPDATE_DUAL_ROLE | PD_EVENT_POWER_STATE_CHANGE); } CPRINTS("PD:S3->S5"); } DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, pd_chipset_shutdown, HOOK_PRIO_DEFAULT); static void pd_set_power_change(void) { int i; for (i = 0; i < CONFIG_USB_PD_PORT_MAX_COUNT; i++) { task_set_event(PD_PORT_TO_TASK_ID(i), PD_EVENT_POWER_STATE_CHANGE); } } DECLARE_DEFERRED(pd_set_power_change); static void pd_chipset_hard_off(void) { /* * Wait 1 second to check our Vconn sourcing status, as the power rails * which were supporting it may take some time to change after entering * G3. */ hook_call_deferred(&pd_set_power_change_data, 1 * SECOND); } DECLARE_HOOK(HOOK_CHIPSET_HARD_OFF, pd_chipset_hard_off, HOOK_PRIO_DEFAULT); /* * Type-C State Hierarchy (Sub-States are listed inside the boxes) * * |TC_CC_RD --------------| |TC_CC_RP ------------------------| * | | | | * | TC_UNATTACHED_SNK | | TC_UNATTACHED_SRC | * | TC_ATTACH_WAIT_SNK | | TC_ATTACH_WAIT_SRC | * | TC_TRY_WAIT_SNK | | TC_TRY_SRC | * |-----------------------| |---------------------------------| * * |TC_CC_OPEN -----------| * | | * | TC_DISABLED | * | TC_ERROR_RECOVERY | * |----------------------| * * TC_ATTACHED_SNK TC_ATTACHED_SRC TC_DRP_AUTO_TOGGLE TC_LOW_POWER_MODE * */ static __const_data const struct usb_state tc_states[] = { /* Super States */ [TC_CC_OPEN] = { .entry = tc_cc_open_entry, }, [TC_CC_RD] = { .entry = tc_cc_rd_entry, }, [TC_CC_RP] = { .entry = tc_cc_rp_entry, }, /* Normal States */ [TC_DISABLED] = { .entry = tc_disabled_entry, .run = tc_disabled_run, .exit = tc_disabled_exit, .parent = &tc_states[TC_CC_OPEN], }, [TC_ERROR_RECOVERY] = { .entry = tc_error_recovery_entry, .run = tc_error_recovery_run, .exit = tc_error_recovery_exit, .parent = &tc_states[TC_CC_OPEN], }, [TC_UNATTACHED_SNK] = { .entry = tc_unattached_snk_entry, .run = tc_unattached_snk_run, .exit = tc_unattached_snk_exit, .parent = &tc_states[TC_CC_RD], }, [TC_ATTACH_WAIT_SNK] = { .entry = tc_attach_wait_snk_entry, .run = tc_attach_wait_snk_run, .exit = tc_attach_wait_snk_exit, .parent = &tc_states[TC_CC_RD], }, [TC_ATTACHED_SNK] = { .entry = tc_attached_snk_entry, .run = tc_attached_snk_run, .exit = tc_attached_snk_exit, }, [TC_UNATTACHED_SRC] = { .entry = tc_unattached_src_entry, .run = tc_unattached_src_run, .exit = tc_unattached_src_exit, .parent = &tc_states[TC_CC_RP], }, [TC_ATTACH_WAIT_SRC] = { .entry = tc_attach_wait_src_entry, .run = tc_attach_wait_src_run, .exit = tc_attach_wait_src_exit, .parent = &tc_states[TC_CC_RP], }, [TC_ATTACHED_SRC] = { .entry = tc_attached_src_entry, .run = tc_attached_src_run, .exit = tc_attached_src_exit, }, #ifdef CONFIG_USB_PD_TRY_SRC [TC_TRY_SRC] = { .entry = tc_try_src_entry, .run = tc_try_src_run, .exit = tc_try_src_exit, .parent = &tc_states[TC_CC_RP], }, [TC_TRY_WAIT_SNK] = { .entry = tc_try_wait_snk_entry, .run = tc_try_wait_snk_run, .exit = tc_try_wait_snk_exit, .parent = &tc_states[TC_CC_RD], }, #endif /* CONFIG_USB_PD_TRY_SRC */ #ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE [TC_DRP_AUTO_TOGGLE] = { .entry = tc_drp_auto_toggle_entry, .run = tc_drp_auto_toggle_run, .exit = tc_drp_auto_toggle_exit, }, #endif /* CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE */ #ifdef CONFIG_USB_PD_TCPC_LOW_POWER [TC_LOW_POWER_MODE] = { .entry = tc_low_power_mode_entry, .run = tc_low_power_mode_run, .exit = tc_low_power_mode_exit, }, #endif /* CONFIG_USB_PD_TCPC_LOW_POWER */ #ifdef CONFIG_USB_PE_SM [TC_CT_UNATTACHED_SNK] = { .entry = tc_ct_unattached_snk_entry, .run = tc_ct_unattached_snk_run, .exit = tc_ct_unattached_snk_exit, }, [TC_CT_ATTACHED_SNK] = { .entry = tc_ct_attached_snk_entry, .run = tc_ct_attached_snk_run, .exit = tc_ct_attached_snk_exit, }, #endif }; #if defined(TEST_BUILD) && defined(USB_PD_DEBUG_LABELS) const struct test_sm_data test_tc_sm_data[] = { { .base = tc_states, .size = ARRAY_SIZE(tc_states), .names = tc_state_names, .names_size = ARRAY_SIZE(tc_state_names), }, }; const int test_tc_sm_data_size = ARRAY_SIZE(test_tc_sm_data); #endif