diff options
Diffstat (limited to 'common/usbc/usb_pe_drp_sm.c')
-rw-r--r-- | common/usbc/usb_pe_drp_sm.c | 7486 |
1 files changed, 0 insertions, 7486 deletions
diff --git a/common/usbc/usb_pe_drp_sm.c b/common/usbc/usb_pe_drp_sm.c deleted file mode 100644 index 096f689b0a..0000000000 --- a/common/usbc/usb_pe_drp_sm.c +++ /dev/null @@ -1,7486 +0,0 @@ -/* 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 "atomic.h" -#include "battery.h" -#include "battery_smart.h" -#include "charge_manager.h" -#include "charge_state.h" -#include "common.h" -#include "console.h" -#include "dps.h" -#include "driver/tcpm/tcpm.h" -#include "ec_commands.h" -#include "hooks.h" -#include "host_command.h" -#include "stdbool.h" -#include "system.h" -#include "task.h" -#include "tcpm/tcpm.h" -#include "util.h" -#include "usb_common.h" -#include "usb_dp_alt_mode.h" -#include "usb_mode.h" -#include "usb_pd_dpm.h" -#include "usb_pd_policy.h" -#include "usb_pd.h" -#include "usb_pd_tcpm.h" -#include "usb_pd_timer.h" -#include "usb_pe_sm.h" -#include "usb_tbt_alt_mode.h" -#include "usb_prl_sm.h" -#include "usb_tc_sm.h" -#include "usb_emsg.h" -#include "usb_sm.h" -#include "usbc_ppc.h" - -/* - * USB Policy Engine Sink / Source module - * - * Based on Revision 3.0, Version 1.2 of - * the USB Power Delivery Specification. - */ - -#ifdef CONFIG_COMMON_RUNTIME -#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) -#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) -#else -#define CPRINTF(format, args...) -#define CPRINTS(format, args...) -#endif - -#define CPRINTF_LX(x, format, args...) \ - do { \ - if (pe_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 (pe_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 PE_SET_FLAG(port, flag) atomic_or(&pe[port].flags, (flag)) -#define PE_CLR_FLAG(port, flag) atomic_clear_bits(&pe[port].flags, (flag)) -#define PE_CHK_FLAG(port, flag) (pe[port].flags & (flag)) - -/* - * These macros SET, CLEAR, and CHECK, a DPM (Device Policy Manager) - * Request. The Requests are listed in usb_pe_sm.h. - */ -#define PE_SET_DPM_REQUEST(port, req) atomic_or(&pe[port].dpm_request, (req)) -#define PE_CLR_DPM_REQUEST(port, req) \ - atomic_clear_bits(&pe[port].dpm_request, (req)) -#define PE_CHK_DPM_REQUEST(port, req) (pe[port].dpm_request & (req)) - -/* - * Policy Engine Layer Flags - * These are reproduced in test/usb_pe.h. If they change here, they must change - * there. - */ - -/* At least one successful PD communication packet received from port partner */ -#define PE_FLAGS_PD_CONNECTION BIT(0) -/* Accept message received from port partner */ -#define PE_FLAGS_ACCEPT BIT(1) -/* Power Supply Ready message received from port partner */ -#define PE_FLAGS_PS_READY BIT(2) -/* Protocol Error was determined based on error recovery current state */ -#define PE_FLAGS_PROTOCOL_ERROR BIT(3) -/* Set if we are in Modal Operation */ -#define PE_FLAGS_MODAL_OPERATION BIT(4) -/* A message we requested to be sent has been transmitted */ -#define PE_FLAGS_TX_COMPLETE BIT(5) -/* A message sent by a port partner has been received */ -#define PE_FLAGS_MSG_RECEIVED BIT(6) -/* A hard reset has been requested but has not been sent, not currently used */ -#define PE_FLAGS_HARD_RESET_PENDING BIT(7) -/* Port partner sent a Wait message. Wait before we resend our message */ -#define PE_FLAGS_WAIT BIT(8) -/* An explicit contract is in place with our port partner */ -#define PE_FLAGS_EXPLICIT_CONTRACT BIT(9) -/* Waiting for Sink Capabailities timed out. Used for retry error handling */ -#define PE_FLAGS_SNK_WAIT_CAP_TIMEOUT BIT(10) -/* Power Supply voltage/current transition timed out */ -#define PE_FLAGS_PS_TRANSITION_TIMEOUT BIT(11) -/* Flag to note current Atomic Message Sequence is interruptible */ -#define PE_FLAGS_INTERRUPTIBLE_AMS BIT(12) -/* Flag to note Power Supply reset has completed */ -#define PE_FLAGS_PS_RESET_COMPLETE BIT(13) -/* VCONN swap operation has completed */ -#define PE_FLAGS_VCONN_SWAP_COMPLETE BIT(14) -/* Flag to note no more setup VDMs (discovery, etc.) should be sent */ -#define PE_FLAGS_VDM_SETUP_DONE BIT(15) -/* Flag to note PR Swap just completed for Startup entry */ -#define PE_FLAGS_PR_SWAP_COMPLETE BIT(16) -/* Flag to note Port Discovery port partner replied with BUSY */ -#define PE_FLAGS_VDM_REQUEST_BUSY BIT(17) -/* Flag to note Port Discovery port partner replied with NAK */ -#define PE_FLAGS_VDM_REQUEST_NAKED BIT(18) -/* Flag to note FRS/PRS context in shared state machine path */ -#define PE_FLAGS_FAST_ROLE_SWAP_PATH BIT(19) -/* Flag to note if FRS listening is enabled */ -#define PE_FLAGS_FAST_ROLE_SWAP_ENABLED BIT(20) -/* Flag to note TCPC passed on FRS signal from port partner */ -#define PE_FLAGS_FAST_ROLE_SWAP_SIGNALED BIT(21) -/* TODO: POLICY decision: Triggers a DR SWAP attempt from UFP to DFP */ -#define PE_FLAGS_DR_SWAP_TO_DFP BIT(22) -/* - * TODO: POLICY decision - * Flag to trigger a message resend after receiving a WAIT from port partner - */ -#define PE_FLAGS_WAITING_PR_SWAP BIT(23) -/* FLAG is set when an AMS is initiated locally. ie. AP requested a PR_SWAP */ -#define PE_FLAGS_LOCALLY_INITIATED_AMS BIT(24) -/* Flag to note the first message sent in PE_SRC_READY and PE_SNK_READY */ -#define PE_FLAGS_FIRST_MSG BIT(25) -/* Flag to continue a VDM request if it was interrupted */ -#define PE_FLAGS_VDM_REQUEST_CONTINUE BIT(26) -/* TODO: POLICY decision: Triggers a Vconn SWAP attempt to on */ -#define PE_FLAGS_VCONN_SWAP_TO_ON BIT(27) -/* FLAG to track that VDM request to port partner timed out */ -#define PE_FLAGS_VDM_REQUEST_TIMEOUT BIT(28) -/* FLAG to note message was discarded due to incoming message */ -#define PE_FLAGS_MSG_DISCARDED BIT(29) -/* FLAG to note that hard reset can't be performed due to battery low */ -#define PE_FLAGS_SNK_WAITING_BATT BIT(30) - -/* Message flags which should not persist on returning to ready state */ -#define PE_FLAGS_READY_CLR (PE_FLAGS_LOCALLY_INITIATED_AMS \ - | PE_FLAGS_MSG_DISCARDED \ - | PE_FLAGS_VDM_REQUEST_TIMEOUT \ - | PE_FLAGS_INTERRUPTIBLE_AMS) - -/* - * Combination to check whether a reply to a message was received. Our message - * should have sent (i.e. not been discarded) and a partner message is ready to - * process. - * - * When chunking is disabled (ex. for PD 2.0), these flags will set - * on the same run cycle. With chunking, received message will take an - * additional cycle to be flagged. - */ -#define PE_CHK_REPLY(port) (PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED) && \ - !PE_CHK_FLAG(port, PE_FLAGS_MSG_DISCARDED)) - -/* 6.7.3 Hard Reset Counter */ -#define N_HARD_RESET_COUNT 2 - -/* 6.7.4 Capabilities Counter */ -#define N_CAPS_COUNT 25 - -/* 6.7.5 Discover Identity Counter */ -/* - * NOTE: The Protocol Layer tries to send a message 3 time before giving up, - * so a Discover Identity SOP' message will be sent 3*6 = 18 times (slightly - * less than spec maximum of 20). This counter applies only to cable plug - * discovery. - */ -#define N_DISCOVER_IDENTITY_COUNT 6 - -/* - * It is permitted to send SOP' Discover Identity messages before a PD contract - * is in place. However, this is only beneficial if the cable powers up quickly - * solely from VCONN. Limit the number of retries without a contract to - * ensure we attempt some cable discovery after a contract is in place. - */ -#define N_DISCOVER_IDENTITY_PRECONTRACT_LIMIT 2 - -/* - * Once this limit of SOP' Discover Identity messages has been set, downgrade - * to PD 2.0 in case the cable is non-compliant about GoodCRC-ing higher - * revisions. This limit should be higher than the precontract limit. - */ -#define N_DISCOVER_IDENTITY_PD3_0_LIMIT 4 - -/* - * tDiscoverIdentity is only defined while an explicit contract is in place, so - * extend the interval between retries pre-contract. - */ -#define PE_T_DISCOVER_IDENTITY_NO_CONTRACT (200*MSEC) - -/* - * Only VCONN source can communicate with the cable plug. Hence, try VCONN swap - * 3 times before giving up. - * - * Note: This is not a part of power delivery specification - */ -#define N_VCONN_SWAP_COUNT 3 - -/* - * Counter to track how many times to attempt SRC to SNK PR swaps before giving - * up. - * - * Note: This is not a part of power delivery specification - */ -#define N_SNK_SRC_PR_SWAP_COUNT 5 - -/* - * ChromeOS policy: - * For PD2.0, We must be DFP before sending Discover Identity message - * to the port partner. Attempt to DR SWAP from UFP to DFP - * N_DR_SWAP_ATTEMPT_COUNT times before giving up on sending a - * Discover Identity message. - */ -#define N_DR_SWAP_ATTEMPT_COUNT 5 - -#define TIMER_DISABLED 0xffffffffffffffff /* Unreachable time in future */ - -/* - * The time that we allow the port partner to send any messages after an - * explicit contract is established. 200ms was chosen somewhat arbitrarily as - * it should be long enough for sources to decide to send a message if they were - * going to, but not so long that a "low power charger connected" notification - * would be shown in the chrome OS UI. Setting t0o large a delay can cause - * problems if the PD discovery time exceeds 1s (tAMETimeout) - */ -#define SRC_SNK_READY_HOLD_OFF_US (200 * MSEC) - -/* - * Function pointer to a Structured Vendor Defined Message (SVDM) response - * function defined in the board's usb_pd_policy.c file. - */ -typedef int (*svdm_rsp_func)(int port, uint32_t *payload); - -/* List of all Policy Engine level states */ -enum usb_pe_state { - /* Super States */ - PE_PRS_FRS_SHARED, - PE_VDM_SEND_REQUEST, - - /* Normal States */ - PE_SRC_STARTUP, - PE_SRC_DISCOVERY, - PE_SRC_SEND_CAPABILITIES, - PE_SRC_NEGOTIATE_CAPABILITY, - PE_SRC_TRANSITION_SUPPLY, - PE_SRC_READY, - PE_SRC_DISABLED, - PE_SRC_CAPABILITY_RESPONSE, - PE_SRC_HARD_RESET, - PE_SRC_HARD_RESET_RECEIVED, - PE_SRC_TRANSITION_TO_DEFAULT, - PE_SNK_STARTUP, - PE_SNK_DISCOVERY, - PE_SNK_WAIT_FOR_CAPABILITIES, - PE_SNK_EVALUATE_CAPABILITY, - PE_SNK_SELECT_CAPABILITY, - PE_SNK_READY, - PE_SNK_HARD_RESET, - PE_SNK_TRANSITION_TO_DEFAULT, - PE_SNK_GIVE_SINK_CAP, - PE_SNK_GET_SOURCE_CAP, - PE_SNK_TRANSITION_SINK, - PE_SEND_SOFT_RESET, - PE_SOFT_RESET, - PE_SEND_NOT_SUPPORTED, - PE_SRC_PING, - PE_DRS_EVALUATE_SWAP, - PE_DRS_CHANGE, - PE_DRS_SEND_SWAP, - PE_PRS_SRC_SNK_EVALUATE_SWAP, - PE_PRS_SRC_SNK_TRANSITION_TO_OFF, - PE_PRS_SRC_SNK_ASSERT_RD, - PE_PRS_SRC_SNK_WAIT_SOURCE_ON, - PE_PRS_SRC_SNK_SEND_SWAP, - PE_PRS_SNK_SRC_EVALUATE_SWAP, - PE_PRS_SNK_SRC_TRANSITION_TO_OFF, - PE_PRS_SNK_SRC_ASSERT_RP, - PE_PRS_SNK_SRC_SOURCE_ON, - PE_PRS_SNK_SRC_SEND_SWAP, - PE_VCS_EVALUATE_SWAP, - PE_VCS_SEND_SWAP, - PE_VCS_WAIT_FOR_VCONN_SWAP, - PE_VCS_TURN_ON_VCONN_SWAP, - PE_VCS_TURN_OFF_VCONN_SWAP, - PE_VCS_SEND_PS_RDY_SWAP, - PE_VCS_CBL_SEND_SOFT_RESET, - PE_VDM_IDENTITY_REQUEST_CBL, - PE_INIT_PORT_VDM_IDENTITY_REQUEST, - PE_INIT_VDM_SVIDS_REQUEST, - PE_INIT_VDM_MODES_REQUEST, - PE_VDM_REQUEST_DPM, - PE_VDM_RESPONSE, - PE_HANDLE_CUSTOM_VDM_REQUEST, - PE_WAIT_FOR_ERROR_RECOVERY, - PE_BIST_TX, - PE_DEU_SEND_ENTER_USB, - PE_DR_GET_SINK_CAP, - PE_DR_SNK_GIVE_SOURCE_CAP, - PE_DR_SRC_GET_SOURCE_CAP, - - /* PD3.0 only states below here*/ - PE_FRS_SNK_SRC_START_AMS, - PE_GIVE_BATTERY_CAP, - PE_GIVE_BATTERY_STATUS, - PE_SEND_ALERT, - PE_SRC_CHUNK_RECEIVED, - PE_SNK_CHUNK_RECEIVED, - PE_VCS_FORCE_VCONN, -}; - -/* - * The result of a previously sent DPM request; used by PE_VDM_SEND_REQUEST to - * indicate to child states when they need to handle a response. - */ -enum vdm_response_result { - /* The parent state is still waiting for a response. */ - VDM_RESULT_WAITING, - /* - * The parent state parsed a message, but there is nothing for the child - * to handle, e.g. BUSY. - */ - VDM_RESULT_NO_ACTION, - /* The parent state processed an ACK response. */ - VDM_RESULT_ACK, - /* - * The parent state processed a NAK-like response (NAK, Not Supported, - * or response timeout. - */ - VDM_RESULT_NAK, -}; - -/* Forward declare the full list of states. This is indexed by usb_pe_state */ -static const struct usb_state pe_states[]; - -/* - * 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 - -/* List of human readable state names for console debugging */ -__maybe_unused static __const_data const char * const pe_state_names[] = { - /* Super States */ -#ifdef CONFIG_USB_PD_REV30 - [PE_PRS_FRS_SHARED] = "SS:PE_PRS_FRS_SHARED", -#endif - [PE_VDM_SEND_REQUEST] = "SS:PE_VDM_Send_Request", - - /* Normal States */ - [PE_SRC_STARTUP] = "PE_SRC_Startup", - [PE_SRC_DISCOVERY] = "PE_SRC_Discovery", - [PE_SRC_SEND_CAPABILITIES] = "PE_SRC_Send_Capabilities", - [PE_SRC_NEGOTIATE_CAPABILITY] = "PE_SRC_Negotiate_Capability", - [PE_SRC_TRANSITION_SUPPLY] = "PE_SRC_Transition_Supply", - [PE_SRC_READY] = "PE_SRC_Ready", - [PE_SRC_DISABLED] = "PE_SRC_Disabled", - [PE_SRC_CAPABILITY_RESPONSE] = "PE_SRC_Capability_Response", - [PE_SRC_HARD_RESET] = "PE_SRC_Hard_Reset", - [PE_SRC_HARD_RESET_RECEIVED] = "PE_SRC_Hard_Reset_Received", - [PE_SRC_TRANSITION_TO_DEFAULT] = "PE_SRC_Transition_to_default", - [PE_SNK_STARTUP] = "PE_SNK_Startup", - [PE_SNK_DISCOVERY] = "PE_SNK_Discovery", - [PE_SNK_WAIT_FOR_CAPABILITIES] = "PE_SNK_Wait_for_Capabilities", - [PE_SNK_EVALUATE_CAPABILITY] = "PE_SNK_Evaluate_Capability", - [PE_SNK_SELECT_CAPABILITY] = "PE_SNK_Select_Capability", - [PE_SNK_READY] = "PE_SNK_Ready", - [PE_SNK_HARD_RESET] = "PE_SNK_Hard_Reset", - [PE_SNK_TRANSITION_TO_DEFAULT] = "PE_SNK_Transition_to_default", - [PE_SNK_GIVE_SINK_CAP] = "PE_SNK_Give_Sink_Cap", - [PE_SNK_GET_SOURCE_CAP] = "PE_SNK_Get_Source_Cap", - [PE_SNK_TRANSITION_SINK] = "PE_SNK_Transition_Sink", - [PE_SEND_SOFT_RESET] = "PE_Send_Soft_Reset", - [PE_SOFT_RESET] = "PE_Soft_Reset", - [PE_SEND_NOT_SUPPORTED] = "PE_Send_Not_Supported", - [PE_SRC_PING] = "PE_SRC_Ping", - [PE_DRS_EVALUATE_SWAP] = "PE_DRS_Evaluate_Swap", - [PE_DRS_CHANGE] = "PE_DRS_Change", - [PE_DRS_SEND_SWAP] = "PE_DRS_Send_Swap", - [PE_PRS_SRC_SNK_EVALUATE_SWAP] = "PE_PRS_SRC_SNK_Evaluate_Swap", - [PE_PRS_SRC_SNK_TRANSITION_TO_OFF] = "PE_PRS_SRC_SNK_Transition_To_Off", - [PE_PRS_SRC_SNK_ASSERT_RD] = "PE_PRS_SRC_SNK_Assert_Rd", - [PE_PRS_SRC_SNK_WAIT_SOURCE_ON] = "PE_PRS_SRC_SNK_Wait_Source_On", - [PE_PRS_SRC_SNK_SEND_SWAP] = "PE_PRS_SRC_SNK_Send_Swap", - [PE_PRS_SNK_SRC_EVALUATE_SWAP] = "PE_PRS_SNK_SRC_Evaluate_Swap", - [PE_PRS_SNK_SRC_TRANSITION_TO_OFF] = "PE_PRS_SNK_SRC_Transition_To_Off", - [PE_PRS_SNK_SRC_ASSERT_RP] = "PE_PRS_SNK_SRC_Assert_Rp", - [PE_PRS_SNK_SRC_SOURCE_ON] = "PE_PRS_SNK_SRC_Source_On", - [PE_PRS_SNK_SRC_SEND_SWAP] = "PE_PRS_SNK_SRC_Send_Swap", -#ifdef CONFIG_USBC_VCONN - [PE_VCS_EVALUATE_SWAP] = "PE_VCS_Evaluate_Swap", - [PE_VCS_SEND_SWAP] = "PE_VCS_Send_Swap", - [PE_VCS_WAIT_FOR_VCONN_SWAP] = "PE_VCS_Wait_For_Vconn_Swap", - [PE_VCS_TURN_ON_VCONN_SWAP] = "PE_VCS_Turn_On_Vconn_Swap", - [PE_VCS_TURN_OFF_VCONN_SWAP] = "PE_VCS_Turn_Off_Vconn_Swap", - [PE_VCS_SEND_PS_RDY_SWAP] = "PE_VCS_Send_Ps_Rdy_Swap", - [PE_VCS_CBL_SEND_SOFT_RESET] = "PE_VCS_CBL_Send_Soft_Reset", -#endif - [PE_VDM_IDENTITY_REQUEST_CBL] = "PE_VDM_Identity_Request_Cbl", - [PE_INIT_PORT_VDM_IDENTITY_REQUEST] = - "PE_INIT_PORT_VDM_Identity_Request", - [PE_INIT_VDM_SVIDS_REQUEST] = "PE_INIT_VDM_SVIDs_Request", - [PE_INIT_VDM_MODES_REQUEST] = "PE_INIT_VDM_Modes_Request", - [PE_VDM_REQUEST_DPM] = "PE_VDM_Request_DPM", - [PE_VDM_RESPONSE] = "PE_VDM_Response", - [PE_HANDLE_CUSTOM_VDM_REQUEST] = "PE_Handle_Custom_Vdm_Request", - [PE_WAIT_FOR_ERROR_RECOVERY] = "PE_Wait_For_Error_Recovery", - [PE_BIST_TX] = "PE_Bist_TX", - [PE_DEU_SEND_ENTER_USB] = "PE_DEU_Send_Enter_USB", - [PE_DR_GET_SINK_CAP] = "PE_DR_Get_Sink_Cap", - [PE_DR_SNK_GIVE_SOURCE_CAP] = "PE_DR_SNK_Give_Source_Cap", - [PE_DR_SRC_GET_SOURCE_CAP] = "PE_DR_SRC_Get_Source_Cap", - - /* PD3.0 only states below here*/ -#ifdef CONFIG_USB_PD_REV30 - [PE_FRS_SNK_SRC_START_AMS] = "PE_FRS_SNK_SRC_Start_Ams", -#ifdef CONFIG_USB_PD_EXTENDED_MESSAGES - [PE_GIVE_BATTERY_CAP] = "PE_Give_Battery_Cap", - [PE_GIVE_BATTERY_STATUS] = "PE_Give_Battery_Status", - [PE_SEND_ALERT] = "PE_Send_Alert", -#else - [PE_SRC_CHUNK_RECEIVED] = "PE_SRC_Chunk_Received", - [PE_SNK_CHUNK_RECEIVED] = "PE_SNK_Chunk_Received", -#endif -#ifdef CONFIG_USBC_VCONN - [PE_VCS_FORCE_VCONN] = "PE_VCS_Force_Vconn", -#endif -#endif /* CONFIG_USB_PD_REV30 */ -}; - -#ifndef CONFIG_USBC_VCONN -GEN_NOT_SUPPORTED(PE_VCS_EVALUATE_SWAP); -#define PE_VCS_EVALUATE_SWAP PE_VCS_EVALUATE_SWAP_NOT_SUPPORTED -GEN_NOT_SUPPORTED(PE_VCS_SEND_SWAP); -#define PE_VCS_SEND_SWAP PE_VCS_SEND_SWAP_NOT_SUPPORTED -GEN_NOT_SUPPORTED(PE_VCS_WAIT_FOR_VCONN_SWAP); -#define PE_VCS_WAIT_FOR_VCONN_SWAP PE_VCS_WAIT_FOR_VCONN_SWAP_NOT_SUPPORTED -GEN_NOT_SUPPORTED(PE_VCS_TURN_ON_VCONN_SWAP); -#define PE_VCS_TURN_ON_VCONN_SWAP PE_VCS_TURN_ON_VCONN_SWAP_NOT_SUPPORTED -GEN_NOT_SUPPORTED(PE_VCS_TURN_OFF_VCONN_SWAP); -#define PE_VCS_TURN_OFF_VCONN_SWAP PE_VCS_TURN_OFF_VCONN_SWAP_NOT_SUPPORTED -GEN_NOT_SUPPORTED(PE_VCS_SEND_PS_RDY_SWAP); -#define PE_VCS_SEND_PS_RDY_SWAP PE_VCS_SEND_PS_RDY_SWAP_NOT_SUPPORTED -#endif /* CONFIG_USBC_VCONN */ - -#ifndef CONFIG_USB_PD_REV30 -GEN_NOT_SUPPORTED(PE_FRS_SNK_SRC_START_AMS); -#define PE_FRS_SNK_SRC_START_AMS PE_FRS_SNK_SRC_START_AMS_NOT_SUPPORTED -GEN_NOT_SUPPORTED(PE_PRS_FRS_SHARED); -#define PE_PRS_FRS_SHARED PE_PRS_FRS_SHARED_NOT_SUPPORTED -GEN_NOT_SUPPORTED(PE_SRC_CHUNK_RECEIVED); -#define PE_SRC_CHUNK_RECEIVED PE_SRC_CHUNK_RECEIVED_NOT_SUPPORTED -GEN_NOT_SUPPORTED(PE_SNK_CHUNK_RECEIVED); -#define PE_SNK_CHUNK_RECEIVED PE_SNK_CHUNK_RECEIVED_NOT_SUPPORTED -#endif /* CONFIG_USB_PD_REV30 */ - -#if !defined(CONFIG_USBC_VCONN) || !defined(CONFIG_USB_PD_REV30) -GEN_NOT_SUPPORTED(PE_VCS_FORCE_VCONN); -#define PE_VCS_FORCE_VCONN PE_VCS_FORCE_VCONN_NOT_SUPPORTED -#endif - -#ifndef CONFIG_USB_PD_EXTENDED_MESSAGES -GEN_NOT_SUPPORTED(PE_GIVE_BATTERY_CAP); -#define PE_GIVE_BATTERY_CAP PE_GIVE_BATTERY_CAP_NOT_SUPPORTED -GEN_NOT_SUPPORTED(PE_GIVE_BATTERY_STATUS); -#define PE_GIVE_BATTERY_STATUS PE_GIVE_BATTERY_STATUS_NOT_SUPPORTED -GEN_NOT_SUPPORTED(PE_SEND_ALERT); -#define PE_SEND_ALERT PE_SEND_ALERT_NOT_SUPPORTED -#endif /* CONFIG_USB_PD_EXTENDED_MESSAGES */ - -#ifdef CONFIG_USB_PD_EXTENDED_MESSAGES -GEN_NOT_SUPPORTED(PE_SRC_CHUNK_RECEIVED); -#define PE_SRC_CHUNK_RECEIVED PE_SRC_CHUNK_RECEIVED_NOT_SUPPORTED -GEN_NOT_SUPPORTED(PE_SNK_CHUNK_RECEIVED); -#define PE_SNK_CHUNK_RECEIVED PE_SNK_CHUNK_RECEIVED_NOT_SUPPORTED -#endif /* CONFIG_USB_PD_EXTENDED_MESSAGES */ - -static enum sm_local_state local_state[CONFIG_USB_PD_PORT_MAX_COUNT]; - -/* - * Common message send checking - * - * PE_MSG_SEND_PENDING: A message has been requested to be sent. It has - * not been GoodCRCed or Discarded. - * PE_MSG_SEND_COMPLETED: The message that was requested has been sent. - * This will only be returned one time and any other - * request for message send status will just return - * PE_MSG_SENT. This message actually includes both - * The COMPLETED and the SENT bit for easier checking. - * NOTE: PE_MSG_SEND_COMPLETED will only be returned - * a single time, directly after TX_COMPLETE. - * PE_MSG_SENT: The message that was requested to be sent has - * successfully been transferred to the partner. - * PE_MSG_DISCARDED: The message that was requested to be sent was - * discarded. The partner did not receive it. - * NOTE: PE_MSG_DISCARDED will only be returned - * one time and it is up to the caller to process - * what ever is needed to handle the Discard. - * PE_MSG_DPM_DISCARDED: The message that was requested to be sent was - * discarded and an active DRP_REQUEST was active. - * The DRP_REQUEST that was current will be moved - * back to the drp_requests so it can be performed - * later if needed. - * NOTE: PE_MSG_DPM_DISCARDED will only be returned - * one time and it is up to the caller to process - * what ever is needed to handle the Discard. - */ -enum pe_msg_check { - PE_MSG_SEND_PENDING = BIT(0), - PE_MSG_SENT = BIT(1), - PE_MSG_DISCARDED = BIT(2), - - PE_MSG_SEND_COMPLETED = BIT(3) | PE_MSG_SENT, - PE_MSG_DPM_DISCARDED = BIT(4) | PE_MSG_DISCARDED, -}; -static void pe_sender_response_msg_entry(const int port); -static enum pe_msg_check pe_sender_response_msg_run(const int port); -static void pe_sender_response_msg_exit(const int port); - -/* Debug log level - higher number == more log */ -#ifdef CONFIG_USB_PD_DEBUG_LEVEL -static const enum debug_level pe_debug_level = CONFIG_USB_PD_DEBUG_LEVEL; -#else -static enum debug_level pe_debug_level = DEBUG_LEVEL_1; -#endif - -/* - * Policy Engine State Machine Object - */ -static struct policy_engine { - /* 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; - /* state machine flags */ - uint32_t flags; - /* Device Policy Manager Request */ - uint32_t dpm_request; - uint32_t dpm_curr_request; - /* last requested voltage PDO index */ - int requested_idx; - - /* - * Port events - PD_STATUS_EVENT_* values - * Set from PD task but may be cleared by host command - */ - uint32_t events; - - /* port address where soft resets are sent */ - enum tcpci_msg_type soft_reset_sop; - - /* Current limit / voltage based on the last request message */ - uint32_t curr_limit; - uint32_t supply_voltage; - - /* PD_VDO_INVALID is used when there is an invalid VDO */ - int32_t ama_vdo; - int32_t vpd_vdo; - /* Alternate mode discovery results */ - struct pd_discovery discovery[DISCOVERY_TYPE_COUNT]; - /* Active alternate modes */ - struct partner_active_modes partner_amodes[AMODE_TYPE_COUNT]; - - /* Partner type to send */ - enum tcpci_msg_type tx_type; - - /* VDM - used to send information to shared VDM Request state */ - uint32_t vdm_cnt; - uint32_t vdm_data[VDO_HDR_SIZE + VDO_MAX_SIZE]; - uint8_t vdm_ack_min_data_objects; - - /* Counters */ - - /* - * This counter is used to retry the Hard Reset whenever there is no - * response from the remote device. - */ - uint32_t hard_reset_counter; - - /* - * This counter is used to count the number of Source_Capabilities - * Messages which have been sent by a Source at power up or after a - * Hard Reset. - */ - uint32_t caps_counter; - - /* - * This counter maintains a count of Discover Identity Messages sent - * to a cable. If no GoodCRC messages are received after - * nDiscoverIdentityCount, the port shall not send any further - * SOP'/SOP'' messages. - */ - uint32_t discover_identity_counter; - /* - * For PD2.0, we need to be a DFP before sending a discovery identity - * message to our port partner. This counter keeps track of how - * many attempts to DR SWAP from UFP to DFP. - */ - uint32_t dr_swap_attempt_counter; - - /* - * This counter tracks how many PR Swap messages are sent when the - * partner responds with a Wait message. Only used during SRC to SNK - * PR swaps - */ - uint8_t src_snk_pr_swap_counter; - - /* - * This counter maintains a count of VCONN swap requests. If VCONN swap - * isn't successful after N_VCONN_SWAP_COUNT, the port calls - * dpm_vdm_naked(). - */ - uint8_t vconn_swap_counter; - - /* Last received source cap */ - uint32_t src_caps[PDO_MAX_OBJECTS]; - int src_cap_cnt; /* -1 on error retrieving source caps */ - - /* Last received sink cap */ - uint32_t snk_caps[PDO_MAX_OBJECTS]; - int snk_cap_cnt; - - /* Attached ChromeOS device id, RW hash, and current RO / RW image */ - uint16_t dev_id; - uint32_t dev_rw_hash[PD_RW_HASH_SIZE/4]; - enum ec_image current_image; -} pe[CONFIG_USB_PD_PORT_MAX_COUNT]; - -test_export_static enum usb_pe_state get_state_pe(const int port); -test_export_static void set_state_pe(const int port, - const enum usb_pe_state new_state); -static void pe_set_dpm_curr_request(const int port, const int request); -/* - * The spec. revision is used to index into this array. - * PD 1.0 (VDO 1.0) - return VDM_VER10 - * PD 2.0 (VDO 1.0) - return VDM_VER10 - * PD 3.0 (VDO 2.0) - return VDM_VER20 - */ -static const uint8_t vdo_ver[] = { - [PD_REV10] = VDM_VER10, - [PD_REV20] = VDM_VER10, - [PD_REV30] = VDM_VER20, -}; - -int pd_get_rev(int port, enum tcpci_msg_type type) -{ - return prl_get_rev(port, type); -} - -int pd_get_vdo_ver(int port, enum tcpci_msg_type type) -{ - enum pd_rev_type rev = prl_get_rev(port, type); - - if (rev < PD_REV30) - return vdo_ver[rev]; - else - return VDM_VER20; -} - -static void pe_set_ready_state(int port) -{ - if (pe[port].power_role == PD_ROLE_SOURCE) - set_state_pe(port, PE_SRC_READY); - else - set_state_pe(port, PE_SNK_READY); -} - -static inline void send_data_msg(int port, enum tcpci_msg_type type, - enum pd_data_msg_type msg) -{ - /* Clear any previous TX status before sending a new message */ - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - prl_send_data_msg(port, type, msg); -} - -static __maybe_unused inline void send_ext_data_msg( - int port, enum tcpci_msg_type type, enum pd_ext_msg_type msg) -{ - /* Clear any previous TX status before sending a new message */ - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - prl_send_ext_data_msg(port, type, msg); -} - -static inline void send_ctrl_msg(int port, enum tcpci_msg_type type, - enum pd_ctrl_msg_type msg) -{ - /* Clear any previous TX status before sending a new message */ - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - prl_send_ctrl_msg(port, type, msg); -} - -static void set_cable_rev(int port) -{ - /* - * If port partner runs PD 2.0, cable communication must - * also be PD 2.0 - */ - if (prl_get_rev(port, TCPCI_MSG_SOP) == PD_REV20) { - /* - * If the cable supports PD 3.0, but the port partner supports PD 2.0, - * redo the cable discover with PD 2.0 - */ - if (prl_get_rev(port, TCPCI_MSG_SOP_PRIME) == PD_REV30 && - pd_get_identity_discovery(port, TCPCI_MSG_SOP_PRIME) == - PD_DISC_COMPLETE) { - pd_set_identity_discovery(port, TCPCI_MSG_SOP_PRIME, - PD_DISC_NEEDED); - } - prl_set_rev(port, TCPCI_MSG_SOP_PRIME, PD_REV20); - } -} - -/* Compile-time insurance to ensure this code does not call into prl directly */ -#define prl_send_data_msg DO_NOT_USE -#define prl_send_ext_data_msg DO_NOT_USE -#define prl_send_ctrl_msg DO_NOT_USE - -static void pe_init(int port) -{ - pe[port].flags = 0; - pe[port].dpm_request = 0; - pe[port].dpm_curr_request = 0; - pd_timer_disable_range(port, PE_TIMER_RANGE); - pe[port].data_role = pd_get_data_role(port); - pe[port].tx_type = TCPCI_MSG_INVALID; - pe[port].events = 0; - - tc_pd_connection(port, 0); - - if (pd_get_power_role(port) == PD_ROLE_SOURCE) - set_state_pe(port, PE_SRC_STARTUP); - else - set_state_pe(port, PE_SNK_STARTUP); -} - -int pe_is_running(int port) -{ - return local_state[port] == SM_RUN; -} - -bool pe_in_frs_mode(int port) -{ - return PE_CHK_FLAG(port, PE_FLAGS_FAST_ROLE_SWAP_PATH); -} - -bool pe_in_local_ams(int port) -{ - return !!PE_CHK_FLAG(port, PE_FLAGS_LOCALLY_INITIATED_AMS); -} - -void pe_set_debug_level(enum debug_level debug_level) -{ -#ifndef CONFIG_USB_PD_DEBUG_LEVEL - pe_debug_level = debug_level; -#endif -} - -void pe_run(int port, int evt, int en) -{ - switch (local_state[port]) { - case SM_PAUSED: - if (!en) - break; - /* fall through */ - case SM_INIT: - pe_init(port); - local_state[port] = SM_RUN; - /* fall through */ - case SM_RUN: - if (!en) { - local_state[port] = SM_PAUSED; - /* - * While we are paused, exit all states and wait until - * initialized again. - */ - set_state(port, &pe[port].ctx, NULL); - break; - } - - /* - * 8.3.3.3.8 PE_SNK_Hard_Reset State - * The Policy Engine Shall transition to the PE_SNK_Hard_Reset - * state from any state when: - * - Hard Reset request from Device Policy Manager - * - * USB PD specification clearly states that we should go to - * PE_SNK_Hard_Reset from ANY state (including states in which - * port is source) when DPM requests that. This can lead to - * execute Hard Reset path for sink when actually our power - * role is source. In our implementation we will choose Hard - * Reset path depending on current power role. - */ - if (PE_CHK_DPM_REQUEST(port, DPM_REQUEST_HARD_RESET_SEND)) { - pe_set_dpm_curr_request(port, DPM_REQUEST_HARD_RESET_SEND); - if (pd_get_power_role(port) == PD_ROLE_SOURCE) - set_state_pe(port, PE_SRC_HARD_RESET); - else - set_state_pe(port, PE_SNK_HARD_RESET); - } - - /* - * Check for Fast Role Swap signal - * This is not a typical pattern for adding state changes. - * I added this here because FRS SIGNALED can happen at any - * state once we are listening for the signal and we want to - * make sure to handle it immediately. - */ - if (IS_ENABLED(CONFIG_USB_PD_REV30) && - PE_CHK_FLAG(port, PE_FLAGS_FAST_ROLE_SWAP_SIGNALED)) { - PE_CLR_FLAG(port, PE_FLAGS_FAST_ROLE_SWAP_SIGNALED); - set_state_pe(port, PE_FRS_SNK_SRC_START_AMS); - } - - /* Run state machine */ - run_state(port, &pe[port].ctx); - break; - } -} - -int pe_is_explicit_contract(int port) -{ - return PE_CHK_FLAG(port, PE_FLAGS_EXPLICIT_CONTRACT); -} - -void pe_message_received(int port) -{ - /* This should only be called from the PD task */ - assert(port == TASK_ID_TO_PD_PORT(task_get_current())); - - PE_SET_FLAG(port, PE_FLAGS_MSG_RECEIVED); - task_wake(PD_PORT_TO_TASK_ID(port)); -} - -void pe_hard_reset_sent(int port) -{ - /* This should only be called from the PD task */ - assert(port == TASK_ID_TO_PD_PORT(task_get_current())); - - PE_CLR_FLAG(port, PE_FLAGS_HARD_RESET_PENDING); -} - -void pe_got_hard_reset(int port) -{ - /* This should only be called from the PD task */ - assert(port == TASK_ID_TO_PD_PORT(task_get_current())); - - /* - * Transition from any state to the PE_SRC_Hard_Reset_Received or - * PE_SNK_Transition_to_default state when: - * 1) Hard Reset Signaling is detected. - */ - pe[port].power_role = pd_get_power_role(port); - - /* Exit BIST Test mode, in case the TCPC entered it. */ - tcpc_set_bist_test_mode(port, false); - - if (pe[port].power_role == PD_ROLE_SOURCE) - set_state_pe(port, PE_SRC_HARD_RESET_RECEIVED); - else - set_state_pe(port, PE_SNK_TRANSITION_TO_DEFAULT); -} - -#ifdef CONFIG_USB_PD_REV30 -/* - * pd_got_frs_signal - * - * Called by the handler that detects the FRS signal in order to - * switch PE states to complete the FRS that the hardware has - * started. - * - * If the PE is not running, generate an error recovery to turn off - * Vbus and get the port back into a known state. - */ -void pd_got_frs_signal(int port) -{ - if (pe_is_running(port)) - PE_SET_FLAG(port, PE_FLAGS_FAST_ROLE_SWAP_SIGNALED); - else - pd_set_error_recovery(port); - - task_wake(PD_PORT_TO_TASK_ID(port)); -} -#endif /* CONFIG_USB_PD_REV30 */ - -/* - * PE_Set_FRS_Enable - * - * This function should be called every time an explicit contract - * is disabled, to disable FRS. - * - * Enabling an explicit contract is not enough to enable FRS, it - * also requires a Sink Capability power requirement from a Source - * that supports FRS so we can determine if this is something we - * can handle. - */ -static void pe_set_frs_enable(int port, int enable) -{ - int current = PE_CHK_FLAG(port, PE_FLAGS_FAST_ROLE_SWAP_ENABLED); - - /* This should only be called from the PD task */ - if (!IS_ENABLED(TEST_BUILD)) - assert(port == TASK_ID_TO_PD_PORT(task_get_current())); - - if (!IS_ENABLED(CONFIG_USB_PD_FRS) || !IS_ENABLED(CONFIG_USB_PD_REV30)) - return; - - /* Request an FRS change, only if the state has changed */ - if (!!current == !!enable) - return; - - pd_set_frs_enable(port, enable); - if (enable) { - int curr_limit = *pd_get_snk_caps(port) - & PDO_FIXED_FRS_CURR_MASK; - - typec_select_src_current_limit_rp(port, - curr_limit == - PDO_FIXED_FRS_CURR_3A0_AT_5V ? - TYPEC_RP_3A0 : TYPEC_RP_1A5); - PE_SET_FLAG(port, PE_FLAGS_FAST_ROLE_SWAP_ENABLED); - } else { - PE_CLR_FLAG(port, PE_FLAGS_FAST_ROLE_SWAP_ENABLED); - } -} - -void pe_set_explicit_contract(int port) -{ - PE_SET_FLAG(port, PE_FLAGS_EXPLICIT_CONTRACT); - - /* Set Rp for collision avoidance */ - if (IS_ENABLED(CONFIG_USB_PD_REV30)) - typec_update_cc(port); -} - -void pe_invalidate_explicit_contract(int port) -{ - pe_set_frs_enable(port, 0); - - PE_CLR_FLAG(port, PE_FLAGS_EXPLICIT_CONTRACT); - - /* Set Rp for current limit if still attached */ - if (IS_ENABLED(CONFIG_USB_PD_REV30) && pd_is_connected(port)) - typec_update_cc(port); -} - -void pd_notify_event(int port, uint32_t event_mask) -{ - atomic_or(&pe[port].events, event_mask); - - /* Notify the host that new events are available to read */ - pd_send_host_event(PD_EVENT_TYPEC); -} - -void pd_clear_events(int port, uint32_t clear_mask) -{ - atomic_clear_bits(&pe[port].events, clear_mask); -} - -uint32_t pd_get_events(int port) -{ - return pe[port].events; -} - -void pe_set_snk_caps(int port, int cnt, uint32_t *snk_caps) -{ - pe[port].snk_cap_cnt = cnt; - - memcpy(pe[port].snk_caps, snk_caps, sizeof(uint32_t) * cnt); -} - -const uint32_t * const pd_get_snk_caps(int port) -{ - return pe[port].snk_caps; -} - -uint8_t pd_get_snk_cap_cnt(int port) -{ - return pe[port].snk_cap_cnt; -} - -uint32_t pd_get_requested_voltage(int port) -{ - return pe[port].supply_voltage; -} - -uint32_t pd_get_requested_current(int port) -{ - return pe[port].curr_limit; -} - -/* - * Determine if this port may communicate with the cable plug. - * - * In both PD 2.0 and 3.0 (2.5.4 SOP'/SOP'' Communication with Cable Plugs): - * - * When no Contract or an Implicit Contract is in place (e.g. after a Power Role - * Swap or Fast Role Swap) only the Source port that is supplying Vconn is - * allowed to send packets to a Cable Plug - * - * When in an explicit contract, PD 3.0 requires that a port be Vconn source to - * communicate with the cable. PD 2.0 requires that a port be DFP to - * communicate with the cable plug, with an implication that it must be Vconn - * source as well (6.3.11 VCONN_Swap Message). - */ -static bool pe_can_send_sop_prime(int port) -{ - if (IS_ENABLED(CONFIG_USBC_VCONN)) { - if (PE_CHK_FLAG(port, PE_FLAGS_EXPLICIT_CONTRACT)) { - if (prl_get_rev(port, TCPCI_MSG_SOP) == PD_REV20) - return tc_is_vconn_src(port) && - pe[port].data_role == PD_ROLE_DFP; - else - return tc_is_vconn_src(port); - } else { - return tc_is_vconn_src(port) && - pe[port].power_role == PD_ROLE_SOURCE; - } - } else { - return false; - } -} - -/* - * Determine if this port may send the given VDM type - * - * For PD 2.0, "Only the DFP Shall be an Initrator of Structured VDMs except for - * the Attention Command that Shall only be initiated by the UFP" - * - * For PD 3.0, "Either port May be an Initiator of Structured VDMs except for - * the Enter Mode and Exit Mode Commands which shall only be initiated by the - * DFP" (6.4.4.2 Structured VDM) - * - * In both revisions, VDMs may only be initiated while in an explicit contract, - * with the only exception being for cable plug discovery. - */ -static bool pe_can_send_sop_vdm(int port, int vdm_cmd) -{ - if (PE_CHK_FLAG(port, PE_FLAGS_EXPLICIT_CONTRACT)) { - if (prl_get_rev(port, TCPCI_MSG_SOP) == PD_REV20) { - if (pe[port].data_role == PD_ROLE_UFP && - vdm_cmd != CMD_ATTENTION) { - return false; - } - } else { - if (pe[port].data_role == PD_ROLE_UFP && - (vdm_cmd == CMD_ENTER_MODE || - vdm_cmd == CMD_EXIT_MODE)) { - return false; - } - } - return true; - } - - return false; -} - -static void pe_send_soft_reset(const int port, enum tcpci_msg_type type) -{ - pe[port].soft_reset_sop = type; - set_state_pe(port, PE_SEND_SOFT_RESET); -} - -void pe_report_discard(int port) -{ - /* - * Clear local AMS indicator as our AMS message was discarded, and flag - * the discard for the PE - */ - PE_CLR_FLAG(port, PE_FLAGS_LOCALLY_INITIATED_AMS); - PE_SET_FLAG(port, PE_FLAGS_MSG_DISCARDED); - - /* TODO(b/157228506): Ensure all states are checking discard */ -} - -/* - * Utility function to check for an outgoing message discard during states which - * send a message as a part of an AMS and wait for the transmit to complete. - * Note these states should not be power transitioning. - * - * In these states, discard due to an incoming message is a protocol error. - */ -static bool pe_check_outgoing_discard(int port) -{ - /* - * On outgoing discard, soft reset with SOP* of incoming message - * - * See Table 6-65 Response to an incoming Message (except VDM) in PD 3.0 - * Version 2.0 Specification. - */ - if (PE_CHK_FLAG(port, PE_FLAGS_MSG_DISCARDED) && - PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - enum tcpci_msg_type sop = - PD_HEADER_GET_SOP(rx_emsg[port].header); - - PE_CLR_FLAG(port, PE_FLAGS_MSG_DISCARDED); - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - - pe_send_soft_reset(port, sop); - return true; - } - - return false; -} - -void pe_report_error(int port, enum pe_error e, enum tcpci_msg_type type) -{ - /* This should only be called from the PD task */ - assert(port == TASK_ID_TO_PD_PORT(task_get_current())); - - /* - * If there is a timeout error while waiting for a chunk of a chunked - * message, there is no requirement to trigger a soft reset. - */ - if (e == ERR_RCH_CHUNK_WAIT_TIMEOUT) - return; - - /* - * Generate Hard Reset if Protocol Error occurred - * while in PE_Send_Soft_Reset state. - */ - if (get_state_pe(port) == PE_SEND_SOFT_RESET) { - if (pe[port].power_role == PD_ROLE_SINK) - set_state_pe(port, PE_SNK_HARD_RESET); - else - set_state_pe(port, PE_SRC_HARD_RESET); - return; - } - - /* - * The following states require custom handling of protocol errors, - * because they either need special handling of the no GoodCRC case - * (cable identity request, send capabilities), occur before explicit - * contract (discovery), or happen during a power transition. - * - * TODO(b/150774779): TCPMv2: Improve pe_error documentation - */ - if ((get_state_pe(port) == PE_SRC_SEND_CAPABILITIES || - get_state_pe(port) == PE_SRC_TRANSITION_SUPPLY || - get_state_pe(port) == PE_PRS_SNK_SRC_EVALUATE_SWAP || - get_state_pe(port) == PE_PRS_SNK_SRC_SOURCE_ON || - get_state_pe(port) == PE_PRS_SRC_SNK_WAIT_SOURCE_ON || - get_state_pe(port) == PE_SRC_DISABLED || - get_state_pe(port) == PE_SRC_DISCOVERY || - get_state_pe(port) == PE_VCS_CBL_SEND_SOFT_RESET || - get_state_pe(port) == PE_VDM_IDENTITY_REQUEST_CBL) || - (pe_in_frs_mode(port) && - get_state_pe(port) == PE_PRS_SNK_SRC_SEND_SWAP) - ) { - PE_SET_FLAG(port, PE_FLAGS_PROTOCOL_ERROR); - task_wake(PD_PORT_TO_TASK_ID(port)); - return; - } - - /* - * See section 8.3.3.4.1.1 PE_SRC_Send_Soft_Reset State: - * - * The PE_Send_Soft_Reset state shall be entered from - * any state when - * * A Protocol Error is detected by Protocol Layer during a - * Non-Interruptible AMS or - * * A message has not been sent after retries or - * * When not in an explicit contract and - * * Protocol Errors occurred on SOP during an Interruptible AMS or - * * Protocol Errors occurred on SOP during any AMS where the first - * Message in the sequence has not yet been sent i.e. an unexpected - * Message is received instead of the expected GoodCRC Message - * response. - */ - /* All error types besides transmit errors are Protocol Errors. */ - if ((e != ERR_TCH_XMIT && - !PE_CHK_FLAG(port, PE_FLAGS_INTERRUPTIBLE_AMS)) - || e == ERR_TCH_XMIT - || (!PE_CHK_FLAG(port, PE_FLAGS_EXPLICIT_CONTRACT) && - type == TCPCI_MSG_SOP)) { - pe_send_soft_reset(port, type); - } - /* - * Transition to PE_Snk_Ready or PE_Src_Ready by a Protocol - * Error during an Interruptible AMS. - */ - else { - pe_set_ready_state(port); - } -} - -void pe_got_soft_reset(int port) -{ - /* This should only be called from the PD task */ - assert(port == TASK_ID_TO_PD_PORT(task_get_current())); - - /* - * The PE_SRC_Soft_Reset state Shall be entered from any state when a - * Soft_Reset Message is received from the Protocol Layer. - */ - set_state_pe(port, PE_SOFT_RESET); -} - -__overridable bool pd_can_charge_from_device(int port, const int pdo_cnt, - const uint32_t *pdos) -{ - /* - * Don't attempt to charge from a device we have no SrcCaps from. Or, if - * drp_state is FORCE_SOURCE then don't attempt a PRS. - */ - if (pdo_cnt == 0 || pd_get_dual_role(port) == PD_DRP_FORCE_SOURCE) - return false; - - /* - * Treat device as a dedicated charger (meaning we should charge - * from it) if: - * - it does not support power swap, or - * - it is unconstrained power, or - * - it presents at least 27 W of available power - */ - - /* Unconstrained Power or NOT Dual Role Power we can charge from */ - if (pdos[0] & PDO_FIXED_UNCONSTRAINED || - (pdos[0] & PDO_FIXED_DUAL_ROLE) == 0) - return true; - - /* [virtual] allow_list */ - if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) { - uint32_t max_ma, max_mv, max_pdo, max_mw, unused; - - /* - * Get max power that the partner offers (not necessarily what - * this board will request) - */ - pd_find_pdo_index(pdo_cnt, pdos, - PD_REV3_MAX_VOLTAGE, - &max_pdo); - pd_extract_pdo_power(max_pdo, &max_ma, &max_mv, &unused); - max_mw = max_ma * max_mv / 1000; - - if (max_mw >= PD_DRP_CHARGE_POWER_MIN) - return true; - } - return false; -} - -void pd_resume_check_pr_swap_needed(int port) -{ - /* - * Explicit contract, current power role of SNK, the device - * indicates it should not power us, and device isn't selected - * as the charging port (ex. through the GUI) then trigger a PR_Swap - */ - if (pe_is_explicit_contract(port) && - pd_get_power_role(port) == PD_ROLE_SINK && - !pd_can_charge_from_device(port, pd_get_src_cap_cnt(port), - pd_get_src_caps(port)) && - (!IS_ENABLED(CONFIG_CHARGE_MANAGER) || - charge_manager_get_active_charge_port() != port)) - pd_dpm_request(port, DPM_REQUEST_PR_SWAP); -} - -void pd_dpm_request(int port, enum pd_dpm_request req) -{ - PE_SET_DPM_REQUEST(port, req); -} - -void pe_vconn_swap_complete(int port) -{ - /* This should only be called from the PD task */ - assert(port == TASK_ID_TO_PD_PORT(task_get_current())); - - PE_SET_FLAG(port, PE_FLAGS_VCONN_SWAP_COMPLETE); -} - -void pe_ps_reset_complete(int port) -{ - /* This should only be called from the PD task */ - assert(port == TASK_ID_TO_PD_PORT(task_get_current())); - - PE_SET_FLAG(port, PE_FLAGS_PS_RESET_COMPLETE); -} - -void pe_message_sent(int port) -{ - /* This should only be called from the PD task */ - assert(port == TASK_ID_TO_PD_PORT(task_get_current())); - - PE_SET_FLAG(port, PE_FLAGS_TX_COMPLETE); - task_wake(PD_PORT_TO_TASK_ID(port)); -} - -void pd_send_vdm(int port, uint32_t vid, int cmd, const uint32_t *data, - int count) -{ - /* Copy VDM Header */ - pe[port].vdm_data[0] = - VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ? 1 : - (PD_VDO_CMD(cmd) <= CMD_ATTENTION), - VDO_SVDM_VERS(pd_get_vdo_ver(port, TCPCI_MSG_SOP)) | - cmd); - - /* - * Copy VDOs after the VDM Header. Note that the count refers to VDO - * count. - */ - memcpy((pe[port].vdm_data + 1), data, count * sizeof(uint32_t)); - - pe[port].vdm_cnt = count + 1; - - /* - * The PE transmit routine assumes that tx_type was set already. Note, - * that this function is likely called from outside the PD task. - * (b/180465870) - */ - pe[port].tx_type = TCPCI_MSG_SOP; - pd_dpm_request(port, DPM_REQUEST_VDM); - - task_wake(PD_PORT_TO_TASK_ID(port)); -} - -#ifdef TEST_BUILD -/* - * Allow unit tests to access this function to clear internal state data between - * runs - */ -void pe_clear_port_data(int port) -#else -static void pe_clear_port_data(int port) -#endif /* TEST_BUILD */ -{ - /* - * PD 3.0 Section 8.3.3.3.8 - * Note: The HardResetCounter is reset on a power cycle or Detach. - */ - pe[port].hard_reset_counter = 0; - - /* Reset port events */ - pd_clear_events(port, GENMASK(31, 0)); - - /* But then set disconnected event */ - pd_notify_event(port, PD_STATUS_EVENT_DISCONNECTED); - - /* Tell Policy Engine to invalidate the explicit contract */ - pe_invalidate_explicit_contract(port); - - /* - * Saved Source and Sink Capabilities are no longer valid on disconnect - */ - pd_set_src_caps(port, 0, NULL); - pe_set_snk_caps(port, 0, NULL); - - dpm_remove_sink(port); - dpm_remove_source(port); - - /* Exit BIST Test mode, in case the TCPC entered it. */ - tcpc_set_bist_test_mode(port, false); -} - -static void pe_handle_detach(void) -{ - const int port = TASK_ID_TO_PD_PORT(task_get_current()); - - pe_clear_port_data(port); -} -DECLARE_HOOK(HOOK_USB_PD_DISCONNECT, pe_handle_detach, HOOK_PRIO_DEFAULT); - -#ifdef CONFIG_USB_PD_RESET_MIN_BATT_SOC -static void pe_update_waiting_batt_flag(void) -{ - int i; - int batt_soc = usb_get_battery_soc(); - - if (batt_soc < CONFIG_USB_PD_RESET_MIN_BATT_SOC || - battery_get_disconnect_state() != BATTERY_NOT_DISCONNECTED) - return; - - for (i = 0; i < board_get_usb_pd_port_count(); i++) { - if (PE_CHK_FLAG(i, PE_FLAGS_SNK_WAITING_BATT)) { - /* - * Battery has gained sufficient charge to kick off PD - * negotiation and withstand a hard reset. Clear the - * flag and perform Hard Reset. - */ - PE_CLR_FLAG(i, PE_FLAGS_SNK_WAITING_BATT); - CPRINTS("C%d: Battery has enough charge (%d%%) " \ - "to withstand a hard reset", i, batt_soc); - pd_dpm_request(i, DPM_REQUEST_HARD_RESET_SEND); - } - } -} -DECLARE_HOOK(HOOK_BATTERY_SOC_CHANGE, pe_update_waiting_batt_flag, - HOOK_PRIO_DEFAULT); -#endif - -/* - * Private functions - */ -static void pe_set_dpm_curr_request(const int port, - const int request) -{ - PE_CLR_DPM_REQUEST(port, request); - pe[port].dpm_curr_request = request; -} - -/* Set the TypeC state machine to a new state. */ -test_export_static void set_state_pe(const int port, - const enum usb_pe_state new_state) -{ - set_state(port, &pe[port].ctx, &pe_states[new_state]); -} - -/* Get the current TypeC state. */ -test_export_static enum usb_pe_state get_state_pe(const int port) -{ - return pe[port].ctx.current - &pe_states[0]; -} - -/* - * Handle common DPM requests to both source and sink. - * - * Note: it is assumed the calling state set PE_FLAGS_LOCALLY_INITIATED_AMS - * - * Returns true if state was set and calling run state should now return. - */ -static bool common_src_snk_dpm_requests(int port) -{ - if (IS_ENABLED(CONFIG_USB_PD_EXTENDED_MESSAGES) && - PE_CHK_DPM_REQUEST(port, DPM_REQUEST_SEND_ALERT)) { - pe_set_dpm_curr_request(port, DPM_REQUEST_SEND_ALERT); - set_state_pe(port, PE_SEND_ALERT); - return true; - } else if (IS_ENABLED(CONFIG_USBC_VCONN) && - PE_CHK_DPM_REQUEST(port, DPM_REQUEST_VCONN_SWAP)) { - pe_set_dpm_curr_request(port, DPM_REQUEST_VCONN_SWAP); - set_state_pe(port, PE_VCS_SEND_SWAP); - return true; - } else if (PE_CHK_DPM_REQUEST(port, - DPM_REQUEST_BIST_TX)) { - pe_set_dpm_curr_request(port, DPM_REQUEST_BIST_TX); - set_state_pe(port, PE_BIST_TX); - return true; - } else if (PE_CHK_DPM_REQUEST(port, - DPM_REQUEST_SNK_STARTUP)) { - pe_set_dpm_curr_request(port, DPM_REQUEST_SNK_STARTUP); - set_state_pe(port, PE_SNK_STARTUP); - return true; - } else if (PE_CHK_DPM_REQUEST(port, - DPM_REQUEST_SRC_STARTUP)) { - pe_set_dpm_curr_request(port, DPM_REQUEST_SRC_STARTUP); - set_state_pe(port, PE_SRC_STARTUP); - return true; - } else if (PE_CHK_DPM_REQUEST(port, - DPM_REQUEST_SOFT_RESET_SEND)) { - pe_set_dpm_curr_request(port, DPM_REQUEST_SOFT_RESET_SEND); - /* Currently only support sending soft reset to SOP */ - pe_send_soft_reset(port, TCPCI_MSG_SOP); - return true; - } else if (PE_CHK_DPM_REQUEST(port, DPM_REQUEST_PORT_DISCOVERY)) { - pe_set_dpm_curr_request(port, DPM_REQUEST_PORT_DISCOVERY); - if (!PE_CHK_FLAG(port, PE_FLAGS_MODAL_OPERATION)) { - /* - * Clear counters and reset timer to trigger a - * port discovery, and also clear any pending VDM send - * requests. - */ - pd_dfp_discovery_init(port); - /* - * TODO(b/189353401): Do not reinitialize modes when no - * longer required. - */ - pd_dfp_mode_init(port); - pe[port].dr_swap_attempt_counter = 0; - pe[port].discover_identity_counter = 0; - pd_timer_enable(port, PE_TIMER_DISCOVER_IDENTITY, - PD_T_DISCOVER_IDENTITY); - PE_CLR_DPM_REQUEST(port, DPM_REQUEST_VDM); - } - return true; - } else if (PE_CHK_DPM_REQUEST(port, DPM_REQUEST_VDM)) { - pe_set_dpm_curr_request(port, DPM_REQUEST_VDM); - /* Send previously set up SVDM. */ - set_state_pe(port, PE_VDM_REQUEST_DPM); - return true; - } else if (PE_CHK_DPM_REQUEST(port, DPM_REQUEST_ENTER_USB)) { - pe_set_dpm_curr_request(port, DPM_REQUEST_ENTER_USB); - set_state_pe(port, PE_DEU_SEND_ENTER_USB); - return true; - } else if (PE_CHK_DPM_REQUEST(port, DPM_REQUEST_EXIT_MODES)) { - pe_set_dpm_curr_request(port, DPM_REQUEST_EXIT_MODES); - dpm_set_mode_exit_request(port); - return true; - } else if (PE_CHK_DPM_REQUEST(port, DPM_REQUEST_GET_SNK_CAPS)) { - pe_set_dpm_curr_request(port, - DPM_REQUEST_GET_SNK_CAPS); - set_state_pe(port, PE_DR_GET_SINK_CAP); - return true; - } else if (PE_CHK_DPM_REQUEST(port, - DPM_REQUEST_SOP_PRIME_SOFT_RESET_SEND)) { - pe_set_dpm_curr_request(port, - DPM_REQUEST_SOP_PRIME_SOFT_RESET_SEND); - pe[port].tx_type = TCPCI_MSG_SOP_PRIME; - set_state_pe(port, PE_VCS_CBL_SEND_SOFT_RESET); - return true; - } - - return false; -} - -/* - * Handle source-specific DPM requests - * - * Returns true if state was set and calling run state should now return. - */ -static bool source_dpm_requests(int port) -{ - /* - * Ignore sink-specific request: - * DPM_REQUEST_NEW_POWER_LEVEL - * DPM_REQUEST_SOURCE_CAP - * DPM_REQUEST_FRS_DET_ENABLE - * DPM_REQURST_FRS_DET_DISABLE - */ - PE_CLR_DPM_REQUEST(port, DPM_REQUEST_NEW_POWER_LEVEL | - DPM_REQUEST_SOURCE_CAP | - DPM_REQUEST_FRS_DET_ENABLE | - DPM_REQUEST_FRS_DET_DISABLE); - - if (pe[port].dpm_request) { - uint32_t dpm_request = pe[port].dpm_request; - - PE_SET_FLAG(port, PE_FLAGS_LOCALLY_INITIATED_AMS); - - if (PE_CHK_DPM_REQUEST(port, - DPM_REQUEST_DR_SWAP)) { - pe_set_dpm_curr_request(port, - DPM_REQUEST_DR_SWAP); - if (PE_CHK_FLAG(port, PE_FLAGS_MODAL_OPERATION)) - set_state_pe(port, PE_SRC_HARD_RESET); - else - set_state_pe(port, PE_DRS_SEND_SWAP); - return true; - } else if (PE_CHK_DPM_REQUEST(port, - DPM_REQUEST_PR_SWAP)) { - pe_set_dpm_curr_request(port, - DPM_REQUEST_PR_SWAP); - set_state_pe(port, PE_PRS_SRC_SNK_SEND_SWAP); - return true; - } else if (PE_CHK_DPM_REQUEST(port, - DPM_REQUEST_GOTO_MIN)) { - pe_set_dpm_curr_request(port, - DPM_REQUEST_GOTO_MIN); - set_state_pe(port, PE_SRC_TRANSITION_SUPPLY); - return true; - } else if (PE_CHK_DPM_REQUEST(port, - DPM_REQUEST_SRC_CAP_CHANGE)) { - pe_set_dpm_curr_request(port, - DPM_REQUEST_SRC_CAP_CHANGE); - set_state_pe(port, PE_SRC_SEND_CAPABILITIES); - return true; - } else if (PE_CHK_DPM_REQUEST(port, - DPM_REQUEST_GET_SRC_CAPS)) { - pe_set_dpm_curr_request(port, - DPM_REQUEST_GET_SRC_CAPS); - set_state_pe(port, PE_DR_SRC_GET_SOURCE_CAP); - return true; - } else if (PE_CHK_DPM_REQUEST(port, - DPM_REQUEST_SEND_PING)) { - pe_set_dpm_curr_request(port, - DPM_REQUEST_SEND_PING); - set_state_pe(port, PE_SRC_PING); - return true; - } else if (common_src_snk_dpm_requests(port)) { - return true; - } - - CPRINTF("Unhandled DPM Request %x received\n", - dpm_request); - PE_CLR_DPM_REQUEST(port, dpm_request); - PE_CLR_FLAG(port, PE_FLAGS_LOCALLY_INITIATED_AMS); - } - return false; -} - -/* - * Handle sink-specific DPM requests - * - * Returns true if state was set and calling run state should now return. - */ -static bool sink_dpm_requests(int port) -{ - /* - * Ignore source specific requests: - * DPM_REQUEST_GOTO_MIN - * DPM_REQUEST_SRC_CAP_CHANGE, - * DPM_REQUEST_SEND_PING - */ - PE_CLR_DPM_REQUEST(port, DPM_REQUEST_GOTO_MIN | - DPM_REQUEST_SRC_CAP_CHANGE | - DPM_REQUEST_SEND_PING); - - if (pe[port].dpm_request) { - uint32_t dpm_request = pe[port].dpm_request; - - PE_SET_FLAG(port, PE_FLAGS_LOCALLY_INITIATED_AMS); - - if (PE_CHK_DPM_REQUEST(port, - DPM_REQUEST_DR_SWAP)) { - pe_set_dpm_curr_request(port, - DPM_REQUEST_DR_SWAP); - if (PE_CHK_FLAG(port, PE_FLAGS_MODAL_OPERATION)) - set_state_pe(port, PE_SNK_HARD_RESET); - else - set_state_pe(port, PE_DRS_SEND_SWAP); - return true; - } else if (PE_CHK_DPM_REQUEST(port, - DPM_REQUEST_PR_SWAP)) { - pe_set_dpm_curr_request(port, - DPM_REQUEST_PR_SWAP); - set_state_pe(port, PE_PRS_SNK_SRC_SEND_SWAP); - return true; - } else if (PE_CHK_DPM_REQUEST(port, - DPM_REQUEST_SOURCE_CAP)) { - pe_set_dpm_curr_request(port, - DPM_REQUEST_SOURCE_CAP); - set_state_pe(port, PE_SNK_GET_SOURCE_CAP); - return true; - } else if (PE_CHK_DPM_REQUEST(port, - DPM_REQUEST_NEW_POWER_LEVEL)) { - pe_set_dpm_curr_request(port, - DPM_REQUEST_NEW_POWER_LEVEL); - set_state_pe(port, PE_SNK_SELECT_CAPABILITY); - return true; - } else if (PE_CHK_DPM_REQUEST(port, - DPM_REQUEST_FRS_DET_ENABLE)) { - pe_set_frs_enable(port, 1); - - /* Requires no state change, fall through to false */ - PE_CLR_DPM_REQUEST(port, DPM_REQUEST_FRS_DET_ENABLE); - } else if (PE_CHK_DPM_REQUEST(port, - DPM_REQUEST_FRS_DET_DISABLE)) { - pe_set_frs_enable(port, 0); - /* Restore a default port current limit */ - typec_select_src_current_limit_rp(port, - CONFIG_USB_PD_PULLUP); - - /* Requires no state change, fall through to false */ - PE_CLR_DPM_REQUEST(port, DPM_REQUEST_FRS_DET_DISABLE); - } else if (common_src_snk_dpm_requests(port)) { - return true; - } else { - CPRINTF("Unhandled DPM Request %x received\n", - dpm_request); - PE_CLR_DPM_REQUEST(port, dpm_request); - } - - PE_CLR_FLAG(port, PE_FLAGS_LOCALLY_INITIATED_AMS); - } - return false; -} - -/* Get the previous TypeC state. */ -static enum usb_pe_state get_last_state_pe(const int port) -{ - return pe[port].ctx.previous - &pe_states[0]; -} - -static void print_current_state(const int port) -{ - const char *mode = ""; - - if (IS_ENABLED(CONFIG_USB_PD_REV30) && - pe_in_frs_mode(port)) - mode = " FRS-MODE"; - - if (IS_ENABLED(USB_PD_DEBUG_LABELS)) - CPRINTS_L1("C%d: %s%s", port, - pe_state_names[get_state_pe(port)], mode); - else - CPRINTS("C%d: pe-st%d", port, get_state_pe(port)); -} - -static void send_source_cap(int port) -{ - const uint32_t *src_pdo; - const int src_pdo_cnt = dpm_get_source_pdo(&src_pdo, port); - - if (src_pdo_cnt == 0) { - /* No source capabilities defined, sink only */ - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_REJECT); - } - - tx_emsg[port].len = src_pdo_cnt * 4; - memcpy(tx_emsg[port].buf, (uint8_t *)src_pdo, tx_emsg[port].len); - - send_data_msg(port, TCPCI_MSG_SOP, PD_DATA_SOURCE_CAP); -} - -/* - * Request desired charge voltage from source. - */ -static void pe_send_request_msg(int port) -{ - uint32_t vpd_vdo = 0; - uint32_t rdo; - uint32_t curr_limit; - uint32_t supply_voltage; - - /* - * If we are charging through a VPD, the requested voltage and current - * might need adjusting. - */ - if ((get_usb_pd_cable_type(port) == IDH_PTYPE_VPD) && - is_vpd_ct_supported(port)) { - union vpd_vdo vpd = pd_get_am_discovery(port, - TCPCI_MSG_SOP_PRIME)->identity.product_t1.vpd; - - /* The raw vpd_vdo is passed to pd_build_request */ - vpd_vdo = vpd.raw_value; - } - - /* Build and send request RDO */ - pd_build_request(vpd_vdo, &rdo, &curr_limit, - &supply_voltage, port); - - CPRINTF("C%d: Req [%d] %dmV %dmA", port, RDO_POS(rdo), - supply_voltage, curr_limit); - if (rdo & RDO_CAP_MISMATCH) - CPRINTF(" Mismatch"); - CPRINTF("\n"); - - pe[port].curr_limit = curr_limit; - pe[port].supply_voltage = supply_voltage; - - tx_emsg[port].len = 4; - - memcpy(tx_emsg[port].buf, (uint8_t *)&rdo, tx_emsg[port].len); - send_data_msg(port, TCPCI_MSG_SOP, PD_DATA_REQUEST); -} - -static void pe_update_src_pdo_flags(int port, int pdo_cnt, uint32_t *pdos) -{ - /* - * Only parse PDO flags if type is fixed - * - * Note: From 6.4.1 Capabilities Message "The vSafe5V Fixed Supply - * Object Shall always be the first object." so hitting this condition - * would mean the partner is voilating spec. - */ - if ((pdos[0] & PDO_TYPE_MASK) != PDO_TYPE_FIXED) - return; - - if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) { - if (pd_can_charge_from_device(port, pdo_cnt, pdos)) { - charge_manager_update_dualrole(port, CAP_DEDICATED); - } else { - charge_manager_update_dualrole(port, CAP_DUALROLE); - } - } -} - -/* - * Evaluate whether our PR role is in the middle of changing, meaning we our - * current PR role is not the one we expect to have very shortly. - */ -bool pe_is_pr_swapping(int port) -{ - enum usb_pe_state cur_state = get_state_pe(port); - - if (cur_state == PE_PRS_SRC_SNK_EVALUATE_SWAP || - cur_state == PE_PRS_SRC_SNK_TRANSITION_TO_OFF || - cur_state == PE_PRS_SNK_SRC_EVALUATE_SWAP || - cur_state == PE_PRS_SNK_SRC_TRANSITION_TO_OFF) - return true; - - return false; -} - -void pd_request_power_swap(int port) -{ - /* Ignore requests when the board does not wish to swap */ - if (!pd_check_power_swap(port)) - return; - - /* Ignore requests when our power role is transitioning */ - if (pe_is_pr_swapping(port)) - return; - - /* - * Always reset the SRC to SNK PR swap counter when a PR swap is - * requested by policy. - */ - pe[port].src_snk_pr_swap_counter = 0; - pd_dpm_request(port, DPM_REQUEST_PR_SWAP); -} - -/* The function returns true if there is a PE state change, false otherwise */ -static bool port_try_vconn_swap(int port) -{ - if (pe[port].vconn_swap_counter < N_VCONN_SWAP_COUNT) { - pd_dpm_request(port, DPM_REQUEST_VCONN_SWAP); - set_state_pe(port, get_last_state_pe(port)); - return true; - } - return false; -} - -/* - * Run discovery at our leisure from PE_SNK_Ready or PE_SRC_Ready, after - * attempting to get into the desired default policy of DFP/Vconn source - * - * Return indicates whether set_state was called, in which case the calling - * function should return as well. - */ -__maybe_unused static bool pe_attempt_port_discovery(int port) -{ - if (!IS_ENABLED(CONFIG_USB_PD_ALT_MODE_DFP)) - assert(0); - - /* - * DONE set once modal entry is successful, discovery completes, or - * discovery results in a NAK - */ - if (PE_CHK_FLAG(port, PE_FLAGS_VDM_SETUP_DONE)) - return false; - - /* Apply Port Discovery DR Swap Policy */ - if (port_discovery_dr_swap_policy(port, pe[port].data_role, - PE_CHK_FLAG(port, PE_FLAGS_DR_SWAP_TO_DFP))) { - PE_SET_FLAG(port, PE_FLAGS_LOCALLY_INITIATED_AMS); - PE_CLR_FLAG(port, PE_FLAGS_DR_SWAP_TO_DFP); - set_state_pe(port, PE_DRS_SEND_SWAP); - return true; - } - - /* Apply Port Discovery VCONN Swap Policy */ - if (IS_ENABLED(CONFIG_USBC_VCONN) && - port_discovery_vconn_swap_policy(port, - PE_CHK_FLAG(port, PE_FLAGS_VCONN_SWAP_TO_ON))) { - PE_SET_FLAG(port, PE_FLAGS_LOCALLY_INITIATED_AMS); - PE_CLR_FLAG(port, PE_FLAGS_VCONN_SWAP_TO_ON); - set_state_pe(port, PE_VCS_SEND_SWAP); - return true; - } - - /* If mode entry was successful, disable the timer */ - if (PE_CHK_FLAG(port, PE_FLAGS_VDM_SETUP_DONE)) { - pd_timer_disable(port, PE_TIMER_DISCOVER_IDENTITY); - return false; - } - - /* - * Run discovery functions when the timer indicating either cable - * discovery spacing or BUSY spacing runs out. - */ - if (pd_timer_is_expired(port, PE_TIMER_DISCOVER_IDENTITY)) { - if (pd_get_identity_discovery(port, TCPCI_MSG_SOP_PRIME) == - PD_DISC_NEEDED) { - pe[port].tx_type = TCPCI_MSG_SOP_PRIME; - set_state_pe(port, PE_VDM_IDENTITY_REQUEST_CBL); - return true; - } else if (pd_get_identity_discovery(port, TCPCI_MSG_SOP) == - PD_DISC_NEEDED && - pe_can_send_sop_vdm(port, CMD_DISCOVER_IDENT)) { - pe[port].tx_type = TCPCI_MSG_SOP; - set_state_pe(port, - PE_INIT_PORT_VDM_IDENTITY_REQUEST); - return true; - } else if (pd_get_svids_discovery(port, TCPCI_MSG_SOP) == - PD_DISC_NEEDED && - pe_can_send_sop_vdm(port, CMD_DISCOVER_SVID)) { - pe[port].tx_type = TCPCI_MSG_SOP; - set_state_pe(port, PE_INIT_VDM_SVIDS_REQUEST); - return true; - } else if (pd_get_modes_discovery(port, TCPCI_MSG_SOP) == - PD_DISC_NEEDED && - pe_can_send_sop_vdm(port, CMD_DISCOVER_MODES)) { - pe[port].tx_type = TCPCI_MSG_SOP; - set_state_pe(port, PE_INIT_VDM_MODES_REQUEST); - return true; - } else if (pd_get_svids_discovery(port, TCPCI_MSG_SOP_PRIME) - == PD_DISC_NEEDED) { - pe[port].tx_type = TCPCI_MSG_SOP_PRIME; - set_state_pe(port, PE_INIT_VDM_SVIDS_REQUEST); - return true; - } else if (pd_get_modes_discovery(port, TCPCI_MSG_SOP_PRIME) == - PD_DISC_NEEDED) { - pe[port].tx_type = TCPCI_MSG_SOP_PRIME; - set_state_pe(port, PE_INIT_VDM_MODES_REQUEST); - return true; - } - } - - return false; -} - -bool pd_setup_vdm_request(int port, enum tcpci_msg_type tx_type, - uint32_t *vdm, uint32_t vdo_cnt) -{ - if (vdo_cnt < VDO_HDR_SIZE || vdo_cnt > VDO_MAX_SIZE) - return false; - - pe[port].tx_type = tx_type; - memcpy(pe[port].vdm_data, vdm, vdo_cnt * sizeof(*vdm)); - pe[port].vdm_cnt = vdo_cnt; - - return true; -} - -int pd_dev_store_rw_hash(int port, uint16_t dev_id, uint32_t *rw_hash, - uint32_t current_image) -{ - pe[port].dev_id = dev_id; - memcpy(pe[port].dev_rw_hash, rw_hash, PD_RW_HASH_SIZE); -#ifdef CONFIG_CMD_PD_DEV_DUMP_INFO - pd_dev_dump_info(dev_id, rw_hash); -#endif - pe[port].current_image = current_image; - - if (IS_ENABLED(CONFIG_USB_PD_HOST_CMD)) { - int i; - - /* Search table for matching device / hash */ - for (i = 0; i < RW_HASH_ENTRIES; i++) - if (dev_id == rw_hash_table[i].dev_id) - return !memcmp(rw_hash, - rw_hash_table[i].dev_rw_hash, - PD_RW_HASH_SIZE); - } - - return 0; -} - -void pd_dev_get_rw_hash(int port, uint16_t *dev_id, uint8_t *rw_hash, - uint32_t *current_image) -{ - *dev_id = pe[port].dev_id; - *current_image = pe[port].current_image; - if (*dev_id) - memcpy(rw_hash, pe[port].dev_rw_hash, PD_RW_HASH_SIZE); -} - -/* - * This function must only be called from the PE_SNK_READY entry and - * PE_SRC_READY entry State. - * - * TODO(b:181339670) Rethink jitter timer restart if this is the first - * message but the partner gets a message in first, may not want to - * disable and restart it. - */ -static void pe_update_wait_and_add_jitter_timer(int port) -{ - /* - * In PD2.0 Mode - * - * For Source: - * Give the sink some time to send any messages - * before we may send messages of our own. Add - * some jitter of up to ~345ms, to prevent - * multiple collisions. This delay also allows - * the sink device to request power role swap - * and allow the the accept message to be sent - * prior to CMD_DISCOVER_IDENT being sent in the - * SRC_READY state. - * - * For Sink: - * Give the source some time to send any messages before - * we start our interrogation. Add some jitter of up to - * ~345ms to prevent multiple collisions. - */ - if (prl_get_rev(port, TCPCI_MSG_SOP) == PD_REV20 && - PE_CHK_FLAG(port, PE_FLAGS_FIRST_MSG) && - pd_timer_is_disabled(port, PE_TIMER_WAIT_AND_ADD_JITTER)) { - pd_timer_enable(port, PE_TIMER_WAIT_AND_ADD_JITTER, - SRC_SNK_READY_HOLD_OFF_US + - (get_time().le.lo & 0xf) * 23 * MSEC); - } -} - -/** - * Common sender response message handling - * - * This is setup like a pseudo state machine parent state. It - * centralizes the SenderResponseTimer for the calling states, as - * well as checking message send status. - */ -/* - * pe_sender_response_msg_entry - * Initialization for handling sender response messages. - * - * @param port USB-C Port number - */ -static void pe_sender_response_msg_entry(const int port) -{ - /* Stop sender response timer */ - pd_timer_disable(port, PE_TIMER_SENDER_RESPONSE); -} - -/* - * pe_sender_response_msg_run - * Check status of sender response messages. - * - * The normal progression of pe_sender_response_msg_entry is: - * PENDING -> (COMPLETED/SENT) -> SENT -> SENT ... - * or - * PENDING -> DISCARDED - * PENDING -> DPM_DISCARDED - * - * NOTE: it is not valid to call this function for a message after - * receiving either PE_MSG_DISCARDED or PE_MSG_DPM_DISCARDED until - * another message has been sent and pe_sender_response_msg_entry is called - * again. - * - * @param port USB-C Port number - * @return the current pe_msg_check - */ -static enum pe_msg_check pe_sender_response_msg_run(const int port) -{ - timestamp_t tx_success_ts; - uint32_t offset; - if (pd_timer_is_disabled(port, PE_TIMER_SENDER_RESPONSE)) { - /* Check for Discard */ - if (PE_CHK_FLAG(port, PE_FLAGS_MSG_DISCARDED)) { - int dpm_request = pe[port].dpm_curr_request; - - PE_CLR_FLAG(port, PE_FLAGS_MSG_DISCARDED); - /* Restore the DPM Request */ - if (dpm_request) { - PE_SET_DPM_REQUEST(port, dpm_request); - return PE_MSG_DPM_DISCARDED; - } - return PE_MSG_DISCARDED; - } - - /* Check for GoodCRC */ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - - /* TCPC TX success time stamp */ - tx_success_ts = prl_get_tcpc_tx_success_ts(port); - /* Calculate the delay from TX success to PE */ - offset = time_since32(tx_success_ts); - - /* - * Initialize and run the SenderResponseTimer by - * offsetting it with TX transmit success time. - * This would remove the effect of the latency from - * propagating the TX status. - */ - pd_timer_enable(port, PE_TIMER_SENDER_RESPONSE, - PD_T_SENDER_RESPONSE - offset); - return PE_MSG_SEND_COMPLETED; - } - return PE_MSG_SEND_PENDING; - } - return PE_MSG_SENT; -} - -/* - * pe_sender_response_msg_exit - * Exit cleanup for handling sender response messages. - * - * @param port USB-C Port number - */ -static void pe_sender_response_msg_exit(int port) -{ - pd_timer_disable(port, PE_TIMER_SENDER_RESPONSE); -} - -/** - * PE_SRC_Startup - */ -static void pe_src_startup_entry(int port) -{ - print_current_state(port); - - /* Reset CapsCounter */ - pe[port].caps_counter = 0; - - /* Reset the protocol layer */ - prl_reset_soft(port); - - /* Set initial data role */ - pe[port].data_role = pd_get_data_role(port); - - /* Set initial power role */ - pe[port].power_role = PD_ROLE_SOURCE; - - /* Clear explicit contract. */ - pe_invalidate_explicit_contract(port); - - if (PE_CHK_FLAG(port, PE_FLAGS_PR_SWAP_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_PR_SWAP_COMPLETE); - /* - * Protocol layer reset clears the message IDs for all SOP - * types. Indicate that a SOP' soft reset is required before any - * other messages are sent to the cable. - * - * Note that other paths into this state are for the initial - * connection and for a hard reset. In both cases the cable - * should also automatically clear the message IDs so don't - * generate an SOP' soft reset for those cases. Sending - * unnecessary SOP' soft resets causes bad behavior with - * some devices. See b/179325862. - */ - pd_dpm_request(port, DPM_REQUEST_SOP_PRIME_SOFT_RESET_SEND); - - /* Start SwapSourceStartTimer */ - pd_timer_enable(port, PE_TIMER_SWAP_SOURCE_START, - PD_T_SWAP_SOURCE_START); - - /* - * Evaluate port's sink caps for preferred current, if - * already available - */ - if (pd_get_snk_cap_cnt(port) > 0) - dpm_evaluate_sink_fixed_pdo(port, - *pd_get_snk_caps(port)); - - /* - * Remove prior FRS claims to 3.0 A now that sink current has - * been claimed, to avoid issues with lower priority ports - * potentially receiving a 3.0 A claim between calls. - */ - dpm_remove_source(port); - } else { - /* - * SwapSourceStartTimer delay is not needed, so trigger now. - * We can't use set_state_pe here, since we need to ensure that - * the protocol layer is running again (done in run function). - */ - pd_timer_enable(port, PE_TIMER_SWAP_SOURCE_START, 0); - - /* - * Set DiscoverIdentityTimer to trigger when we enter - * src_discovery for the first time. After initial startup - * set, vdm_identity_request_cbl will handle the timer updates. - */ - pd_timer_enable(port, PE_TIMER_DISCOVER_IDENTITY, 0); - - /* Clear port discovery/mode flags */ - pd_dfp_discovery_init(port); - pd_dfp_mode_init(port); - pe[port].ama_vdo = PD_VDO_INVALID; - pe[port].vpd_vdo = PD_VDO_INVALID; - pe[port].discover_identity_counter = 0; - - /* Reset dr swap attempt counter */ - pe[port].dr_swap_attempt_counter = 0; - - /* Reset VCONN swap counter */ - pe[port].vconn_swap_counter = 0; - - /* Request partner sink caps if a feature requires them */ - if (IS_ENABLED(CONFIG_USB_PD_HOST_CMD) || - CONFIG_USB_PD_3A_PORTS > 0 || - IS_ENABLED(CONFIG_USB_PD_FRS)) - pd_dpm_request(port, DPM_REQUEST_GET_SNK_CAPS); - } -} - -static void pe_src_startup_run(int port) -{ - /* Wait until protocol layer is running */ - if (!prl_is_running(port)) - return; - - if (pd_timer_is_expired(port, PE_TIMER_SWAP_SOURCE_START)) - set_state_pe(port, PE_SRC_SEND_CAPABILITIES); -} - -static void pe_src_startup_exit(int port) -{ - pd_timer_disable(port, PE_TIMER_SWAP_SOURCE_START); -} - -/** - * PE_SRC_Discovery - */ -static void pe_src_discovery_entry(int port) -{ - print_current_state(port); - - /* - * Initialize and run the SourceCapabilityTimer in order - * to trigger sending a Source_Capabilities Message. - * - * The SourceCapabilityTimer Shall continue to run during - * identity discover and Shall Not be initialized on re-entry - * to PE_SRC_Discovery. - * - * Note: Cable identity is the only valid VDM to probe before a contract - * is in place. All other probing must happen from ready states. - */ - if (get_last_state_pe(port) != PE_VDM_IDENTITY_REQUEST_CBL) - pd_timer_enable(port, PE_TIMER_SOURCE_CAP, - PD_T_SEND_SOURCE_CAP); -} - -static void pe_src_discovery_run(int port) -{ - /* - * Transition to the PE_SRC_Send_Capabilities state when: - * 1) The SourceCapabilityTimer times out and - * CapsCounter ≤ nCapsCount. - * - * Transition to the PE_SRC_Disabled state when: - * 1) The Port Partners are not presently PD Connected - * 2) And the SourceCapabilityTimer times out - * 3) And CapsCounter > nCapsCount. - * - * Transition to the PE_SRC_VDM_Identity_request state when: - * 1) DPM requests the identity of the cable plug and - * 2) DiscoverIdentityCounter < nDiscoverIdentityCount - */ - if (pd_timer_is_expired(port, PE_TIMER_SOURCE_CAP)) { - if (pe[port].caps_counter <= N_CAPS_COUNT) { - set_state_pe(port, PE_SRC_SEND_CAPABILITIES); - return; - } else if (!PE_CHK_FLAG(port, PE_FLAGS_PD_CONNECTION)) { - set_state_pe(port, PE_SRC_DISABLED); - return; - } - } - - /* - * Note: While the DiscoverIdentityTimer is only required in an explicit - * contract, we use it here to ensure we space any potential BUSY - * requests properly. - */ - if (pd_get_identity_discovery(port, TCPCI_MSG_SOP_PRIME) == - PD_DISC_NEEDED - && pd_timer_is_expired(port, PE_TIMER_DISCOVER_IDENTITY) - && pe_can_send_sop_prime(port) - && (pe[port].discover_identity_counter < - N_DISCOVER_IDENTITY_PRECONTRACT_LIMIT)) { - pe[port].tx_type = TCPCI_MSG_SOP_PRIME; - set_state_pe(port, PE_VDM_IDENTITY_REQUEST_CBL); - return; - } - - /* - * Transition to the PE_SRC_Disabled state when: - * 1) The Port Partners have not been PD Connected. - * 2) And the NoResponseTimer times out. - * 3) And the HardResetCounter > nHardResetCount. - */ - if (!PE_CHK_FLAG(port, PE_FLAGS_PD_CONNECTION) && - pd_timer_is_expired(port, PE_TIMER_NO_RESPONSE) && - pe[port].hard_reset_counter > N_HARD_RESET_COUNT) { - set_state_pe(port, PE_SRC_DISABLED); - return; - } -} - -/** - * PE_SRC_Send_Capabilities - */ -static void pe_src_send_capabilities_entry(int port) -{ - print_current_state(port); - - /* Send PD Capabilities message */ - send_source_cap(port); - pe_sender_response_msg_entry(port); - - /* Increment CapsCounter */ - pe[port].caps_counter++; -} - -static void pe_src_send_capabilities_run(int port) -{ - enum pe_msg_check msg_check; - - /* - * Check the state of the message sent - */ - msg_check = pe_sender_response_msg_run(port); - - /* - * Handle Discarded message - * PE_SNK/SRC_READY if DPM_REQUEST - * PE_SEND_SOFT_RESET otherwise - */ - if (msg_check == PE_MSG_DPM_DISCARDED) { - set_state_pe(port, PE_SRC_READY); - return; - } else if (msg_check == PE_MSG_DISCARDED) { - pe_send_soft_reset(port, TCPCI_MSG_SOP); - return; - } - - /* - * Handle message that was just sent - */ - if (msg_check == PE_MSG_SEND_COMPLETED) { - /* - * If a GoodCRC Message is received then the Policy Engine - * Shall: - * 1) Stop the NoResponseTimer. - * 2) Reset the HardResetCounter and CapsCounter to zero. - * 3) Initialize and run the SenderResponseTimer. - */ - /* Stop the NoResponseTimer */ - pd_timer_disable(port, PE_TIMER_NO_RESPONSE); - - /* Reset the HardResetCounter to zero */ - pe[port].hard_reset_counter = 0; - - /* Reset the CapsCounter to zero */ - pe[port].caps_counter = 0; - } - - /* - * Transition to the PE_SRC_Negotiate_Capability state when: - * 1) A Request Message is received from the Sink - */ - if ((msg_check & PE_MSG_SENT) && - PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - - /* - * Request Message Received? - */ - if (PD_HEADER_CNT(rx_emsg[port].header) > 0 && - PD_HEADER_TYPE(rx_emsg[port].header) == - PD_DATA_REQUEST) { - - /* - * Set to highest revision supported by both - * ports. - */ - prl_set_rev(port, TCPCI_MSG_SOP, - MIN(PD_REVISION, PD_HEADER_REV(rx_emsg[port].header))); - - set_cable_rev(port); - - /* We are PD connected */ - PE_SET_FLAG(port, PE_FLAGS_PD_CONNECTION); - tc_pd_connection(port, 1); - - /* - * Handle the Sink Request in - * PE_SRC_Negotiate_Capability state - */ - set_state_pe(port, PE_SRC_NEGOTIATE_CAPABILITY); - return; - } - - /* - * We have a Protocol Error. - * PE_SEND_SOFT_RESET - */ - pe_send_soft_reset(port, - PD_HEADER_GET_SOP(rx_emsg[port].header)); - return; - } - - /* - * Transition to the PE_SRC_Discovery state when: - * 1) The Protocol Layer indicates that the Message has not been sent - * and we are presently not Connected - * - * Send soft reset when: - * 1) The Protocol Layer indicates that the Message has not been sent - * and we are already Connected - * - * See section 8.3.3.4.1.1 PE_SRC_Send_Soft_Reset State and section - * 8.3.3.2.3 PE_SRC_Send_Capabilities State. - * - * NOTE: The PE_FLAGS_PROTOCOL_ERROR is set if a GoodCRC Message - * is not received. - */ - if (PE_CHK_FLAG(port, PE_FLAGS_PROTOCOL_ERROR)) { - PE_CLR_FLAG(port, PE_FLAGS_PROTOCOL_ERROR); - if (!PE_CHK_FLAG(port, PE_FLAGS_PD_CONNECTION)) - set_state_pe(port, PE_SRC_DISCOVERY); - else - pe_send_soft_reset(port, TCPCI_MSG_SOP); - return; - } - - /* - * Transition to the PE_SRC_Disabled state when: - * 1) The Port Partners have not been PD Connected - * 2) The NoResponseTimer times out - * 3) And the HardResetCounter > nHardResetCount. - * - * Transition to the Error Recovery state when: - * 1) The Port Partners have previously been PD Connected - * 2) The NoResponseTimer times out - * 3) And the HardResetCounter > nHardResetCount. - */ - if (pd_timer_is_expired(port, PE_TIMER_NO_RESPONSE)) { - if (pe[port].hard_reset_counter <= N_HARD_RESET_COUNT) - set_state_pe(port, PE_SRC_HARD_RESET); - else if (PE_CHK_FLAG(port, PE_FLAGS_PD_CONNECTION)) - set_state_pe(port, PE_WAIT_FOR_ERROR_RECOVERY); - else - set_state_pe(port, PE_SRC_DISABLED); - return; - } - - /* - * Transition to the PE_SRC_Hard_Reset state when: - * 1) The SenderResponseTimer times out. - */ - if (pd_timer_is_expired(port, PE_TIMER_SENDER_RESPONSE)) { - set_state_pe(port, PE_SRC_HARD_RESET); - return; - } -} - -static void pe_src_send_capabilities_exit(int port) -{ - pe_sender_response_msg_exit(port); -} - -/** - * PE_SRC_Negotiate_Capability - */ -static void pe_src_negotiate_capability_entry(int port) -{ - uint32_t payload; - - print_current_state(port); - - /* Get message payload */ - payload = *(uint32_t *)(&rx_emsg[port].buf); - - /* - * Evaluate the Request from the Attached Sink - */ - - /* - * Transition to the PE_SRC_Capability_Response state when: - * 1) The Request cannot be met. - * 2) Or the Request can be met later from the Power Reserve - * - * Transition to the PE_SRC_Transition_Supply state when: - * 1) The Request can be met - * - */ - if (pd_check_requested_voltage(payload, port) != EC_SUCCESS) { - set_state_pe(port, PE_SRC_CAPABILITY_RESPONSE); - } else { - PE_SET_FLAG(port, PE_FLAGS_ACCEPT); - pe[port].requested_idx = RDO_POS(payload); - set_state_pe(port, PE_SRC_TRANSITION_SUPPLY); - } -} - -/** - * PE_SRC_Transition_Supply - */ -static void pe_src_transition_supply_entry(int port) -{ - print_current_state(port); - - /* Send a GotoMin Message or otherwise an Accept Message */ - if (PE_CHK_FLAG(port, PE_FLAGS_ACCEPT)) { - PE_CLR_FLAG(port, PE_FLAGS_ACCEPT); - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_ACCEPT); - } else { - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_GOTO_MIN); - } -} - -static void pe_src_transition_supply_run(int port) -{ - /* - * Transition to the PE_SRC_Ready state when: - * 1) The power supply is ready. - * - * NOTE: This code block is executed twice: - * First Pass) - * When PE_FLAGS_TX_COMPLETE is set due to the - * PD_CTRL_ACCEPT or PD_CTRL_GOTO_MIN messages - * being sent. - * - * Second Pass) - * When PE_FLAGS_TX_COMPLETE is set due to the - * PD_CTRL_PS_RDY message being sent. - */ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - - /* - * NOTE: If a message was received, - * pe_src_ready state will handle it. - */ - - if (PE_CHK_FLAG(port, PE_FLAGS_PS_READY)) { - PE_CLR_FLAG(port, PE_FLAGS_PS_READY); - - /* - * Set first message flag to trigger a wait and add - * jitter delay when operating in PD2.0 mode. Skip - * if we already have a contract. - */ - if (!pe_is_explicit_contract(port)) { - PE_SET_FLAG(port, PE_FLAGS_FIRST_MSG); - pd_timer_disable(port, - PE_TIMER_WAIT_AND_ADD_JITTER); - } - - /* NOTE: Second pass through this code block */ - /* Explicit Contract is now in place */ - pe_set_explicit_contract(port); - - /* - * Setup to get Device Policy Manager to request - * Source Capabilities, if needed, for possible - * PR_Swap. Get the number directly to avoid re-probing - * if the partner generated an error and left -1 for the - * count. - */ - if (pe[port].src_cap_cnt == 0) - pd_dpm_request(port, DPM_REQUEST_GET_SRC_CAPS); - - set_state_pe(port, PE_SRC_READY); - } else { - /* NOTE: First pass through this code block */ - /* Wait for tSrcTransition before changing supply. */ - pd_timer_enable(port, PE_TIMER_SRC_TRANSITION, - PD_T_SRC_TRANSITION); - } - - return; - } - - if (pd_timer_is_expired(port, PE_TIMER_SRC_TRANSITION)) { - pd_timer_disable(port, PE_TIMER_SRC_TRANSITION); - /* Transition power supply and send PS_RDY. */ - pd_transition_voltage(pe[port].requested_idx); - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_PS_RDY); - PE_SET_FLAG(port, PE_FLAGS_PS_READY); - } - - /* - * Transition to the PE_SRC_Hard_Reset state when: - * 1) A Protocol Error occurs. - */ - if (PE_CHK_FLAG(port, PE_FLAGS_PROTOCOL_ERROR)) { - PE_CLR_FLAG(port, PE_FLAGS_PROTOCOL_ERROR); - set_state_pe(port, PE_SRC_HARD_RESET); - } -} - -static void pe_src_transition_supply_exit(int port) -{ - pd_timer_disable(port, PE_TIMER_SRC_TRANSITION); -} - -/* - * Transitions state after receiving a Not Supported extended message. Under - * appropriate conditions, transitions to a PE_{SRC,SNK}_Chunk_Received. - */ -static void extended_message_not_supported(int port, uint32_t *payload) -{ - uint16_t ext_header = GET_EXT_HEADER(*payload); - - if (IS_ENABLED(CONFIG_USB_PD_REV30) && - !IS_ENABLED(CONFIG_USB_PD_EXTENDED_MESSAGES) && - PD_EXT_HEADER_CHUNKED(ext_header) && - PD_EXT_HEADER_DATA_SIZE(ext_header) > - PD_MAX_EXTENDED_MSG_CHUNK_LEN) { - set_state_pe(port, - pe[port].power_role == PD_ROLE_SOURCE ? - PE_SRC_CHUNK_RECEIVED : PE_SNK_CHUNK_RECEIVED); - return; - } - - set_state_pe(port, PE_SEND_NOT_SUPPORTED); -} - -/** - * PE_SRC_Ready - */ -static void pe_src_ready_entry(int port) -{ - print_current_state(port); - - /* Ensure any message send flags are cleaned up */ - PE_CLR_FLAG(port, PE_FLAGS_READY_CLR); - - /* Clear DPM Current Request */ - pe[port].dpm_curr_request = 0; - - /* - * Wait and add jitter if we are operating in PD2.0 mode and no messages - * have been sent since enter this state. - */ - pe_update_wait_and_add_jitter_timer(port); -} - -static void pe_src_ready_run(int port) -{ - /* - * Handle incoming messages before discovery and DPMs other than hard - * reset - */ - if (PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - uint8_t type = PD_HEADER_TYPE(rx_emsg[port].header); - uint8_t cnt = PD_HEADER_CNT(rx_emsg[port].header); - uint8_t ext = PD_HEADER_EXT(rx_emsg[port].header); - uint32_t *payload = (uint32_t *)rx_emsg[port].buf; - - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - - /* Extended Message Requests */ - if (ext > 0) { - switch (type) { -#if defined(CONFIG_USB_PD_EXTENDED_MESSAGES) && defined(CONFIG_BATTERY) - case PD_EXT_GET_BATTERY_CAP: - set_state_pe(port, PE_GIVE_BATTERY_CAP); - break; - case PD_EXT_GET_BATTERY_STATUS: - set_state_pe(port, PE_GIVE_BATTERY_STATUS); - break; -#endif /* CONFIG_USB_PD_EXTENDED_MESSAGES && CONFIG_BATTERY */ - default: - extended_message_not_supported(port, payload); - } - return; - } - /* Data Message Requests */ - else if (cnt > 0) { - switch (type) { - case PD_DATA_REQUEST: - set_state_pe(port, PE_SRC_NEGOTIATE_CAPABILITY); - return; - case PD_DATA_SINK_CAP: - break; - case PD_DATA_VENDOR_DEF: - if (PD_HEADER_TYPE(rx_emsg[port].header) == - PD_DATA_VENDOR_DEF) { - if (PD_VDO_SVDM(*payload)) { - set_state_pe(port, - PE_VDM_RESPONSE); - } else - set_state_pe(port, - PE_HANDLE_CUSTOM_VDM_REQUEST); - } - return; - case PD_DATA_BIST: - set_state_pe(port, PE_BIST_TX); - return; - default: - set_state_pe(port, PE_SEND_NOT_SUPPORTED); - return; - } - } - /* Control Message Requests */ - else { - switch (type) { - case PD_CTRL_GOOD_CRC: - break; - case PD_CTRL_NOT_SUPPORTED: - break; - case PD_CTRL_PING: - break; - case PD_CTRL_GET_SOURCE_CAP: - set_state_pe(port, PE_SRC_SEND_CAPABILITIES); - return; - case PD_CTRL_GET_SINK_CAP: - set_state_pe(port, PE_SNK_GIVE_SINK_CAP); - return; - case PD_CTRL_GOTO_MIN: - break; - case PD_CTRL_PR_SWAP: - set_state_pe(port, - PE_PRS_SRC_SNK_EVALUATE_SWAP); - return; - case PD_CTRL_DR_SWAP: - if (PE_CHK_FLAG(port, - PE_FLAGS_MODAL_OPERATION)) { - set_state_pe(port, PE_SRC_HARD_RESET); - return; - } - - set_state_pe(port, PE_DRS_EVALUATE_SWAP); - return; - case PD_CTRL_VCONN_SWAP: - if (IS_ENABLED(CONFIG_USBC_VCONN)) - set_state_pe(port, - PE_VCS_EVALUATE_SWAP); - else - set_state_pe(port, - PE_SEND_NOT_SUPPORTED); - return; - /* - * USB PD 3.0 6.8.1: - * Receiving an unexpected message shall be responded - * to with a soft reset message. - */ - case PD_CTRL_ACCEPT: - case PD_CTRL_REJECT: - case PD_CTRL_WAIT: - case PD_CTRL_PS_RDY: - pe_send_soft_reset(port, - PD_HEADER_GET_SOP(rx_emsg[port].header)); - return; - /* - * Receiving an unknown or unsupported message - * shall be responded to with a not supported message. - */ - default: - set_state_pe(port, PE_SEND_NOT_SUPPORTED); - return; - } - } - } - - /* - * Make sure the PRL layer isn't busy with receiving or transmitting - * chunked messages before attempting to transmit a new message. - */ - if (prl_is_busy(port)) - return; - - if (PE_CHK_FLAG(port, PE_FLAGS_VDM_REQUEST_CONTINUE)) { - PE_CLR_FLAG(port, PE_FLAGS_VDM_REQUEST_CONTINUE); - set_state_pe(port, PE_VDM_REQUEST_DPM); - return; - } - - if (PE_CHK_FLAG(port, PE_FLAGS_WAITING_PR_SWAP) && - pd_timer_is_expired(port, PE_TIMER_PR_SWAP_WAIT)) { - PE_CLR_FLAG(port, PE_FLAGS_WAITING_PR_SWAP); - PE_SET_DPM_REQUEST(port, DPM_REQUEST_PR_SWAP); - } - - if (pd_timer_is_disabled(port, PE_TIMER_WAIT_AND_ADD_JITTER) || - pd_timer_is_expired(port, PE_TIMER_WAIT_AND_ADD_JITTER)) { - - PE_CLR_FLAG(port, PE_FLAGS_FIRST_MSG); - pd_timer_disable(port, PE_TIMER_WAIT_AND_ADD_JITTER); - - /* - * Handle Device Policy Manager Requests - */ - if (source_dpm_requests(port)) - return; - - /* - * Attempt discovery if possible, and return if state was - * changed for that discovery. - */ - if (pe_attempt_port_discovery(port)) - return; - - /* No DPM requests; attempt mode entry/exit if needed */ - dpm_run(port); - } -} - -/** - * PE_SRC_Disabled - */ -static void pe_src_disabled_entry(int port) -{ - print_current_state(port); - - if ((get_usb_pd_cable_type(port) == IDH_PTYPE_VPD) && - is_vpd_ct_supported(port)) { - /* - * Inform the Device Policy Manager that a Charge-Through VCONN - * Powered Device was detected. - */ - tc_ctvpd_detected(port); - } - - if (pd_get_power_role(port) == PD_ROLE_SOURCE) - dpm_add_non_pd_sink(port); - - /* - * Unresponsive to USB Power Delivery messaging, but not to Hard Reset - * Signaling. See pe_got_hard_reset - */ -} - -/** - * PE_SRC_Capability_Response - */ -static void pe_src_capability_response_entry(int port) -{ - print_current_state(port); - - /* NOTE: Wait messaging should be implemented. */ - - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_REJECT); -} - -static void pe_src_capability_response_run(int port) -{ - /* - * Transition to the PE_SRC_Ready state when: - * 1) There is an Explicit Contract and - * 2) A Reject Message has been sent and the present Contract is still - * Valid or - * 3) A Wait Message has been sent. - * - * Transition to the PE_SRC_Hard_Reset state when: - * 1) There is an Explicit Contract and - * 2) The Reject Message has been sent and the present - * Contract is Invalid - * - * Transition to the PE_SRC_Wait_New_Capabilities state when: - * 1) There is no Explicit Contract and - * 2) A Reject Message has been sent or - * 3) A Wait Message has been sent. - */ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - - if (PE_CHK_FLAG(port, PE_FLAGS_EXPLICIT_CONTRACT)) - /* - * NOTE: The src capabilities listed in - * board/xxx/usb_pd_policy.c will not - * change so the present contract will - * never be invalid. - */ - set_state_pe(port, PE_SRC_READY); - else - /* - * NOTE: The src capabilities listed in - * board/xxx/usb_pd_policy.c will not - * change, so no need to resending them - * again. Transition to disabled state. - */ - set_state_pe(port, PE_SRC_DISABLED); - } -} - -/** - * PE_SRC_Hard_Reset - */ -static void pe_src_hard_reset_entry(int port) -{ - print_current_state(port); - - /* Generate Hard Reset Signal */ - prl_execute_hard_reset(port); - - /* Increment the HardResetCounter */ - pe[port].hard_reset_counter++; - - /* Start NoResponseTimer */ - pd_timer_enable(port, PE_TIMER_NO_RESPONSE, PD_T_NO_RESPONSE); - - /* Start PSHardResetTimer */ - pd_timer_enable(port, PE_TIMER_PS_HARD_RESET, PD_T_PS_HARD_RESET); - - /* Clear error flags */ - PE_CLR_FLAG(port, PE_FLAGS_VDM_REQUEST_NAKED | - PE_FLAGS_PROTOCOL_ERROR | - PE_FLAGS_VDM_REQUEST_BUSY); -} - -static void pe_src_hard_reset_run(int port) -{ - /* - * Transition to the PE_SRC_Transition_to_default state when: - * 1) The PSHardResetTimer times out. - */ - if (pd_timer_is_expired(port, PE_TIMER_PS_HARD_RESET)) - set_state_pe(port, PE_SRC_TRANSITION_TO_DEFAULT); -} - -static void pe_src_hard_reset_exit(int port) -{ - pd_timer_disable(port, PE_TIMER_PS_HARD_RESET); -} - -/** - * PE_SRC_Hard_Reset_Received - */ -static void pe_src_hard_reset_received_entry(int port) -{ - print_current_state(port); - - /* Start NoResponseTimer */ - pd_timer_enable(port, PE_TIMER_NO_RESPONSE, PD_T_NO_RESPONSE); - - /* Start PSHardResetTimer */ - pd_timer_enable(port, PE_TIMER_PS_HARD_RESET, PD_T_PS_HARD_RESET); -} - -static void pe_src_hard_reset_received_run(int port) -{ - /* - * Transition to the PE_SRC_Transition_to_default state when: - * 1) The PSHardResetTimer times out. - */ - if (pd_timer_is_expired(port, PE_TIMER_PS_HARD_RESET)) - set_state_pe(port, PE_SRC_TRANSITION_TO_DEFAULT); -} - -static void pe_src_hard_reset_received_exit(int port) -{ - pd_timer_disable(port, PE_TIMER_PS_HARD_RESET); -} - -/** - * PE_SRC_Transition_To_Default - */ -static void pe_src_transition_to_default_entry(int port) -{ - print_current_state(port); - - /* Reset flags */ - pe[port].flags = 0; - - /* Reset DPM Request */ - pe[port].dpm_request = 0; - - /* - * Request Device Policy Manager to request power - * supply Hard Resets to vSafe5V via vSafe0V - * Reset local HW - * Request Device Policy Manager to set Port Data - * Role to DFP and turn off VCONN - */ - tc_hard_reset_request(port); -} - -static void pe_src_transition_to_default_run(int port) -{ - /* - * Transition to the PE_SRC_Startup state when: - * 1) The power supply has reached the default level. - */ - if (PE_CHK_FLAG(port, PE_FLAGS_PS_RESET_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_PS_RESET_COMPLETE); - /* Inform the Protocol Layer that the Hard Reset is complete */ - prl_hard_reset_complete(port); - set_state_pe(port, PE_SRC_STARTUP); - } -} - -/** - * PE_SNK_Startup State - */ -static void pe_snk_startup_entry(int port) -{ - print_current_state(port); - - /* Reset the protocol layer */ - prl_reset_soft(port); - - /* Set initial data role */ - pe[port].data_role = pd_get_data_role(port); - - /* Set initial power role */ - pe[port].power_role = PD_ROLE_SINK; - - /* Invalidate explicit contract */ - pe_invalidate_explicit_contract(port); - - if (PE_CHK_FLAG(port, PE_FLAGS_PR_SWAP_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_PR_SWAP_COMPLETE); - /* - * Protocol layer reset clears the message IDs for all SOP - * types. Indicate that a SOP' soft reset is required before any - * other messages are sent to the cable. - * - * Note that other paths into this state are for the initial - * connection and for a hard reset. In both cases the cable - * should also automatically clear the message IDs so don't - * generate an SOP' soft reset for those cases. Sending - * unnecessary SOP' soft resets causes bad behavior with - * some devices. See b/179325862. - */ - pd_dpm_request(port, DPM_REQUEST_SOP_PRIME_SOFT_RESET_SEND); - - /* - * Some port partners may violate spec and attempt to - * communicate with the cable after power role swaps, despite - * not being Vconn source. Disable our SOP' receiving here to - * avoid GoodCRC-ing any erroneous cable probes, and re-enable - * after our contract is in place. - */ - if (tc_is_vconn_src(port)) - tcpm_sop_prime_enable(port, false); - - dpm_remove_sink(port); - } else { - /* - * Set DiscoverIdentityTimer to trigger when we enter - * snk_ready for the first time. - */ - pd_timer_enable(port, PE_TIMER_DISCOVER_IDENTITY, 0); - - /* Clear port discovery/mode flags */ - pd_dfp_discovery_init(port); - pd_dfp_mode_init(port); - pe[port].discover_identity_counter = 0; - - /* Reset dr swap attempt counter */ - pe[port].dr_swap_attempt_counter = 0; - - /* Reset VCONN swap counter */ - pe[port].vconn_swap_counter = 0; - /* - * TODO: POLICY decision: - * Mark that we'd like to try being Vconn source and DFP - */ - PE_SET_FLAG(port, PE_FLAGS_DR_SWAP_TO_DFP); - PE_SET_FLAG(port, PE_FLAGS_VCONN_SWAP_TO_ON); - } - - /* - * Request sink caps for FRS, output power consideration, or reporting - * to the AP through host commands. - * - * On entry to the PE_SNK_Ready state if the Sink supports Fast Role - * Swap, then the Policy Engine Shall do the following: - * - Send a Get_Sink_Cap Message - */ - if (IS_ENABLED(CONFIG_USB_PD_HOST_CMD) || - CONFIG_USB_PD_3A_PORTS > 0 || - IS_ENABLED(CONFIG_USB_PD_FRS)) - pd_dpm_request(port, DPM_REQUEST_GET_SNK_CAPS); - -} - -static void pe_snk_startup_run(int port) -{ - /* Wait until protocol layer is running */ - if (!prl_is_running(port)) - return; - - /* - * Once the reset process completes, the Policy Engine Shall - * transition to the PE_SNK_Discovery state - */ - set_state_pe(port, PE_SNK_DISCOVERY); -} - -/** - * PE_SNK_Discovery State - */ -static void pe_snk_discovery_entry(int port) -{ - print_current_state(port); -} - -static void pe_snk_discovery_run(int port) -{ - /* - * Transition to the PE_SNK_Wait_for_Capabilities state when: - * 1) VBUS has been detected - */ - if (!pd_check_vbus_level(port, VBUS_REMOVED)) - set_state_pe(port, PE_SNK_WAIT_FOR_CAPABILITIES); -} - -/** - * PE_SNK_Wait_For_Capabilities State - */ -static void pe_snk_wait_for_capabilities_entry(int port) -{ - print_current_state(port); - - /* Initialize and start the SinkWaitCapTimer */ - pd_timer_enable(port, PE_TIMER_TIMEOUT, PD_T_SINK_WAIT_CAP); -} - -static void pe_snk_wait_for_capabilities_run(int port) -{ - uint8_t type; - uint8_t cnt; - uint8_t ext; - - /* - * Transition to the PE_SNK_Evaluate_Capability state when: - * 1) A Source_Capabilities Message is received. - */ - if (PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - - type = PD_HEADER_TYPE(rx_emsg[port].header); - cnt = PD_HEADER_CNT(rx_emsg[port].header); - ext = PD_HEADER_EXT(rx_emsg[port].header); - - if ((ext == 0) && (cnt > 0) && (type == PD_DATA_SOURCE_CAP)) { - set_state_pe(port, PE_SNK_EVALUATE_CAPABILITY); - return; - } - } - - /* When the SinkWaitCapTimer times out, perform a Hard Reset. */ - if (pd_timer_is_expired(port, PE_TIMER_TIMEOUT)) { - PE_SET_FLAG(port, PE_FLAGS_SNK_WAIT_CAP_TIMEOUT); - set_state_pe(port, PE_SNK_HARD_RESET); - } -} - -static void pe_snk_wait_for_capabilities_exit(int port) -{ - pd_timer_disable(port, PE_TIMER_TIMEOUT); -} - -/** - * PE_SNK_Evaluate_Capability State - */ -static void pe_snk_evaluate_capability_entry(int port) -{ - uint32_t *pdo = (uint32_t *)rx_emsg[port].buf; - uint32_t num = rx_emsg[port].len >> 2; - - print_current_state(port); - - /* Reset Hard Reset counter to zero */ - pe[port].hard_reset_counter = 0; - - /* Set to highest revision supported by both ports. */ - prl_set_rev(port, TCPCI_MSG_SOP, - MIN(PD_REVISION, PD_HEADER_REV(rx_emsg[port].header))); - - set_cable_rev(port); - - /* Parse source caps if they have changed */ - if (pe[port].src_cap_cnt != num || - memcmp(pdo, pe[port].src_caps, num << 2)) { - /* - * If port policy preference is to be a power role source, - * then request a power role swap. If we'd previously queued a - * PR swap but can now charge from this device, clear it. - */ - if (!pd_can_charge_from_device(port, num, pdo)) - pd_request_power_swap(port); - else - PE_CLR_DPM_REQUEST(port, DPM_REQUEST_PR_SWAP); - } - - pe_update_src_pdo_flags(port, num, pdo); - pd_set_src_caps(port, num, pdo); - - /* Evaluate the options based on supplied capabilities */ - pd_process_source_cap(port, pe[port].src_cap_cnt, pe[port].src_caps); - - /* Device Policy Response Received */ - set_state_pe(port, PE_SNK_SELECT_CAPABILITY); - -#ifdef HAS_TASK_DPS - /* Wake DPS task to evaluate the SrcCaps */ - task_wake(TASK_ID_DPS); -#endif -} - -/** - * PE_SNK_Select_Capability State - */ -static void pe_snk_select_capability_entry(int port) -{ - print_current_state(port); - - /* Send Request */ - pe_send_request_msg(port); - pe_sender_response_msg_entry(port); - - /* We are PD Connected */ - PE_SET_FLAG(port, PE_FLAGS_PD_CONNECTION); - tc_pd_connection(port, 1); -} - -static void pe_snk_select_capability_run(int port) -{ - uint8_t type; - uint8_t cnt; - enum tcpci_msg_type sop; - enum pe_msg_check msg_check; - - /* - * Check the state of the message sent - */ - msg_check = pe_sender_response_msg_run(port); - - /* - * Handle discarded message - */ - if (msg_check & PE_MSG_DISCARDED) { - /* - * The sent REQUEST message was discarded. This can be at - * the start of an AMS or in the middle. Handle what to - * do based on where we came from. - * 1) SE_SNK_EVALUATE_CAPABILITY: sends SoftReset - * 2) SE_SNK_READY: goes back to SNK Ready - */ - if (get_last_state_pe(port) == PE_SNK_EVALUATE_CAPABILITY) - pe_send_soft_reset(port, TCPCI_MSG_SOP); - else - set_state_pe(port, PE_SNK_READY); - return; - } - - if ((msg_check & PE_MSG_SENT) && - PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - type = PD_HEADER_TYPE(rx_emsg[port].header); - cnt = PD_HEADER_CNT(rx_emsg[port].header); - sop = PD_HEADER_GET_SOP(rx_emsg[port].header); - - /* - * Transition to the PE_SNK_Transition_Sink state when: - * 1) An Accept Message is received from the Source. - * - * Transition to the PE_SNK_Wait_for_Capabilities state when: - * 1) There is no Explicit Contract in place and - * 2) A Reject Message is received from the Source or - * 3) A Wait Message is received from the Source. - * - * Transition to the PE_SNK_Ready state when: - * 1) There is an Explicit Contract in place and - * 2) A Reject Message is received from the Source or - * 3) A Wait Message is received from the Source. - * - * Transition to the PE_SNK_Hard_Reset state when: - * 1) A SenderResponseTimer timeout occurs. - */ - - /* Only look at control messages */ - if (cnt == 0) { - /* - * Accept Message Received - */ - if (type == PD_CTRL_ACCEPT) { - /* explicit contract is now in place */ - pe_set_explicit_contract(port); - - set_state_pe(port, PE_SNK_TRANSITION_SINK); - - return; - } - /* - * Reject or Wait Message Received - */ - else if (type == PD_CTRL_REJECT || - type == PD_CTRL_WAIT) { - if (type == PD_CTRL_WAIT) - PE_SET_FLAG(port, PE_FLAGS_WAIT); - - pd_timer_disable(port, PE_TIMER_SINK_REQUEST); - - /* - * We had a previous explicit contract, so - * transition to PE_SNK_Ready - */ - if (PE_CHK_FLAG(port, - PE_FLAGS_EXPLICIT_CONTRACT)) - set_state_pe(port, PE_SNK_READY); - /* - * No previous explicit contract, so transition - * to PE_SNK_Wait_For_Capabilities - */ - else - set_state_pe(port, - PE_SNK_WAIT_FOR_CAPABILITIES); - return; - } - /* - * Unexpected Control Message Received - */ - else { - /* Send Soft Reset */ - pe_send_soft_reset(port, sop); - return; - } - } - /* - * Unexpected Data Message - */ - else { - /* Send Soft Reset */ - pe_send_soft_reset(port, sop); - return; - } - } - - /* SenderResponsetimer timeout */ - if (pd_timer_is_expired(port, PE_TIMER_SENDER_RESPONSE)) - set_state_pe(port, PE_SNK_HARD_RESET); -} - -void pe_snk_select_capability_exit(int port) -{ - pe_sender_response_msg_exit(port); -} - -/** - * PE_SNK_Transition_Sink State - */ -static void pe_snk_transition_sink_entry(int port) -{ - print_current_state(port); - - /* Initialize and run PSTransitionTimer */ - pd_timer_enable(port, PE_TIMER_PS_TRANSITION, PD_T_PS_TRANSITION); -} - -static void pe_snk_transition_sink_run(int port) -{ - /* - * Transition to the PE_SNK_Ready state when: - * 1) A PS_RDY Message is received from the Source. - * - * Transition to the PE_SNK_Hard_Reset state when: - * 1) A Protocol Error occurs. - */ - - if (PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - - /* - * PS_RDY message received - */ - if ((PD_HEADER_CNT(rx_emsg[port].header) == 0) && - (PD_HEADER_TYPE(rx_emsg[port].header) == - PD_CTRL_PS_RDY)) { - /* - * Set first message flag to trigger a wait and add - * jitter delay when operating in PD2.0 mode. - */ - PE_SET_FLAG(port, PE_FLAGS_FIRST_MSG); - pd_timer_disable(port, PE_TIMER_WAIT_AND_ADD_JITTER); - - /* - * If we've successfully completed our new power - * contract, ensure SOP' communication is enabled before - * entering PE_SNK_READY. It may have been disabled - * during a power role swap to avoid interoperability - * issues with out-of-spec partners. - */ - if (tc_is_vconn_src(port)) - tcpm_sop_prime_enable(port, true); - - /* - * Evaluate port's sink caps for FRS current, if - * already available - */ - if (pd_get_snk_cap_cnt(port) > 0) - dpm_evaluate_sink_fixed_pdo(port, - *pd_get_snk_caps(port)); - - set_state_pe(port, PE_SNK_READY); - } else { - /* - * Protocol Error - */ - set_state_pe(port, PE_SNK_HARD_RESET); - } - return; - } - - /* - * Timeout will lead to a Hard Reset - */ - if (pd_timer_is_expired(port, PE_TIMER_PS_TRANSITION) && - pe[port].hard_reset_counter <= N_HARD_RESET_COUNT) { - PE_SET_FLAG(port, PE_FLAGS_PS_TRANSITION_TIMEOUT); - - set_state_pe(port, PE_SNK_HARD_RESET); - } -} - -static void pe_snk_transition_sink_exit(int port) -{ - /* Transition Sink's power supply to the new power level */ - pd_set_input_current_limit(port, - pe[port].curr_limit, pe[port].supply_voltage); - - if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) - /* Set ceiling based on what's negotiated */ - charge_manager_set_ceil(port, - CEIL_REQUESTOR_PD, pe[port].curr_limit); - - pd_timer_disable(port, PE_TIMER_PS_TRANSITION); - - if (IS_ENABLED(CONFIG_USB_PD_DPS)) - if (charge_manager_get_active_charge_port() == port) - dps_update_stabilized_time(port); -} - - -/** - * PE_SNK_Ready State - */ -static void pe_snk_ready_entry(int port) -{ - print_current_state(port); - - /* Ensure any message send flags are cleaned up */ - PE_CLR_FLAG(port, PE_FLAGS_READY_CLR); - - /* Clear DPM Current Request */ - pe[port].dpm_curr_request = 0; - - /* - * On entry to the PE_SNK_Ready state as the result of a wait, - * then do the following: - * 1) Initialize and run the SinkRequestTimer - */ - if (PE_CHK_FLAG(port, PE_FLAGS_WAIT)) { - PE_CLR_FLAG(port, PE_FLAGS_WAIT); - pd_timer_enable(port, PE_TIMER_SINK_REQUEST, - PD_T_SINK_REQUEST); - } - - /* - * Wait and add jitter if we are operating in PD2.0 mode and no messages - * have been sent since enter this state. - */ - pe_update_wait_and_add_jitter_timer(port); -} - -static void pe_snk_ready_run(int port) -{ - /* - * Handle incoming messages before discovery and DPMs other than hard - * reset - */ - if (PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - uint8_t type = PD_HEADER_TYPE(rx_emsg[port].header); - uint8_t cnt = PD_HEADER_CNT(rx_emsg[port].header); - uint8_t ext = PD_HEADER_EXT(rx_emsg[port].header); - uint32_t *payload = (uint32_t *)rx_emsg[port].buf; - - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - - /* Extended Message Request */ - if (ext > 0) { - switch (type) { -#if defined(CONFIG_USB_PD_EXTENDED_MESSAGES) && defined(CONFIG_BATTERY) - case PD_EXT_GET_BATTERY_CAP: - set_state_pe(port, PE_GIVE_BATTERY_CAP); - break; - case PD_EXT_GET_BATTERY_STATUS: - set_state_pe(port, PE_GIVE_BATTERY_STATUS); - break; -#endif /* CONFIG_USB_PD_EXTENDED_MESSAGES && CONFIG_BATTERY */ - default: - extended_message_not_supported(port, payload); - } - return; - } - /* Data Messages */ - else if (cnt > 0) { - switch (type) { - case PD_DATA_SOURCE_CAP: - set_state_pe(port, - PE_SNK_EVALUATE_CAPABILITY); - break; - case PD_DATA_VENDOR_DEF: - if (PD_HEADER_TYPE(rx_emsg[port].header) == - PD_DATA_VENDOR_DEF) { - if (PD_VDO_SVDM(*payload)) - set_state_pe(port, - PE_VDM_RESPONSE); - else - set_state_pe(port, - PE_HANDLE_CUSTOM_VDM_REQUEST); - } - break; - case PD_DATA_BIST: - set_state_pe(port, PE_BIST_TX); - break; - default: - set_state_pe(port, PE_SEND_NOT_SUPPORTED); - } - return; - } - /* Control Messages */ - else { - switch (type) { - case PD_CTRL_GOOD_CRC: - /* Do nothing */ - break; - case PD_CTRL_PING: - /* Do nothing */ - break; - case PD_CTRL_GET_SOURCE_CAP: - set_state_pe(port, PE_DR_SNK_GIVE_SOURCE_CAP); - return; - case PD_CTRL_GET_SINK_CAP: - set_state_pe(port, PE_SNK_GIVE_SINK_CAP); - return; - case PD_CTRL_GOTO_MIN: - set_state_pe(port, PE_SNK_TRANSITION_SINK); - return; - case PD_CTRL_PR_SWAP: - set_state_pe(port, - PE_PRS_SNK_SRC_EVALUATE_SWAP); - return; - case PD_CTRL_DR_SWAP: - if (PE_CHK_FLAG(port, PE_FLAGS_MODAL_OPERATION)) - set_state_pe(port, PE_SNK_HARD_RESET); - else - set_state_pe(port, - PE_DRS_EVALUATE_SWAP); - return; - case PD_CTRL_VCONN_SWAP: - if (IS_ENABLED(CONFIG_USBC_VCONN)) - set_state_pe(port, - PE_VCS_EVALUATE_SWAP); - else - set_state_pe(port, - PE_SEND_NOT_SUPPORTED); - return; - case PD_CTRL_NOT_SUPPORTED: - /* Do nothing */ - break; - /* - * USB PD 3.0 6.8.1: - * Receiving an unexpected message shall be responded - * to with a soft reset message. - */ - case PD_CTRL_ACCEPT: - case PD_CTRL_REJECT: - case PD_CTRL_WAIT: - case PD_CTRL_PS_RDY: - pe_send_soft_reset(port, - PD_HEADER_GET_SOP(rx_emsg[port].header)); - return; - /* - * Receiving an unknown or unsupported message - * shall be responded to with a not supported message. - */ - default: - set_state_pe(port, PE_SEND_NOT_SUPPORTED); - return; - } - } - } - - /* - * Make sure the PRL layer isn't busy with receiving or transmitting - * chunked messages before attempting to transmit a new message. - */ - if (prl_is_busy(port)) - return; - - if (PE_CHK_FLAG(port, PE_FLAGS_VDM_REQUEST_CONTINUE)) { - PE_CLR_FLAG(port, PE_FLAGS_VDM_REQUEST_CONTINUE); - set_state_pe(port, PE_VDM_REQUEST_DPM); - return; - } - - if (pd_timer_is_disabled(port, PE_TIMER_WAIT_AND_ADD_JITTER) || - pd_timer_is_expired(port, PE_TIMER_WAIT_AND_ADD_JITTER)) { - PE_CLR_FLAG(port, PE_FLAGS_FIRST_MSG); - pd_timer_disable(port, PE_TIMER_WAIT_AND_ADD_JITTER); - - if (pd_timer_is_expired(port, PE_TIMER_SINK_REQUEST)) { - pd_timer_disable(port, PE_TIMER_SINK_REQUEST); - set_state_pe(port, PE_SNK_SELECT_CAPABILITY); - return; - } - - /* - * Handle Device Policy Manager Requests - */ - if (sink_dpm_requests(port)) - return; - - /* - * Attempt discovery if possible, and return if state was - * changed for that discovery. - */ - if (pe_attempt_port_discovery(port)) - return; - - /* No DPM requests; attempt mode entry/exit if needed */ - dpm_run(port); - - } -} - -/** - * PE_SNK_Hard_Reset - */ -static void pe_snk_hard_reset_entry(int port) -{ -#ifdef CONFIG_USB_PD_RESET_MIN_BATT_SOC - int batt_soc; -#endif - - print_current_state(port); - - /* - * Note: If the SinkWaitCapTimer times out and the HardResetCounter is - * greater than nHardResetCount the Sink Shall assume that the - * Source is non-responsive. - */ - if (PE_CHK_FLAG(port, PE_FLAGS_SNK_WAIT_CAP_TIMEOUT) && - pe[port].hard_reset_counter > N_HARD_RESET_COUNT) { - set_state_pe(port, PE_SRC_DISABLED); - return; - } - - /* - * If we're about to kill our active charge port and have no battery to - * supply power, disable the PE layer instead. If we have no battery, - * but we haven't determined our active charge port yet, also avoid - * performing the HardReset. It might be that this port was our active - * charge port. - * - * Note: On systems without batteries (ex. chromeboxes), it's preferable - * to brown out rather than leave the port only semi-functional for a - * customer. For systems which should have a battery, this condition is - * not expected to be encountered by a customer. - */ - if (IS_ENABLED(CONFIG_BATTERY) && (battery_is_present() == BP_NO) && - IS_ENABLED(CONFIG_CHARGE_MANAGER) && - ((port == charge_manager_get_active_charge_port() || - (charge_manager_get_active_charge_port() == CHARGE_PORT_NONE))) && - system_get_reset_flags() & EC_RESET_FLAG_SYSJUMP) { - CPRINTS("C%d: Disabling port to avoid brown out, " - "please reboot EC to enable port again", port); - set_state_pe(port, PE_SRC_DISABLED); - return; - - } - -#ifdef CONFIG_USB_PD_RESET_MIN_BATT_SOC - /* - * If the battery has not met a configured safe level for hard - * resets, set state to PE_SRC_Disabled as a hard - * reset could brown out the board. - * Note this may mean that high-power chargers will stay at - * 15W until a reset is sent, depending on boot timing. - * - * PE_FLAGS_SNK_WAITING_BATT flags will be cleared and - * PE state will be switched to PE_SNK_Startup when - * battery reaches CONFIG_USB_PD_RESET_MIN_BATT_SOC. - * See pe_update_waiting_batt_flag() for more details. - */ - batt_soc = usb_get_battery_soc(); - - if (batt_soc < CONFIG_USB_PD_RESET_MIN_BATT_SOC || - battery_get_disconnect_state() != BATTERY_NOT_DISCONNECTED) { - PE_SET_FLAG(port, PE_FLAGS_SNK_WAITING_BATT); - CPRINTS("C%d: Battery low %d%%! Stay in disabled state " \ - "until battery level reaches %d%%", port, batt_soc, - CONFIG_USB_PD_RESET_MIN_BATT_SOC); - set_state_pe(port, PE_SRC_DISABLED); - return; - } -#endif - - PE_CLR_FLAG(port, PE_FLAGS_SNK_WAIT_CAP_TIMEOUT | - PE_FLAGS_VDM_REQUEST_NAKED | - PE_FLAGS_PROTOCOL_ERROR | - PE_FLAGS_VDM_REQUEST_BUSY); - - /* Request the generation of Hard Reset Signaling by the PHY Layer */ - prl_execute_hard_reset(port); - - /* Increment the HardResetCounter */ - pe[port].hard_reset_counter++; - - /* - * Transition the Sink’s power supply to the new power level if - * PSTransistionTimer timeout occurred. - */ - if (PE_CHK_FLAG(port, PE_FLAGS_PS_TRANSITION_TIMEOUT)) { - PE_CLR_FLAG(port, PE_FLAGS_PS_TRANSITION_TIMEOUT); - - /* Transition Sink's power supply to the new power level */ - pd_set_input_current_limit(port, pe[port].curr_limit, - pe[port].supply_voltage); - if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) - /* Set ceiling based on what's negotiated */ - charge_manager_set_ceil(port, CEIL_REQUESTOR_PD, - pe[port].curr_limit); - } -} - -static void pe_snk_hard_reset_run(int port) -{ - /* - * Transition to the PE_SNK_Transition_to_default state when: - * 1) The Hard Reset is complete. - */ - if (PE_CHK_FLAG(port, PE_FLAGS_HARD_RESET_PENDING)) - return; - - set_state_pe(port, PE_SNK_TRANSITION_TO_DEFAULT); -} - -/** - * PE_SNK_Transition_to_default - */ -static void pe_snk_transition_to_default_entry(int port) -{ - print_current_state(port); - - /* Reset flags */ - pe[port].flags = 0; - - /* Reset DPM Request */ - pe[port].dpm_request = 0; - - /* Inform the TC Layer of Hard Reset */ - tc_hard_reset_request(port); -} - -static void pe_snk_transition_to_default_run(int port) -{ - if (PE_CHK_FLAG(port, PE_FLAGS_PS_RESET_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_PS_RESET_COMPLETE); - /* Inform the Protocol Layer that the Hard Reset is complete */ - prl_hard_reset_complete(port); - set_state_pe(port, PE_SNK_STARTUP); - } -} - -/** - * PE_SNK_Get_Source_Cap - */ -static void pe_snk_get_source_cap_entry(int port) -{ - print_current_state(port); - - /* Send a Get_Source_Cap Message */ - tx_emsg[port].len = 0; - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_GET_SOURCE_CAP); -} - -static void pe_snk_get_source_cap_run(int port) -{ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - - set_state_pe(port, PE_SNK_READY); - } -} - -/** - * PE_SNK_Send_Soft_Reset and PE_SRC_Send_Soft_Reset - */ -static void pe_send_soft_reset_entry(int port) -{ - print_current_state(port); - - /* Reset Protocol Layer (softly) */ - prl_reset_soft(port); - - pe_sender_response_msg_entry(port); - - /* - * Mark the temporary timer PE_TIMER_TIMEOUT as expired to limit - * to sending a single SoftReset message. - */ - pd_timer_enable(port, PE_TIMER_TIMEOUT, 0); -} - -static void pe_send_soft_reset_run(int port) -{ - int type; - int cnt; - int ext; - enum pe_msg_check msg_check; - - /* Wait until protocol layer is running */ - if (!prl_is_running(port)) - return; - - /* - * Protocol layer is running, so need to send a single SoftReset. - * Use temporary timer to act as a flag to keep this as a single - * message send. - */ - if (!pd_timer_is_disabled(port, PE_TIMER_TIMEOUT)) { - pd_timer_disable(port, PE_TIMER_TIMEOUT); - - /* - * TODO(b/150614211): Soft reset type should match - * unexpected incoming message type - */ - /* Send Soft Reset message */ - send_ctrl_msg(port, - pe[port].soft_reset_sop, PD_CTRL_SOFT_RESET); - - return; - } - - /* - * Check the state of the message sent - */ - msg_check = pe_sender_response_msg_run(port); - - /* - * Handle discarded message - */ - if (msg_check == PE_MSG_DISCARDED) { - pe_set_ready_state(port); - return; - } - - /* - * Transition to the PE_SNK_Send_Capabilities or - * PE_SRC_Send_Capabilities state when: - * 1) An Accept Message has been received. - */ - if (msg_check == PE_MSG_SENT && - PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - - type = PD_HEADER_TYPE(rx_emsg[port].header); - cnt = PD_HEADER_CNT(rx_emsg[port].header); - ext = PD_HEADER_EXT(rx_emsg[port].header); - - if ((ext == 0) && (cnt == 0) && (type == PD_CTRL_ACCEPT)) { - if (pe[port].power_role == PD_ROLE_SINK) - set_state_pe(port, - PE_SNK_WAIT_FOR_CAPABILITIES); - else - set_state_pe(port, - PE_SRC_SEND_CAPABILITIES); - return; - } - } - - /* - * Transition to PE_SNK_Hard_Reset or PE_SRC_Hard_Reset on Sender - * Response Timer Timeout or Protocol Layer or Protocol Error - */ - if (pd_timer_is_expired(port, PE_TIMER_SENDER_RESPONSE) || - PE_CHK_FLAG(port, PE_FLAGS_PROTOCOL_ERROR)) { - PE_CLR_FLAG(port, PE_FLAGS_PROTOCOL_ERROR); - - if (pe[port].power_role == PD_ROLE_SINK) - set_state_pe(port, PE_SNK_HARD_RESET); - else - set_state_pe(port, PE_SRC_HARD_RESET); - return; - } -} - -static void pe_send_soft_reset_exit(int port) -{ - pe_sender_response_msg_exit(port); - pd_timer_disable(port, PE_TIMER_TIMEOUT); -} - -/** - * PE_SNK_Soft_Reset and PE_SNK_Soft_Reset - */ -static void pe_soft_reset_entry(int port) -{ - print_current_state(port); - - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_ACCEPT); -} - -static void pe_soft_reset_run(int port) -{ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - - if (pe[port].power_role == PD_ROLE_SINK) - set_state_pe(port, PE_SNK_WAIT_FOR_CAPABILITIES); - else - set_state_pe(port, PE_SRC_SEND_CAPABILITIES); - } else if (PE_CHK_FLAG(port, PE_FLAGS_PROTOCOL_ERROR)) { - PE_CLR_FLAG(port, PE_FLAGS_PROTOCOL_ERROR); - - if (pe[port].power_role == PD_ROLE_SINK) - set_state_pe(port, PE_SNK_HARD_RESET); - else - set_state_pe(port, PE_SRC_HARD_RESET); - } -} - -/** - * PE_SRC_Not_Supported and PE_SNK_Not_Supported - * - * 6.7.1 Soft Reset and Protocol Error (Revision 2.0, Version 1.3) - * An unrecognized or unsupported Message (except for a Structured VDM), - * received in the PE_SNK_Ready or PE_SRC_Ready states, Shall Not cause - * a Soft_Reset Message to be generated but instead a Reject Message - * Shall be generated. - */ -static void pe_send_not_supported_entry(int port) -{ - print_current_state(port); - - /* Request the Protocol Layer to send a Not_Supported Message. */ - if (prl_get_rev(port, TCPCI_MSG_SOP) > PD_REV20) - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_NOT_SUPPORTED); - else - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_REJECT); -} - -static void pe_send_not_supported_run(int port) -{ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - pe_set_ready_state(port); - - } -} - -/** - * PE_SRC_Chunk_Received and PE_SNK_Chunk_Received - * - * 6.11.2.1.1 Architecture of Device Including Chunking Layer (Revision 3.0, - * Version 2.0): If a PD Device or Cable Marker has no requirement to handle any - * message requiring more than one Chunk of any Extended Message, it May omit - * the Chunking Layer. In this case it Shall implement the - * ChunkingNotSupportedTimer to ensure compatible operation with partners which - * support Chunking. - * - * See also: - * 6.6.18.1 ChunkingNotSupportedTimer - * 8.3.3.6 Not Supported Message State Diagrams - */ -__maybe_unused static void pe_chunk_received_entry(int port) -{ - if (!IS_ENABLED(CONFIG_USB_PD_REV30) || - IS_ENABLED(CONFIG_USB_PD_EXTENDED_MESSAGES)) - assert(0); - - print_current_state(port); - pd_timer_enable(port, PE_TIMER_CHUNKING_NOT_SUPPORTED, - PD_T_CHUNKING_NOT_SUPPORTED); -} - -__maybe_unused static void pe_chunk_received_run(int port) -{ - if (!IS_ENABLED(CONFIG_USB_PD_REV30) || - IS_ENABLED(CONFIG_USB_PD_EXTENDED_MESSAGES)) - assert(0); - - if (pd_timer_is_expired(port, PE_TIMER_CHUNKING_NOT_SUPPORTED)) - set_state_pe(port, PE_SEND_NOT_SUPPORTED); -} - -__maybe_unused static void pe_chunk_received_exit(int port) -{ - pd_timer_disable(port, PE_TIMER_CHUNKING_NOT_SUPPORTED); -} - -/** - * PE_SRC_Ping - */ -static void pe_src_ping_entry(int port) -{ - print_current_state(port); - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_PING); -} - -static void pe_src_ping_run(int port) -{ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - set_state_pe(port, PE_SRC_READY); - } -} - -#ifdef CONFIG_USB_PD_EXTENDED_MESSAGES -/** - * PE_Give_Battery_Cap - */ -static void pe_give_battery_cap_entry(int port) -{ - uint8_t *payload = rx_emsg[port].buf; - uint16_t *msg = (uint16_t *)tx_emsg[port].buf; - - if (!IS_ENABLED(CONFIG_BATTERY)) - return; - print_current_state(port); - - /* Set VID */ - msg[BCDB_VID] = USB_VID_GOOGLE; - - /* Set PID */ - msg[BCDB_PID] = CONFIG_USB_PID; - - if (battery_is_present()) { - /* - * We only have one fixed battery, - * so make sure batt cap ref is 0. - * This value is the first byte after the headers. - */ - if (payload[0] != 0) { - /* Invalid battery reference */ - msg[BCDB_DESIGN_CAP] = 0; - msg[BCDB_FULL_CAP] = 0; - /* Set invalid battery bit in response bit 0, byte 8 */ - msg[BCDB_BATT_TYPE] = 1; - } else { - /* - * The Battery Design Capacity field shall return the - * Battery’s design capacity in tenths of Wh. If the - * Battery is Hot Swappable and is not present, the - * Battery Design Capacity field shall be set to 0. If - * the Battery is unable to report its Design Capacity, - * it shall return 0xFFFF - */ - msg[BCDB_DESIGN_CAP] = 0xffff; - - /* - * The Battery Last Full Charge Capacity field shall - * return the Battery’s last full charge capacity in - * tenths of Wh. If the Battery is Hot Swappable and - * is not present, the Battery Last Full Charge Capacity - * field shall be set to 0. If the Battery is unable to - * report its Design Capacity, the Battery Last Full - * Charge Capacity field shall be set to 0xFFFF. - */ - msg[BCDB_FULL_CAP] = 0xffff; - - - if (IS_ENABLED(HAS_TASK_HOSTCMD) && - *host_get_memmap(EC_MEMMAP_BATTERY_VERSION) != 0) { - int design_volt, design_cap, full_cap; - - design_volt = *(int *)host_get_memmap( - EC_MEMMAP_BATT_DVLT); - design_cap = *(int *)host_get_memmap( - EC_MEMMAP_BATT_DCAP); - full_cap = *(int *)host_get_memmap( - EC_MEMMAP_BATT_LFCC); - - /* - * Wh = (c * v) / 1000000 - * 10th of a Wh = Wh * 10 - */ - msg[BCDB_DESIGN_CAP] = DIV_ROUND_NEAREST( - (design_cap * design_volt), - 100000); - /* - * Wh = (c * v) / 1000000 - * 10th of a Wh = Wh * 10 - */ - msg[BCDB_FULL_CAP] = DIV_ROUND_NEAREST( - (design_cap * full_cap), - 100000); - } else { - uint32_t v; - uint32_t c; - - if (battery_design_voltage(&v) == 0) { - if (battery_design_capacity(&c) == 0) { - /* - * Wh = (c * v) / 1000000 - * 10th of a Wh = Wh * 10 - */ - msg[BCDB_DESIGN_CAP] = - DIV_ROUND_NEAREST( - (c * v), - 100000); - } - - if (battery_full_charge_capacity(&c) - == 0) { - /* - * Wh = (c * v) / 1000000 - * 10th of a Wh = Wh * 10 - */ - msg[BCDB_FULL_CAP] = - DIV_ROUND_NEAREST( - (c * v), - 100000); - } - } - - } - /* Valid battery selected */ - msg[BCDB_BATT_TYPE] = 0; - } - } else { - /* Battery not present indicated by 0's in the capacity */ - msg[BCDB_DESIGN_CAP] = 0; - msg[BCDB_FULL_CAP] = 0; - if (payload[0] != 0) - msg[BCDB_BATT_TYPE] = 1; - else - msg[BCDB_BATT_TYPE] = 0; - } - - /* Extended Battery Cap data is 9 bytes */ - tx_emsg[port].len = 9; - - send_ext_data_msg(port, TCPCI_MSG_SOP, PD_EXT_BATTERY_CAP); -} - -static void pe_give_battery_cap_run(int port) -{ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - pe_set_ready_state(port); - } -} - -/** - * PE_Give_Battery_Status - */ -static void pe_give_battery_status_entry(int port) -{ - uint8_t *payload = rx_emsg[port].buf; - uint32_t *msg = (uint32_t *)tx_emsg[port].buf; - - if (!IS_ENABLED(CONFIG_BATTERY)) - return; - print_current_state(port); - - if (battery_is_present()) { - /* - * We only have one fixed battery, - * so make sure batt cap ref is 0. - * This value is the first byte after the headers. - */ - if (payload[0] != 0) { - /* Invalid battery reference */ - *msg = BSDO_CAP(BSDO_CAP_UNKNOWN); - *msg |= BSDO_INVALID; - } else { - uint32_t v; - uint32_t c; - - *msg = BSDO_CAP(BSDO_CAP_UNKNOWN); - - if (IS_ENABLED(HAS_TASK_HOSTCMD) && - *host_get_memmap(EC_MEMMAP_BATTERY_VERSION) != 0) { - v = *(int *)host_get_memmap( - EC_MEMMAP_BATT_DVLT); - c = *(int *)host_get_memmap( - EC_MEMMAP_BATT_CAP); - - /* - * Wh = (c * v) / 1000000 - * 10th of a Wh = Wh * 10 - */ - *msg = BSDO_CAP(DIV_ROUND_NEAREST((c * v), - 100000)); - } else if (battery_design_voltage(&v) == 0 && - battery_remaining_capacity(&c) == 0) { - /* - * Wh = (c * v) / 1000000 - * 10th of a Wh = Wh * 10 - */ - *msg = BSDO_CAP(DIV_ROUND_NEAREST((c * v), - 100000)); - } - - /* Battery is present */ - *msg |= BSDO_PRESENT; - - /* - * For drivers that are not smart battery compliant, - * battery_status() returns EC_ERROR_UNIMPLEMENTED and - * the battery is assumed to be idle. - */ - if (battery_status(&c) != 0) { - *msg |= BSDO_IDLE; /* assume idle */ - } else { - if (c & STATUS_FULLY_CHARGED) - /* Fully charged */ - *msg |= BSDO_IDLE; - else if (c & STATUS_DISCHARGING) - /* Discharging */ - *msg |= BSDO_DISCHARGING; - /* else battery is charging.*/ - } - } - } else { - *msg = BSDO_CAP(BSDO_CAP_UNKNOWN); - if (payload[0] != 0) - *msg |= BSDO_INVALID; - } - - /* Battery Status data is 4 bytes */ - tx_emsg[port].len = 4; - - send_data_msg(port, TCPCI_MSG_SOP, PD_DATA_BATTERY_STATUS); -} - -static void pe_give_battery_status_run(int port) -{ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - set_state_pe(port, PE_SRC_READY); - } -} - -/** - * PE_SRC_Send_Source_Alert and - * PE_SNK_Send_Sink_Alert - */ -static void pe_send_alert_entry(int port) -{ - uint32_t *msg = (uint32_t *)tx_emsg[port].buf; - uint32_t *len = &tx_emsg[port].len; - - print_current_state(port); - - if (pd_build_alert_msg(msg, len, pe[port].power_role) != EC_SUCCESS) - pe_set_ready_state(port); - - /* Request the Protocol Layer to send Alert Message. */ - send_data_msg(port, TCPCI_MSG_SOP, PD_DATA_ALERT); -} - -static void pe_send_alert_run(int port) -{ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - pe_set_ready_state(port); - } -} -#endif /* CONFIG_USB_PD_EXTENDED_MESSAGES */ - -/** - * PE_DRS_Evaluate_Swap - */ -static void pe_drs_evaluate_swap_entry(int port) -{ - print_current_state(port); - - /* Get evaluation of Data Role Swap request from DPM */ - if (pd_check_data_swap(port, pe[port].data_role)) { - PE_SET_FLAG(port, PE_FLAGS_ACCEPT); - /* - * PE_DRS_UFP_DFP_Evaluate_Swap and - * PE_DRS_DFP_UFP_Evaluate_Swap states embedded here. - */ - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_ACCEPT); - } else { - /* - * PE_DRS_UFP_DFP_Reject_Swap and PE_DRS_DFP_UFP_Reject_Swap - * states embedded here. - */ - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_REJECT); - } -} - -static void pe_drs_evaluate_swap_run(int port) -{ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - - /* Accept Message sent. Transtion to PE_DRS_Change */ - if (PE_CHK_FLAG(port, PE_FLAGS_ACCEPT)) { - PE_CLR_FLAG(port, PE_FLAGS_ACCEPT); - set_state_pe(port, PE_DRS_CHANGE); - } else { - /* - * Message sent. Transition back to PE_SRC_Ready or - * PE_SNK_Ready. - */ - pe_set_ready_state(port); - } - } -} - -/** - * PE_DRS_Change - */ -static void pe_drs_change_entry(int port) -{ - print_current_state(port); - - /* - * PE_DRS_UFP_DFP_Change_to_DFP and PE_DRS_DFP_UFP_Change_to_UFP - * states embedded here. - */ - /* Request DPM to change port data role */ - pd_request_data_swap(port); -} - -static void pe_drs_change_run(int port) -{ - /* Wait until the data role is changed */ - if (pe[port].data_role == pd_get_data_role(port)) - return; - - /* Update the data role */ - pe[port].data_role = pd_get_data_role(port); - - if (pe[port].data_role == PD_ROLE_DFP) - PE_CLR_FLAG(port, PE_FLAGS_DR_SWAP_TO_DFP); - - /* - * Port changed. Transition back to PE_SRC_Ready or - * PE_SNK_Ready. - */ - pe_set_ready_state(port); -} - -/** - * PE_DRS_Send_Swap - */ -static void pe_drs_send_swap_entry(int port) -{ - print_current_state(port); - - /* - * PE_DRS_UFP_DFP_Send_Swap and PE_DRS_DFP_UFP_Send_Swap - * states embedded here. - */ - /* Request the Protocol Layer to send a DR_Swap Message */ - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_DR_SWAP); - pe_sender_response_msg_entry(port); -} - -static void pe_drs_send_swap_run(int port) -{ - int type; - int cnt; - int ext; - enum pe_msg_check msg_check; - - /* - * Check the state of the message sent - */ - msg_check = pe_sender_response_msg_run(port); - - /* - * Transition to PE_DRS_Change when: - * 1) An Accept Message is received. - * - * Transition to PE_SRC_Ready or PE_SNK_Ready state when: - * 1) A Reject Message is received. - * 2) Or a Wait Message is received. - */ - if ((msg_check & PE_MSG_SENT) && - PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - - type = PD_HEADER_TYPE(rx_emsg[port].header); - cnt = PD_HEADER_CNT(rx_emsg[port].header); - ext = PD_HEADER_EXT(rx_emsg[port].header); - - if ((ext == 0) && (cnt == 0)) { - if (type == PD_CTRL_ACCEPT) { - set_state_pe(port, PE_DRS_CHANGE); - return; - } else if ((type == PD_CTRL_REJECT) || - (type == PD_CTRL_WAIT) || - (type == PD_CTRL_NOT_SUPPORTED)) { - pe_set_ready_state(port); - return; - } - } - } - - /* - * Transition to PE_SRC_Ready or PE_SNK_Ready state when: - * 1) the SenderResponseTimer times out. - * 2) Message was discarded. - */ - if ((msg_check & PE_MSG_DISCARDED) || - pd_timer_is_expired(port, PE_TIMER_SENDER_RESPONSE)) - pe_set_ready_state(port); -} - -static void pe_drs_send_swap_exit(int port) -{ - pe_sender_response_msg_exit(port); -} - -/** - * PE_PRS_SRC_SNK_Evaluate_Swap - */ -static void pe_prs_src_snk_evaluate_swap_entry(int port) -{ - print_current_state(port); - - if (!pd_check_power_swap(port)) { - /* PE_PRS_SRC_SNK_Reject_PR_Swap state embedded here */ - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_REJECT); - } else { - tc_request_power_swap(port); - /* PE_PRS_SRC_SNK_Accept_Swap state embedded here */ - PE_SET_FLAG(port, PE_FLAGS_ACCEPT); - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_ACCEPT); - } -} - -static void pe_prs_src_snk_evaluate_swap_run(int port) -{ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - - if (PE_CHK_FLAG(port, PE_FLAGS_ACCEPT)) { - PE_CLR_FLAG(port, PE_FLAGS_ACCEPT); - - /* - * Clear any pending DPM power role swap request so we - * don't trigger a power role swap request back to src - * power role. - */ - PE_CLR_DPM_REQUEST(port, DPM_REQUEST_PR_SWAP); - /* - * Power Role Swap OK, transition to - * PE_PRS_SRC_SNK_Transition_to_off - */ - set_state_pe(port, PE_PRS_SRC_SNK_TRANSITION_TO_OFF); - } else { - /* Message sent, return to PE_SRC_Ready */ - set_state_pe(port, PE_SRC_READY); - } - } -} - -/** - * PE_PRS_SRC_SNK_Transition_To_Off - */ -static void pe_prs_src_snk_transition_to_off_entry(int port) -{ - print_current_state(port); - - /* Contract is invalid */ - pe_invalidate_explicit_contract(port); - - /* Tell TypeC to power off the source */ - tc_src_power_off(port); - - pd_timer_enable(port, PE_TIMER_PS_SOURCE, - PD_POWER_SUPPLY_TURN_OFF_DELAY); -} - -static void pe_prs_src_snk_transition_to_off_run(int port) -{ - /* - * This is a non-interruptible AMS and power is transitioning - hard - * reset on interruption. - */ - if (PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - - tc_pr_swap_complete(port, 0); - set_state_pe(port, PE_SRC_HARD_RESET); - } - - /* Give time for supply to power off */ - if (pd_timer_is_expired(port, PE_TIMER_PS_SOURCE) && - pd_check_vbus_level(port, VBUS_SAFE0V)) - set_state_pe(port, PE_PRS_SRC_SNK_ASSERT_RD); -} - -static void pe_prs_src_snk_transition_to_off_exit(int port) -{ - pd_timer_disable(port, PE_TIMER_PS_SOURCE); -} - -/** - * PE_PRS_SRC_SNK_Assert_Rd - */ -static void pe_prs_src_snk_assert_rd_entry(int port) -{ - print_current_state(port); - - /* Tell TypeC to swap from Attached.SRC to Attached.SNK */ - tc_prs_src_snk_assert_rd(port); -} - -static void pe_prs_src_snk_assert_rd_run(int port) -{ - /* Wait until Rd is asserted */ - if (tc_is_attached_snk(port)) - set_state_pe(port, PE_PRS_SRC_SNK_WAIT_SOURCE_ON); -} - -/** - * PE_PRS_SRC_SNK_Wait_Source_On - */ -static void pe_prs_src_snk_wait_source_on_entry(int port) -{ - print_current_state(port); - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_PS_RDY); -} - -static void pe_prs_src_snk_wait_source_on_run(int port) -{ - if (pd_timer_is_disabled(port, PE_TIMER_PS_SOURCE) && - PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - - /* Update pe power role */ - pe[port].power_role = pd_get_power_role(port); - pd_timer_enable(port, PE_TIMER_PS_SOURCE, PD_T_PS_SOURCE_ON); - } - - /* - * Transition to PE_SNK_Startup when: - * 1) A PS_RDY Message is received. - */ - if (!pd_timer_is_disabled(port, PE_TIMER_PS_SOURCE) && - PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - int type = PD_HEADER_TYPE(rx_emsg[port].header); - int cnt = PD_HEADER_CNT(rx_emsg[port].header); - int ext = PD_HEADER_EXT(rx_emsg[port].header); - - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - - if ((ext == 0) && (cnt == 0) && (type == PD_CTRL_PS_RDY)) { - PE_SET_FLAG(port, PE_FLAGS_PR_SWAP_COMPLETE); - set_state_pe(port, PE_SNK_STARTUP); - } else { - int sop = PD_HEADER_GET_SOP(rx_emsg[port].header); - /* - * USB PD 3.0 6.8.1: - * Receiving an unexpected message shall be responded - * to with a soft reset message. - */ - pe_send_soft_reset(port, sop); - } - return; - } - - /* - * Transition to ErrorRecovery state when: - * 1) The PSSourceOnTimer times out. - * 2) PS_RDY not sent after retries. - */ - if (pd_timer_is_expired(port, PE_TIMER_PS_SOURCE) || - PE_CHK_FLAG(port, PE_FLAGS_PROTOCOL_ERROR)) { - PE_CLR_FLAG(port, PE_FLAGS_PROTOCOL_ERROR); - - set_state_pe(port, PE_WAIT_FOR_ERROR_RECOVERY); - return; - } -} - -static void pe_prs_src_snk_wait_source_on_exit(int port) -{ - pd_timer_disable(port, PE_TIMER_PS_SOURCE); - tc_pr_swap_complete(port, - PE_CHK_FLAG(port, PE_FLAGS_PR_SWAP_COMPLETE)); -} - -/** - * PE_PRS_SRC_SNK_Send_Swap - */ -static void pe_prs_src_snk_send_swap_entry(int port) -{ - print_current_state(port); - - /* Making an attempt to PR_Swap, clear we were possibly waiting */ - pd_timer_disable(port, PE_TIMER_PR_SWAP_WAIT); - - /* Request the Protocol Layer to send a PR_Swap Message. */ - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_PR_SWAP); - pe_sender_response_msg_entry(port); -} - -static void pe_prs_src_snk_send_swap_run(int port) -{ - int type; - int cnt; - int ext; - enum pe_msg_check msg_check; - - /* - * Check the state of the message sent - */ - msg_check = pe_sender_response_msg_run(port); - - /* - * Transition to PE_PRS_SRC_SNK_Transition_To_Off when: - * 1) An Accept Message is received. - * - * Transition to PE_SRC_Ready state when: - * 1) A Reject Message is received. - * 2) Or a Wait Message is received. - */ - if ((msg_check & PE_MSG_SENT) && - PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - - type = PD_HEADER_TYPE(rx_emsg[port].header); - cnt = PD_HEADER_CNT(rx_emsg[port].header); - ext = PD_HEADER_EXT(rx_emsg[port].header); - - if ((ext == 0) && (cnt == 0)) { - if (type == PD_CTRL_ACCEPT) { - pe[port].src_snk_pr_swap_counter = 0; - tc_request_power_swap(port); - set_state_pe(port, - PE_PRS_SRC_SNK_TRANSITION_TO_OFF); - } else if (type == PD_CTRL_REJECT) { - pe[port].src_snk_pr_swap_counter = 0; - set_state_pe(port, PE_SRC_READY); - } else if (type == PD_CTRL_WAIT) { - if (pe[port].src_snk_pr_swap_counter < - N_SNK_SRC_PR_SWAP_COUNT) { - PE_SET_FLAG(port, - PE_FLAGS_WAITING_PR_SWAP); - pd_timer_enable(port, - PE_TIMER_PR_SWAP_WAIT, - PD_T_PR_SWAP_WAIT); - } - pe[port].src_snk_pr_swap_counter++; - set_state_pe(port, PE_SRC_READY); - } - return; - } - } - - /* - * Transition to PE_SRC_Ready state when: - * 1) Or the SenderResponseTimer times out. - * 2) Message was discarded. - */ - if ((msg_check & PE_MSG_DISCARDED) || - pd_timer_is_expired(port, PE_TIMER_SENDER_RESPONSE)) - set_state_pe(port, PE_SRC_READY); -} - -static void pe_prs_src_snk_send_swap_exit(int port) -{ - pe_sender_response_msg_exit(port); -} - -/** - * PE_PRS_SNK_SRC_Evaluate_Swap - */ -static void pe_prs_snk_src_evaluate_swap_entry(int port) -{ - print_current_state(port); - - /* - * Cancel any pending PR swap request due to a received Wait since the - * partner just sent us a PR swap message. - */ - PE_CLR_FLAG(port, PE_FLAGS_WAITING_PR_SWAP); - pe[port].src_snk_pr_swap_counter = 0; - - if (!pd_check_power_swap(port)) { - /* PE_PRS_SNK_SRC_Reject_Swap state embedded here */ - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_REJECT); - } else { - tc_request_power_swap(port); - /* PE_PRS_SNK_SRC_Accept_Swap state embedded here */ - PE_SET_FLAG(port, PE_FLAGS_ACCEPT); - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_ACCEPT); - } -} - -static void pe_prs_snk_src_evaluate_swap_run(int port) -{ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - if (PE_CHK_FLAG(port, PE_FLAGS_ACCEPT)) { - PE_CLR_FLAG(port, PE_FLAGS_ACCEPT); - - /* - * Clear any pending DPM power role swap request so we - * don't trigger a power role swap request back to sink - * power role. - */ - PE_CLR_DPM_REQUEST(port, DPM_REQUEST_PR_SWAP); - /* - * Accept message sent, transition to - * PE_PRS_SNK_SRC_Transition_to_off - */ - set_state_pe(port, PE_PRS_SNK_SRC_TRANSITION_TO_OFF); - } else { - /* Message sent, return to PE_SNK_Ready */ - set_state_pe(port, PE_SNK_READY); - } - } - - if (PE_CHK_FLAG(port, PE_FLAGS_PROTOCOL_ERROR)) { - PE_CLR_FLAG(port, PE_FLAGS_PROTOCOL_ERROR); - /* - * Protocol Error occurs while PR swap, this may - * brown out if the port-parnter can't hold VBUS - * for tSrcTransition. Notify TC that we end the PR - * swap and start to watch VBUS. - * - * TODO(b:155181980): issue soft reset on protocol error. - */ - tc_pr_swap_complete(port, 0); - } -} - -/** - * PE_PRS_SNK_SRC_Transition_To_Off - * PE_FRS_SNK_SRC_Transition_To_Off - * - * NOTE: Shared action code used for Power Role Swap and Fast Role Swap - */ -static void pe_prs_snk_src_transition_to_off_entry(int port) -{ - print_current_state(port); - - if (!IS_ENABLED(CONFIG_USB_PD_REV30) || - !pe_in_frs_mode(port)) - tc_snk_power_off(port); - - pd_timer_enable(port, PE_TIMER_PS_SOURCE, PD_T_PS_SOURCE_OFF); -} - -static void pe_prs_snk_src_transition_to_off_run(int port) -{ - int type; - int cnt; - int ext; - - /* - * Transition to ErrorRecovery state when: - * 1) The PSSourceOffTimer times out. - */ - if (pd_timer_is_expired(port, PE_TIMER_PS_SOURCE)) - set_state_pe(port, PE_WAIT_FOR_ERROR_RECOVERY); - - /* - * Transition to PE_PRS_SNK_SRC_Assert_Rp when: - * 1) An PS_RDY Message is received. - */ - else if (PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - - type = PD_HEADER_TYPE(rx_emsg[port].header); - cnt = PD_HEADER_CNT(rx_emsg[port].header); - ext = PD_HEADER_EXT(rx_emsg[port].header); - - if ((ext == 0) && (cnt == 0) && (type == PD_CTRL_PS_RDY)) { - /* - * FRS: We are always ready to drive vSafe5v, so just - * skip PE_FRS_SNK_SRC_Vbus_Applied and go direct to - * PE_FRS_SNK_SRC_Assert_Rp - */ - set_state_pe(port, PE_PRS_SNK_SRC_ASSERT_RP); - } - } -} - -static void pe_prs_snk_src_transition_to_off_exit(int port) -{ - pd_timer_disable(port, PE_TIMER_PS_SOURCE); -} - -/** - * PE_PRS_SNK_SRC_Assert_Rp - * PE_FRS_SNK_SRC_Assert_Rp - * - * NOTE: Shared action code used for Power Role Swap and Fast Role Swap - */ -static void pe_prs_snk_src_assert_rp_entry(int port) -{ - print_current_state(port); - - /* - * Tell TypeC to Power/Fast Role Swap (PRS/FRS) from - * Attached.SNK to Attached.SRC - */ - tc_prs_snk_src_assert_rp(port); -} - -static void pe_prs_snk_src_assert_rp_run(int port) -{ - /* Wait until TypeC is in the Attached.SRC state */ - if (tc_is_attached_src(port)) { - if (!IS_ENABLED(CONFIG_USB_PD_REV30) || - !pe_in_frs_mode(port)) { - /* Contract is invalid now */ - pe_invalidate_explicit_contract(port); - } - set_state_pe(port, PE_PRS_SNK_SRC_SOURCE_ON); - } -} - -/** - * PE_PRS_SNK_SRC_Source_On - * PE_FRS_SNK_SRC_Source_On - * - * NOTE: Shared action code used for Power Role Swap and Fast Role Swap - */ -static void pe_prs_snk_src_source_on_entry(int port) -{ - print_current_state(port); - - /* - * VBUS was enabled when the TypeC state machine entered - * Attached.SRC state - */ - pd_timer_enable(port, PE_TIMER_PS_SOURCE, - PD_POWER_SUPPLY_TURN_ON_DELAY); -} - -static void pe_prs_snk_src_source_on_run(int port) -{ - /* Wait until power supply turns on */ - if (!pd_timer_is_disabled(port, PE_TIMER_PS_SOURCE)) { - if (!pd_timer_is_expired(port, PE_TIMER_PS_SOURCE)) - return; - - /* update pe power role */ - pe[port].power_role = pd_get_power_role(port); - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_PS_RDY); - /* reset timer so PD_CTRL_PS_RDY isn't sent again */ - pd_timer_disable(port, PE_TIMER_PS_SOURCE); - } - - /* - * Transition to ErrorRecovery state when: - * 1) On protocol error - */ - else if (PE_CHK_FLAG(port, PE_FLAGS_PROTOCOL_ERROR)) { - PE_CLR_FLAG(port, PE_FLAGS_PROTOCOL_ERROR); - set_state_pe(port, PE_WAIT_FOR_ERROR_RECOVERY); - } - - else if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - - /* Run swap source timer on entry to pe_src_startup */ - PE_SET_FLAG(port, PE_FLAGS_PR_SWAP_COMPLETE); - set_state_pe(port, PE_SRC_STARTUP); - } -} - -static void pe_prs_snk_src_source_on_exit(int port) -{ - pd_timer_disable(port, PE_TIMER_PS_SOURCE); - tc_pr_swap_complete(port, - PE_CHK_FLAG(port, PE_FLAGS_PR_SWAP_COMPLETE)); -} - -/** - * PE_PRS_SNK_SRC_Send_Swap - * PE_FRS_SNK_SRC_Send_Swap - * - * NOTE: Shared action code used for Power Role Swap and Fast Role Swap - */ -static void pe_prs_snk_src_send_swap_entry(int port) -{ - print_current_state(port); - - /* - * PRS_SNK_SRC_SEND_SWAP - * Request the Protocol Layer to send a PR_Swap Message. - * - * FRS_SNK_SRC_SEND_SWAP - * Hardware should have turned off sink power and started - * bringing Vbus to vSafe5. - * Request the Protocol Layer to send a FR_Swap Message. - */ - if (IS_ENABLED(CONFIG_USB_PD_REV30)) { - send_ctrl_msg(port, - TCPCI_MSG_SOP, - pe_in_frs_mode(port) - ? PD_CTRL_FR_SWAP - : PD_CTRL_PR_SWAP); - } else { - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_PR_SWAP); - } - pe_sender_response_msg_entry(port); -} - -static void pe_prs_snk_src_send_swap_run(int port) -{ - int type; - int cnt; - int ext; - enum pe_msg_check msg_check; - - /* - * Check the state of the message sent - */ - msg_check = pe_sender_response_msg_run(port); - - /* - * Handle discarded message - */ - if (msg_check & PE_MSG_DISCARDED) { - if (pe_in_frs_mode(port)) - set_state_pe(port, PE_SNK_HARD_RESET); - else - set_state_pe(port, PE_SNK_READY); - return; - } - - /* - * Transition to PE_PRS_SNK_SRC_Transition_to_off when: - * 1) An Accept Message is received. - * - * PRS: Transition to PE_SNK_Ready state when: - * FRS: Transition to ErrorRecovery state when: - * 1) A Reject Message is received. - * 2) Or a Wait Message is received. - */ - if ((msg_check & PE_MSG_SENT) && - PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - - type = PD_HEADER_TYPE(rx_emsg[port].header); - cnt = PD_HEADER_CNT(rx_emsg[port].header); - ext = PD_HEADER_EXT(rx_emsg[port].header); - - if ((ext == 0) && (cnt == 0)) { - if (type == PD_CTRL_ACCEPT) { - tc_request_power_swap(port); - set_state_pe(port, - PE_PRS_SNK_SRC_TRANSITION_TO_OFF); - } else if ((type == PD_CTRL_REJECT) || - (type == PD_CTRL_WAIT)) { - if (IS_ENABLED(CONFIG_USB_PD_REV30)) - set_state_pe(port, - pe_in_frs_mode(port) - ? PE_WAIT_FOR_ERROR_RECOVERY - : PE_SNK_READY); - else - set_state_pe(port, PE_SNK_READY); - } - return; - } - } - - /* - * PRS: Transition to PE_SNK_Ready state when: - * FRS: Transition to ErrorRecovery state when: - * 1) The SenderResponseTimer times out. - */ - if (pd_timer_is_expired(port, PE_TIMER_SENDER_RESPONSE)) { - if (IS_ENABLED(CONFIG_USB_PD_REV30)) - set_state_pe(port, - pe_in_frs_mode(port) - ? PE_WAIT_FOR_ERROR_RECOVERY - : PE_SNK_READY); - else - set_state_pe(port, PE_SNK_READY); - return; - } - /* - * FRS Only: Transition to ErrorRecovery state when: - * 2) The FR_Swap Message is not sent after retries (a GoodCRC Message - * has not been received). A soft reset Shall Not be initiated in - * this case. - */ - if (IS_ENABLED(CONFIG_USB_PD_REV30) && - pe_in_frs_mode(port) && - PE_CHK_FLAG(port, PE_FLAGS_PROTOCOL_ERROR)) { - PE_CLR_FLAG(port, PE_FLAGS_PROTOCOL_ERROR); - set_state_pe(port, PE_WAIT_FOR_ERROR_RECOVERY); - } -} - -static void pe_prs_snk_src_send_swap_exit(int port) -{ - pe_sender_response_msg_exit(port); -} - -/** - * PE_FRS_SNK_SRC_Start_AMS - */ -__maybe_unused static void pe_frs_snk_src_start_ams_entry(int port) -{ - if (!IS_ENABLED(CONFIG_USB_PD_REV30)) - assert(0); - - print_current_state(port); - - /* Contract is invalid now */ - pe_invalidate_explicit_contract(port); - - /* Inform Protocol Layer this is start of AMS */ - PE_SET_FLAG(port, PE_FLAGS_LOCALLY_INITIATED_AMS); - - /* Shared PRS/FRS code, indicate FRS path */ - PE_SET_FLAG(port, PE_FLAGS_FAST_ROLE_SWAP_PATH); - set_state_pe(port, PE_PRS_SNK_SRC_SEND_SWAP); -} - -/** - * PE_PRS_FRS_SHARED - */ -__maybe_unused static void pe_prs_frs_shared_entry(int port) -{ - if (!IS_ENABLED(CONFIG_USB_PD_REV30)) - assert(0); - - /* - * Shared PRS/FRS code, assume PRS path - * - * This is the super state entry. It will be called before - * the first entry state to get into the PRS/FRS path. - * For FRS, PE_FRS_SNK_SRC_START_AMS entry will be called - * after this and that will set for the FRS path. - */ - PE_CLR_FLAG(port, PE_FLAGS_FAST_ROLE_SWAP_PATH); -} - -__maybe_unused static void pe_prs_frs_shared_exit(int port) -{ - if (!IS_ENABLED(CONFIG_USB_PD_REV30)) - assert(0); - - /* - * Shared PRS/FRS code, when not in shared path - * indicate PRS path - */ - PE_CLR_FLAG(port, PE_FLAGS_FAST_ROLE_SWAP_PATH); -} - -/** - * PE_BIST_TX - */ -static void pe_bist_tx_entry(int port) -{ - uint32_t *payload = (uint32_t *)rx_emsg[port].buf; - uint8_t mode = BIST_MODE(payload[0]); - int vbus_mv; - int ibus_ma; - - print_current_state(port); - - /* Get the current nominal VBUS value */ - if (pe[port].power_role == PD_ROLE_SOURCE) { - const uint32_t *src_pdo; - uint32_t unused; - - dpm_get_source_pdo(&src_pdo, port); - pd_extract_pdo_power(src_pdo[pe[port].requested_idx - 1], - &ibus_ma, &vbus_mv, &unused); - } else { - vbus_mv = pe[port].supply_voltage; - } - - /* If VBUS is not at vSafe5V, then don't enter BIST test mode */ - if (vbus_mv != PD_V_SAFE5V_NOM) { - pe_set_ready_state(port); - return; - } - - if (mode == BIST_CARRIER_MODE_2) { - /* - * PE_BIST_Carrier_Mode embedded here. - * See PD 3.0 section 6.4.3.1 BIST Carrier Mode 2: With a BIST - * Carrier Mode 2 BIST Data Object, the UUT Shall send out a - * continuous string of BMC-encoded alternating "1"s and “0”s. - * The UUT Shall exit the Continuous BIST Mode within - * tBISTContMode of this Continuous BIST Mode being enabled. - */ - send_ctrl_msg(port, TCPCI_MSG_TX_BIST_MODE_2, 0); - pd_timer_enable(port, PE_TIMER_BIST_CONT_MODE, - PD_T_BIST_CONT_MODE); - } else if (mode == BIST_TEST_DATA) { - /* - * See PD 3.0 section 6.4.3.2 BIST Test Data: - * With a BIST Test Data BIST Data Object, the UUT Shall return - * a GoodCRC Message and Shall enter a test mode in which it - * sends no further Messages except for GoodCRC Messages in - * response to received Messages.... The test Shall be ended by - * sending Hard Reset Signaling to reset the UUT. - */ - if (tcpc_set_bist_test_mode(port, true) != EC_SUCCESS) - CPRINTS("C%d: Failed to enter BIST Test Mode", port); - } else { - /* Ignore unsupported BIST messages. */ - pe_set_ready_state(port); - return; - } -} - -static void pe_bist_tx_run(int port) -{ - if (pd_timer_is_expired(port, PE_TIMER_BIST_CONT_MODE)) { - /* - * Entry point to disable BIST in TCPC if that's not already - * handled automatically by the TCPC. Unless this method is - * implemented in a TCPM driver, this function does nothing. - */ - tcpm_reset_bist_type_2(port); - - if (pe[port].power_role == PD_ROLE_SOURCE) - set_state_pe(port, PE_SRC_TRANSITION_TO_DEFAULT); - else - set_state_pe(port, PE_SNK_TRANSITION_TO_DEFAULT); - } else { - /* - * We are in test data mode and no further Messages except for - * GoodCRC Messages in response to received Messages will - * be sent. - */ - if (PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - } -} - -static void pe_bist_tx_exit(int port) -{ - pd_timer_disable(port, PE_TIMER_BIST_CONT_MODE); -} - -/** - * Give_Sink_Cap Message - */ -static void pe_snk_give_sink_cap_entry(int port) -{ - print_current_state(port); - - /* Send a Sink_Capabilities Message */ - tx_emsg[port].len = pd_snk_pdo_cnt * 4; - memcpy(tx_emsg[port].buf, (uint8_t *)pd_snk_pdo, tx_emsg[port].len); - send_data_msg(port, TCPCI_MSG_SOP, PD_DATA_SINK_CAP); -} - -static void pe_snk_give_sink_cap_run(int port) -{ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - pe_set_ready_state(port); - return; - } - - if (pe_check_outgoing_discard(port)) - return; -} - -/** - * Wait For Error Recovery - */ -static void pe_wait_for_error_recovery_entry(int port) -{ - print_current_state(port); - tc_start_error_recovery(port); -} - -static void pe_wait_for_error_recovery_run(int port) -{ - /* Stay here until error recovery is complete */ -} - -/** - * PE_Handle_Custom_Vdm_Request - */ -static void pe_handle_custom_vdm_request_entry(int port) -{ - /* Get the message */ - uint32_t *payload = (uint32_t *)rx_emsg[port].buf; - int cnt = PD_HEADER_CNT(rx_emsg[port].header); - int sop = PD_HEADER_GET_SOP(rx_emsg[port].header); - int rlen = 0; - uint32_t *rdata; - - print_current_state(port); - - /* This is an Interruptible AMS */ - PE_SET_FLAG(port, PE_FLAGS_INTERRUPTIBLE_AMS); - - rlen = pd_custom_vdm(port, cnt, payload, &rdata); - if (rlen > 0) { - tx_emsg[port].len = rlen * 4; - memcpy(tx_emsg[port].buf, (uint8_t *)rdata, tx_emsg[port].len); - send_data_msg(port, sop, PD_DATA_VENDOR_DEF); - } else { - if (prl_get_rev(port, TCPCI_MSG_SOP) > PD_REV20) { - set_state_pe(port, PE_SEND_NOT_SUPPORTED); - } else { - PE_CLR_FLAG(port, PE_FLAGS_INTERRUPTIBLE_AMS); - pe_set_ready_state(port); - } - } -} - -static void pe_handle_custom_vdm_request_run(int port) -{ - /* Wait for ACCEPT, WAIT or Reject message to send. */ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - - /* - * Message sent. Transition back to - * PE_SRC_Ready or PE_SINK_Ready - */ - pe_set_ready_state(port); - } -} - -static void pe_handle_custom_vdm_request_exit(int port) -{ - PE_CLR_FLAG(port, PE_FLAGS_INTERRUPTIBLE_AMS); -} - -static enum vdm_response_result parse_vdm_response_common(int port) -{ - /* Retrieve the message information */ - uint32_t *payload; - int sop; - uint8_t type; - uint8_t cnt; - uint8_t ext; - - if (!PE_CHK_REPLY(port)) - return VDM_RESULT_WAITING; - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - - payload = (uint32_t *)rx_emsg[port].buf; - sop = PD_HEADER_GET_SOP(rx_emsg[port].header); - type = PD_HEADER_TYPE(rx_emsg[port].header); - cnt = PD_HEADER_CNT(rx_emsg[port].header); - ext = PD_HEADER_EXT(rx_emsg[port].header); - - if (sop == pe[port].tx_type && type == PD_DATA_VENDOR_DEF && cnt >= 1 - && ext == 0) { - if (PD_VDO_CMDT(payload[0]) == CMDT_RSP_ACK && - cnt >= pe[port].vdm_ack_min_data_objects) { - /* Handle ACKs in state-specific code. */ - return VDM_RESULT_ACK; - } else if (PD_VDO_CMDT(payload[0]) == CMDT_RSP_NAK) { - /* Handle NAKs in state-specific code. */ - return VDM_RESULT_NAK; - } else if (PD_VDO_CMDT(payload[0]) == CMDT_RSP_BUSY) { - /* - * Don't fill in the discovery field so we re-probe in - * tVDMBusy - */ - CPRINTS("C%d: Partner BUSY, request will be retried", - port); - pd_timer_enable(port, PE_TIMER_DISCOVER_IDENTITY, - PD_T_VDM_BUSY); - - return VDM_RESULT_NO_ACTION; - } else if (PD_VDO_CMDT(payload[0]) == CMDT_INIT) { - /* - * Unexpected VDM REQ received. Let Src.Ready or - * Snk.Ready handle it. - */ - PE_SET_FLAG(port, PE_FLAGS_MSG_RECEIVED); - return VDM_RESULT_NO_ACTION; - } - - /* - * Partner gave us an incorrect size or command; mark discovery - * as failed. - */ - CPRINTS("C%d: Unexpected VDM response: 0x%04x 0x%04x", - port, rx_emsg[port].header, payload[0]); - return VDM_RESULT_NAK; - } else if (sop == pe[port].tx_type && ext == 0 && cnt == 0 && - type == PD_CTRL_NOT_SUPPORTED) { - /* - * A NAK would be more expected here, but Not Supported is still - * allowed with the same meaning. - */ - return VDM_RESULT_NAK; - } - - /* Unexpected Message Received. Src.Ready or Snk.Ready can handle it. */ - PE_SET_FLAG(port, PE_FLAGS_MSG_RECEIVED); - return VDM_RESULT_NO_ACTION; -} - -/** - * PE_VDM_SEND_REQUEST - * Shared parent to manage VDM timer and other shared parts of the VDM request - * process - */ -static void pe_vdm_send_request_entry(int port) -{ - if (pe[port].tx_type == TCPCI_MSG_INVALID) { - if (IS_ENABLED(USB_PD_DEBUG_LABELS)) - CPRINTS("C%d: %s: Tx type expected to be set, " - "returning", - port, pe_state_names[get_state_pe(port)]); - set_state_pe(port, get_last_state_pe(port)); - return; - } - - if ((pe[port].tx_type == TCPCI_MSG_SOP_PRIME || - pe[port].tx_type == TCPCI_MSG_SOP_PRIME_PRIME) && - !tc_is_vconn_src(port) && port_discovery_vconn_swap_policy(port, - PE_FLAGS_VCONN_SWAP_TO_ON)) { - if (port_try_vconn_swap(port)) - return; - } - - /* All VDM sequences are Interruptible */ - PE_SET_FLAG(port, PE_FLAGS_LOCALLY_INITIATED_AMS | - PE_FLAGS_INTERRUPTIBLE_AMS); -} - -static void pe_vdm_send_request_run(int port) -{ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE) && - pd_timer_is_disabled(port, PE_TIMER_VDM_RESPONSE)) { - /* Message was sent */ - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - - /* Start no response timer */ - /* TODO(b/155890173): Support DPM-supplied timeout */ - pd_timer_enable(port, PE_TIMER_VDM_RESPONSE, - PD_T_VDM_SNDR_RSP); - } - - if (PE_CHK_FLAG(port, PE_FLAGS_MSG_DISCARDED)) { - /* - * Go back to ready on first AMS message discard - * (ready states will clear the discard flag) - */ - pe_set_ready_state(port); - return; - } - - /* - * Check the VDM timer, child will be responsible for processing - * messages and reacting appropriately to unexpected messages. - */ - if (pd_timer_is_expired(port, PE_TIMER_VDM_RESPONSE)) { - CPRINTF("VDM %s Response Timeout\n", - pe[port].tx_type == TCPCI_MSG_SOP ? - "Port" : "Cable"); - /* - * Flag timeout so child state can mark appropriate discovery - * item as failed. - */ - PE_SET_FLAG(port, PE_FLAGS_VDM_REQUEST_TIMEOUT); - - set_state_pe(port, get_last_state_pe(port)); - } -} - -static void pe_vdm_send_request_exit(int port) -{ - /* - * Clear TX complete in case child called set_state_pe() before parent - * could process transmission - */ - PE_CLR_FLAG(port, PE_FLAGS_INTERRUPTIBLE_AMS); - - /* Invalidate TX type so it must be set before next call */ - pe[port].tx_type = TCPCI_MSG_INVALID; - - pd_timer_disable(port, PE_TIMER_VDM_RESPONSE); -} - -/** - * PE_VDM_IDENTITY_REQUEST_CBL - * Combination of PE_INIT_PORT_VDM_Identity_Request State specific to the - * cable and PE_SRC_VDM_Identity_Request State. - * pe[port].tx_type must be set (to SOP') prior to entry. - */ -static void pe_vdm_identity_request_cbl_entry(int port) -{ - uint32_t *msg = (uint32_t *)tx_emsg[port].buf; - - print_current_state(port); - - if (!pe_can_send_sop_prime(port)) { - /* - * The parent state already tried to enable SOP' traffic. If it - * is still disabled, there's nothing left to try. - */ - pd_set_identity_discovery(port, pe[port].tx_type, PD_DISC_FAIL); - set_state_pe(port, get_last_state_pe(port)); - return; - } - - msg[0] = VDO(USB_SID_PD, 1, - VDO_SVDM_VERS(pd_get_vdo_ver(port, pe[port].tx_type)) | - CMD_DISCOVER_IDENT); - tx_emsg[port].len = sizeof(uint32_t); - - send_data_msg(port, pe[port].tx_type, PD_DATA_VENDOR_DEF); - - pe[port].discover_identity_counter++; - - /* - * Valid DiscoverIdentity responses should have at least 4 objects - * (header, ID header, Cert Stat, Product VDO). - */ - pe[port].vdm_ack_min_data_objects = 4; -} - -static void pe_vdm_identity_request_cbl_run(int port) -{ - /* Retrieve the message information */ - uint32_t *payload = (uint32_t *) rx_emsg[port].buf; - int sop = PD_HEADER_GET_SOP(rx_emsg[port].header); - uint8_t type = PD_HEADER_TYPE(rx_emsg[port].header); - uint8_t cnt = PD_HEADER_CNT(rx_emsg[port].header); - uint8_t ext = PD_HEADER_EXT(rx_emsg[port].header); - - switch (parse_vdm_response_common(port)) { - case VDM_RESULT_WAITING: - /* - * The common code didn't parse a message. Handle protocol - * errors; otherwise, continue waiting. - */ - if (PE_CHK_FLAG(port, PE_FLAGS_PROTOCOL_ERROR)) { - /* - * No Good CRC: See section 6.4.4.3.1 - Discover - * Identity. - * - * Discover Identity Command request sent to SOP' Shall - * Not cause a Soft Reset if a GoodCRC Message response - * is not returned since this can indicate a non-PD - * Capable cable. - */ - PE_CLR_FLAG(port, PE_FLAGS_PROTOCOL_ERROR); - set_state_pe(port, get_last_state_pe(port)); - } - return; - case VDM_RESULT_NO_ACTION: - /* - * If the received message doesn't change the discovery state, - * there is nothing to do but return to the previous ready - * state. - */ - if (get_last_state_pe(port) == PE_SRC_DISCOVERY && - (sop != pe[port].tx_type || - type != PD_DATA_VENDOR_DEF || - cnt == 0 || ext != 0)) { - /* - * Unexpected non-VDM received: Before an explicit - * contract, an unexpected message shall generate a soft - * reset using the SOP* of the incoming message. - */ - pe_send_soft_reset(port, sop); - return; - } - break; - case VDM_RESULT_ACK: - /* PE_INIT_PORT_VDM_Identity_ACKed embedded here */ - dfp_consume_identity(port, sop, cnt, payload); - - /* - * Note: If port partner runs PD 2.0, we must use PD 2.0 to - * communicate with the cable plug when in an explicit contract. - * - * PD Spec Table 6-2: Revision Interoperability during an - * Explicit Contract - */ - if (prl_get_rev(port, TCPCI_MSG_SOP) != PD_REV20) - prl_set_rev(port, sop, - PD_HEADER_REV(rx_emsg[port].header)); - break; - case VDM_RESULT_NAK: - /* PE_INIT_PORT_VDM_IDENTITY_NAKed embedded here */ - pd_set_identity_discovery(port, pe[port].tx_type, PD_DISC_FAIL); - break; - } - - /* Return to calling state (PE_{SRC,SNK}_Ready or PE_SRC_Discovery) */ - set_state_pe(port, get_last_state_pe(port)); -} - -static void pe_vdm_identity_request_cbl_exit(int port) -{ - /* - * When cable GoodCRCs but does not reply, down-rev to PD 2.0 and try - * again. - * - * PD 3.0 Rev 2.0 6.2.1.1.5 Specification Revision - * - * "When a Cable Plug does not respond to a Revision 3.0 Discover - * Identity REQ with a Discover Identity ACK or BUSY the Vconn Source - * May repeat steps 1-4 using a Revision 2.0 Discover Identity REQ in - * step 1 before establishing that there is no Cable Plug to - * communicate with" - */ - if (PE_CHK_FLAG(port, PE_FLAGS_VDM_REQUEST_TIMEOUT)) { - PE_CLR_FLAG(port, PE_FLAGS_VDM_REQUEST_TIMEOUT); - prl_set_rev(port, TCPCI_MSG_SOP_PRIME, PD_REV20); - } - - /* - * 6.6.15 DiscoverIdentityTimer - * - * No more than nDiscoverIdentityCount Discover Identity Messages - * without a GoodCRC Message response Shall be sent. If no GoodCRC - * Message response is received after nDiscoverIdentityCount Discover - * Identity Command requests have been sent by a Port, the Port Shall - * Not send any further SOP’/SOP’’ Messages. - */ - if (pe[port].discover_identity_counter >= N_DISCOVER_IDENTITY_COUNT) - pd_set_identity_discovery(port, pe[port].tx_type, - PD_DISC_FAIL); - else if (pe[port].discover_identity_counter == - N_DISCOVER_IDENTITY_PD3_0_LIMIT) - /* - * Downgrade to PD 2.0 if the partner hasn't replied before - * all retries are exhausted in case the cable is - * non-compliant about GoodCRC-ing higher revisions - */ - prl_set_rev(port, TCPCI_MSG_SOP_PRIME, PD_REV20); - - /* - * Set discover identity timer unless BUSY case already did so. - */ - if (pd_get_identity_discovery(port, pe[port].tx_type) == PD_DISC_NEEDED - && pd_timer_is_expired(port, PE_TIMER_DISCOVER_IDENTITY)) { - /* - * The tDiscoverIdentity timer is used during an explicit - * contract when discovering whether a cable is PD capable. - * - * Pre-contract, slow the rate Discover Identity commands are - * sent. This permits operation with captive cable devices that - * power the SOP' responder from VBUS instead of VCONN. - */ - pd_timer_enable(port, PE_TIMER_DISCOVER_IDENTITY, - pe_is_explicit_contract(port) - ? PD_T_DISCOVER_IDENTITY - : PE_T_DISCOVER_IDENTITY_NO_CONTRACT); - } - - /* Do not attempt further discovery if identity discovery failed. */ - if (pd_get_identity_discovery(port, pe[port].tx_type) == PD_DISC_FAIL) { - pd_set_svids_discovery(port, pe[port].tx_type, PD_DISC_FAIL); - pd_notify_event(port, pe[port].tx_type == TCPCI_MSG_SOP ? - PD_STATUS_EVENT_SOP_DISC_DONE : - PD_STATUS_EVENT_SOP_PRIME_DISC_DONE); - } -} - -/** - * PE_INIT_PORT_VDM_Identity_Request - * - * Specific to SOP requests, as cables require additions for the discover - * identity counter, must tolerate not receiving a GoodCRC, and need to set the - * cable revision based on response. - * pe[port].tx_type must be set (to SOP) prior to entry. - */ -static void pe_init_port_vdm_identity_request_entry(int port) -{ - uint32_t *msg = (uint32_t *)tx_emsg[port].buf; - - print_current_state(port); - - msg[0] = VDO(USB_SID_PD, 1, - VDO_SVDM_VERS(pd_get_vdo_ver(port, pe[port].tx_type)) | - CMD_DISCOVER_IDENT); - tx_emsg[port].len = sizeof(uint32_t); - - send_data_msg(port, pe[port].tx_type, PD_DATA_VENDOR_DEF); - - /* - * Valid DiscoverIdentity responses should have at least 4 objects - * (header, ID header, Cert Stat, Product VDO). - */ - pe[port].vdm_ack_min_data_objects = 4; -} - -static void pe_init_port_vdm_identity_request_run(int port) -{ - switch (parse_vdm_response_common(port)) { - case VDM_RESULT_WAITING: - /* If common code didn't parse a message, continue waiting. */ - return; - case VDM_RESULT_NO_ACTION: - /* - * If the received message doesn't change the discovery state, - * there is nothing to do but return to the previous ready - * state. - */ - break; - case VDM_RESULT_ACK: { - /* Retrieve the message information. */ - uint32_t *payload = (uint32_t *) rx_emsg[port].buf; - int sop = PD_HEADER_GET_SOP(rx_emsg[port].header); - uint8_t cnt = PD_HEADER_CNT(rx_emsg[port].header); - - /* PE_INIT_PORT_VDM_Identity_ACKed embedded here */ - dfp_consume_identity(port, sop, cnt, payload); - - break; - } - case VDM_RESULT_NAK: - /* PE_INIT_PORT_VDM_IDENTITY_NAKed embedded here */ - pd_set_identity_discovery(port, pe[port].tx_type, PD_DISC_FAIL); - break; - } - - /* Return to calling state (PE_{SRC,SNK}_Ready) */ - set_state_pe(port, get_last_state_pe(port)); -} - -static void pe_init_port_vdm_identity_request_exit(int port) -{ - if (PE_CHK_FLAG(port, PE_FLAGS_VDM_REQUEST_TIMEOUT)) { - PE_CLR_FLAG(port, PE_FLAGS_VDM_REQUEST_TIMEOUT); - /* - * Mark failure to respond as discovery failure. - * - * For PD 2.0 partners (6.10.3 Applicability of Structured VDM - * Commands Note 3): - * - * If Structured VDMs are not supported, a Structured VDM - * Command received by a DFP or UFP Shall be Ignored. - */ - pd_set_identity_discovery(port, pe[port].tx_type, PD_DISC_FAIL); - } - - /* Do not attempt further discovery if identity discovery failed. */ - if (pd_get_identity_discovery(port, pe[port].tx_type) == PD_DISC_FAIL) { - pd_set_svids_discovery(port, pe[port].tx_type, PD_DISC_FAIL); - pd_notify_event(port, pe[port].tx_type == TCPCI_MSG_SOP ? - PD_STATUS_EVENT_SOP_DISC_DONE : - PD_STATUS_EVENT_SOP_PRIME_DISC_DONE); - } -} - -/** - * PE_INIT_VDM_SVIDs_Request - * - * Used for SOP and SOP' requests, selected by pe[port].tx_type prior to entry. - */ -static void pe_init_vdm_svids_request_entry(int port) -{ - uint32_t *msg = (uint32_t *)tx_emsg[port].buf; - - print_current_state(port); - - if (pe[port].tx_type == TCPCI_MSG_SOP_PRIME && - !pe_can_send_sop_prime(port)) { - /* - * The parent state already tried to enable SOP' traffic. If it - * is still disabled, there's nothing left to try. - */ - pd_set_svids_discovery(port, pe[port].tx_type, PD_DISC_FAIL); - set_state_pe(port, get_last_state_pe(port)); - return; - } - - msg[0] = VDO(USB_SID_PD, 1, - VDO_SVDM_VERS(pd_get_vdo_ver(port, pe[port].tx_type)) | - CMD_DISCOVER_SVID); - tx_emsg[port].len = sizeof(uint32_t); - - send_data_msg(port, pe[port].tx_type, PD_DATA_VENDOR_DEF); - - /* - * Valid Discover SVIDs ACKs should have at least 2 objects (VDM header - * and at least 1 SVID VDO). - */ - pe[port].vdm_ack_min_data_objects = 2; -} - -static void pe_init_vdm_svids_request_run(int port) -{ - switch (parse_vdm_response_common(port)) { - case VDM_RESULT_WAITING: - /* If common code didn't parse a message, continue waiting. */ - return; - case VDM_RESULT_NO_ACTION: - /* - * If the received message doesn't change the discovery state, - * there is nothing to do but return to the previous ready - * state. - */ - break; - case VDM_RESULT_ACK: { - /* Retrieve the message information. */ - uint32_t *payload = (uint32_t *) rx_emsg[port].buf; - int sop = PD_HEADER_GET_SOP(rx_emsg[port].header); - uint8_t cnt = PD_HEADER_CNT(rx_emsg[port].header); - - /* PE_INIT_VDM_SVIDs_ACKed embedded here */ - dfp_consume_svids(port, sop, cnt, payload); - break; - } - case VDM_RESULT_NAK: - /* PE_INIT_VDM_SVIDs_NAKed embedded here */ - pd_set_svids_discovery(port, pe[port].tx_type, PD_DISC_FAIL); - break; - } - - /* Return to calling state (PE_{SRC,SNK}_Ready) */ - set_state_pe(port, get_last_state_pe(port)); -} - -static void pe_init_vdm_svids_request_exit(int port) -{ - if (PE_CHK_FLAG(port, PE_FLAGS_VDM_REQUEST_TIMEOUT)) { - PE_CLR_FLAG(port, PE_FLAGS_VDM_REQUEST_TIMEOUT); - /* - * Mark failure to respond as discovery failure. - * - * For PD 2.0 partners (6.10.3 Applicability of Structured VDM - * Commands Note 3): - * - * If Structured VDMs are not supported, a Structured VDM - * Command received by a DFP or UFP Shall be Ignored. - */ - pd_set_svids_discovery(port, pe[port].tx_type, PD_DISC_FAIL); - } - - /* If SVID discovery failed, discovery is done at this point */ - if (pd_get_svids_discovery(port, pe[port].tx_type) == PD_DISC_FAIL) - pd_notify_event(port, pe[port].tx_type == TCPCI_MSG_SOP ? - PD_STATUS_EVENT_SOP_DISC_DONE : - PD_STATUS_EVENT_SOP_PRIME_DISC_DONE); -} - -/** - * PE_INIT_VDM_Modes_Request - * - * Used for SOP and SOP' requests, selected by pe[port].tx_type prior to entry. - */ -static void pe_init_vdm_modes_request_entry(int port) -{ - uint32_t *msg = (uint32_t *)tx_emsg[port].buf; - const struct svid_mode_data *mode_data = - pd_get_next_mode(port, pe[port].tx_type); - uint16_t svid; - /* - * The caller should have checked that there was something to discover - * before entering this state. - */ - assert(mode_data); - assert(mode_data->discovery == PD_DISC_NEEDED); - svid = mode_data->svid; - - print_current_state(port); - - if (pe[port].tx_type == TCPCI_MSG_SOP_PRIME && - !pe_can_send_sop_prime(port)) { - /* - * The parent state already tried to enable SOP' traffic. If it - * is still disabled, there's nothing left to try. - */ - pd_set_modes_discovery(port, pe[port].tx_type, svid, - PD_DISC_FAIL); - set_state_pe(port, get_last_state_pe(port)); - return; - } - - msg[0] = VDO((uint16_t) svid, 1, - VDO_SVDM_VERS(pd_get_vdo_ver(port, pe[port].tx_type)) | - CMD_DISCOVER_MODES); - tx_emsg[port].len = sizeof(uint32_t); - - send_data_msg(port, pe[port].tx_type, PD_DATA_VENDOR_DEF); - - /* - * Valid Discover Modes responses should have at least 2 objects (VDM - * header and at least 1 mode VDO). - */ - pe[port].vdm_ack_min_data_objects = 2; -} - -static void pe_init_vdm_modes_request_run(int port) -{ - const struct svid_mode_data *mode_data; - uint16_t requested_svid; - - mode_data = pd_get_next_mode(port, pe[port].tx_type); - - assert(mode_data); - assert(mode_data->discovery == PD_DISC_NEEDED); - requested_svid = mode_data->svid; - - switch (parse_vdm_response_common(port)) { - case VDM_RESULT_WAITING: - /* If common code didn't parse a message, continue waiting. */ - return; - case VDM_RESULT_NO_ACTION: - /* - * If the received message doesn't change the discovery state, - * there is nothing to do but return to the previous ready - * state. - */ - break; - case VDM_RESULT_ACK: { - /* Retrieve the message information. */ - uint32_t *payload = (uint32_t *) rx_emsg[port].buf; - int sop = PD_HEADER_GET_SOP(rx_emsg[port].header); - uint8_t cnt = PD_HEADER_CNT(rx_emsg[port].header); - uint16_t response_svid = (uint16_t) PD_VDO_VID(payload[0]); - - /* - * Accept ACK if the request and response SVIDs are equal; - * otherwise, treat this as a NAK of the request SVID. - * - * TODO(b:169242812): support valid mode checking in - * dfp_consume_modes. - */ - if (requested_svid == response_svid) { - /* PE_INIT_VDM_Modes_ACKed embedded here */ - dfp_consume_modes(port, sop, cnt, payload); - break; - } - } - /* Fall Through */ - case VDM_RESULT_NAK: - /* PE_INIT_VDM_Modes_NAKed embedded here */ - pd_set_modes_discovery(port, pe[port].tx_type, requested_svid, - PD_DISC_FAIL); - break; - } - - /* Return to calling state (PE_{SRC,SNK}_Ready) */ - set_state_pe(port, get_last_state_pe(port)); -} - -static void pe_init_vdm_modes_request_exit(int port) -{ - if (pd_get_modes_discovery(port, pe[port].tx_type) != PD_DISC_NEEDED) - /* Mode discovery done, notify the AP */ - pd_notify_event(port, pe[port].tx_type == TCPCI_MSG_SOP ? - PD_STATUS_EVENT_SOP_DISC_DONE : - PD_STATUS_EVENT_SOP_PRIME_DISC_DONE); - -} - -/** - * PE_VDM_REQUEST_DPM - * - * Makes a VDM request with contents and SOP* type previously set up by the DPM. - */ - -static void pe_vdm_request_dpm_entry(int port) -{ - print_current_state(port); - - if ((pe[port].tx_type == TCPCI_MSG_SOP_PRIME || - pe[port].tx_type == TCPCI_MSG_SOP_PRIME_PRIME) && - !pe_can_send_sop_prime(port)) { - /* - * The parent state already tried to enable SOP' traffic. If it - * is still disabled, there's nothing left to try. - */ - dpm_vdm_naked(port, pe[port].tx_type, - PD_VDO_VID(pe[port].vdm_data[0]), - PD_VDO_CMD(pe[port].vdm_data[0])); - set_state_pe(port, get_last_state_pe(port)); - return; - } - - /* Copy Vendor Data Objects (VDOs) into message buffer */ - if (pe[port].vdm_cnt > 0) { - /* Copy data after header */ - memcpy(&tx_emsg[port].buf, - (uint8_t *)pe[port].vdm_data, - pe[port].vdm_cnt * 4); - /* Update len with the number of VDO bytes */ - tx_emsg[port].len = pe[port].vdm_cnt * 4; - } - - /* - * Clear the VDM nak'ed flag so that each request is - * treated separately (NAKs are handled by the - * DPM layer). Otherwise previous NAKs received will - * cause the state to exit early. - */ - PE_CLR_FLAG(port, PE_FLAGS_VDM_REQUEST_NAKED); - send_data_msg(port, pe[port].tx_type, PD_DATA_VENDOR_DEF); - - /* - * In general, valid VDM ACKs must have a VDM header. Other than that, - * ACKs must be validated based on the command and SVID. - */ - pe[port].vdm_ack_min_data_objects = 1; -} - -static void pe_vdm_request_dpm_run(int port) -{ - uint32_t vdm_hdr; - - switch (parse_vdm_response_common(port)) { - case VDM_RESULT_WAITING: - /* - * USB-PD 3.0 Rev 1.1 - 6.4.4.2.5 - * Structured VDM command consists of a command request and a - * command response (ACK, NAK, or BUSY). An exception is made - * for the Attention command which shall have no response. - * - * Since Attention commands do not have an expected reply, - * the SVDM command is complete once the Attention command - * transmit is complete. - */ - vdm_hdr = pe[port].vdm_data[0]; - if(PD_VDO_SVDM(vdm_hdr) && - (PD_VDO_CMD(vdm_hdr) == CMD_ATTENTION)) { - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - break; - } - } - /* - * If common code didn't parse a message, and the VDM - * just sent was not an Attention message, then continue - * waiting. - */ - return; - case VDM_RESULT_NO_ACTION: - /* - * If the received message doesn't change the discovery state, - * there is nothing to do but return to the previous ready - * state. This includes Attention commands which have no - * expected SVDM response. - */ - break; - case VDM_RESULT_ACK: { - /* Retrieve the message information. */ - uint32_t *payload = (uint32_t *) rx_emsg[port].buf; - int sop = PD_HEADER_GET_SOP(rx_emsg[port].header); - uint8_t cnt = PD_HEADER_CNT(rx_emsg[port].header); - uint16_t svid = PD_VDO_VID(payload[0]); - uint8_t vdm_cmd = PD_VDO_CMD(payload[0]); - - /* - * PE initiator VDM-ACKed state for requested VDM, like - * PE_INIT_VDM_FOO_ACKed, embedded here. - */ - dpm_vdm_acked(port, sop, cnt, payload); - - if (sop == TCPCI_MSG_SOP && svid == USB_SID_DISPLAYPORT && - vdm_cmd == CMD_DP_CONFIG) { - PE_SET_FLAG(port, PE_FLAGS_VDM_SETUP_DONE); - } - break; - } - case VDM_RESULT_NAK: - /* - * PE initiator VDM-NAKed state for requested VDM, like - * PE_INIT_VDM_FOO_NAKed, embedded here. - */ - PE_SET_FLAG(port, PE_FLAGS_VDM_SETUP_DONE); - - /* - * Because Not Supported messages or response timeouts are - * treated as NAKs, there may not be a NAK message to parse. - * Extract the needed information from the sent VDM. - */ - dpm_vdm_naked(port, pe[port].tx_type, - PD_VDO_VID(pe[port].vdm_data[0]), - PD_VDO_CMD(pe[port].vdm_data[0])); - break; - } - - /* Return to calling state (PE_{SRC,SNK}_Ready) */ - set_state_pe(port, get_last_state_pe(port)); -} - -static void pe_vdm_request_dpm_exit(int port) -{ - /* - * Force Tx type to be reset before reentering a VDM state, unless the - * current VDM request will be resumed. - */ - if (!PE_CHK_FLAG(port, PE_FLAGS_VDM_REQUEST_CONTINUE)) - pe[port].tx_type = TCPCI_MSG_INVALID; -} - -/** - * PE_VDM_Response - */ -static void pe_vdm_response_entry(int port) -{ - int vdo_len = 0; - uint32_t *rx_payload; - uint32_t *tx_payload; - uint8_t vdo_cmd; - svdm_rsp_func func = NULL; - - print_current_state(port); - - /* This is an Interruptible AMS */ - PE_SET_FLAG(port, PE_FLAGS_INTERRUPTIBLE_AMS); - - /* Get the message */ - rx_payload = (uint32_t *)rx_emsg[port].buf; - - /* Extract VDM command from the VDM header */ - vdo_cmd = PD_VDO_CMD(rx_payload[0]); - /* This must be a command request to proceed further */ - if (PD_VDO_CMDT(rx_payload[0]) != CMDT_INIT) { - CPRINTF("ERR:CMDT:%d:%d\n", PD_VDO_CMDT(rx_payload[0]), - vdo_cmd); - - pe_set_ready_state(port); - return; - } - - tx_payload = (uint32_t *)tx_emsg[port].buf; - /* - * Designed in TCPMv1, svdm_response functions use same - * buffer to take received data and overwrite with response - * data. To work with this interface, here copy rx data to - * tx buffer and pass tx_payload to func. - * TODO(b/166455363): change the interface to pass both rx - * and tx buffer. - * - * The SVDM header is dependent on both VDM command request being - * replied to and the result of response function. The SVDM command - * message is copied into tx_payload. tx_payload[0] is the VDM header - * for the response message. The SVDM response function takes the role - * of the DPM layer and will indicate the response type (ACK/NAK/BUSY) - * by its return value (vdo_len) - * vdo_len > 0 --> ACK - * vdo_len == 0 --> NAK - * vdo_len < 0 --> BUSY - */ - memcpy(tx_payload, rx_payload, PD_HEADER_CNT(rx_emsg[port].header) * 4); - /* - * Clear fields in SVDM response message that will be set based on the - * result of the svdm response function. - */ - tx_payload[0] &= ~VDO_CMDT_MASK; - tx_payload[0] &= ~VDO_SVDM_VERS(0x3); - - /* Add SVDM structured version being used */ - tx_payload[0] |= VDO_SVDM_VERS(pd_get_vdo_ver(port, TCPCI_MSG_SOP)); - - /* Use VDM command to select the response handler function */ - switch (vdo_cmd) { - case CMD_DISCOVER_IDENT: - func = svdm_rsp.identity; - break; - case CMD_DISCOVER_SVID: - func = svdm_rsp.svids; - break; - case CMD_DISCOVER_MODES: - func = svdm_rsp.modes; - break; - case CMD_ENTER_MODE: - func = svdm_rsp.enter_mode; - break; - case CMD_DP_STATUS: - if (svdm_rsp.amode) - func = svdm_rsp.amode->status; - break; - case CMD_DP_CONFIG: - if (svdm_rsp.amode) - func = svdm_rsp.amode->config; - break; - case CMD_EXIT_MODE: - func = svdm_rsp.exit_mode; - break; -#ifdef CONFIG_USB_PD_ALT_MODE_DFP - case CMD_ATTENTION: - /* - * attention is only SVDM with no response - * (just goodCRC) return zero here. - */ - dfp_consume_attention(port, rx_payload); - pe_set_ready_state(port); - return; -#endif - default: - CPRINTF("VDO ERR:CMD:%d\n", vdo_cmd); - } - - /* - * If the port partner is PD_REV20 and our data role is DFP, we must - * reply to any SVDM command with a NAK. If the SVDM was an Attention - * command, it does not have a response, and exits the function above. - */ - if (func && (prl_get_rev(port, TCPCI_MSG_SOP) != PD_REV20 || - pe[port].data_role == PD_ROLE_UFP)) { - /* - * Execute SVDM response function selected above and set the - * correct response type in the VDM header. - */ - vdo_len = func(port, tx_payload); - if (vdo_len > 0) { - tx_payload[0] |= VDO_CMDT(CMDT_RSP_ACK); - /* - * If command response is an ACK and if the command was - * either enter/exit mode, then update the PE modal flag - * accordingly. - */ - if (vdo_cmd == CMD_ENTER_MODE) - PE_SET_FLAG(port, PE_FLAGS_MODAL_OPERATION); - if (vdo_cmd == CMD_EXIT_MODE) - PE_CLR_FLAG(port, PE_FLAGS_MODAL_OPERATION); - } else if (!vdo_len) { - tx_payload[0] |= VDO_CMDT(CMDT_RSP_NAK); - vdo_len = 1; - } else { - tx_payload[0] |= VDO_CMDT(CMDT_RSP_BUSY); - vdo_len = 1; - } - } else { - /* - * Received at VDM command which is not supported. PD 2.0 may - * NAK or ignore the message (see TD.PD.VNDI.E1. VDM Identity - * steps), but PD 3.0 must send Not_Supported (PD 3.0 Ver 2.0 + - * ECNs 2020-12-10 Table 6-64 Response to an incoming - * VDM or TD.PD.VNDI3.E3 VDM Identity steps) - */ - if (prl_get_rev(port, TCPCI_MSG_SOP) == PD_REV30) { - set_state_pe(port, PE_SEND_NOT_SUPPORTED); - return; - } - tx_payload[0] |= VDO_CMDT(CMDT_RSP_NAK); - vdo_len = 1; - } - - /* Send response message. Note len is in bytes, not VDO objects */ - tx_emsg[port].len = (vdo_len * sizeof(uint32_t)); - send_data_msg(port, TCPCI_MSG_SOP, PD_DATA_VENDOR_DEF); -} - -static void pe_vdm_response_run(int port) -{ - /* - * This state waits for a VDM response message to be sent. Return to the - * ready state once the message has been sent, a protocol error was - * detected, or if the VDM response msg was discarded based on being - * interrupted by another rx message. Since VDM sequences are AMS - * interruptible, there is no need to soft reset regardless of exit - * reason. - */ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE) || - PE_CHK_FLAG(port, PE_FLAGS_PROTOCOL_ERROR) || - PE_CHK_FLAG(port, PE_FLAGS_MSG_DISCARDED)) { - - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE | - PE_FLAGS_PROTOCOL_ERROR | - PE_FLAGS_MSG_DISCARDED); - - pe_set_ready_state(port); - } -} - -static void pe_vdm_response_exit(int port) -{ - PE_CLR_FLAG(port, PE_FLAGS_INTERRUPTIBLE_AMS); -} - -/** - * PE_DEU_SEND_ENTER_USB - */ -static void pe_enter_usb_entry(int port) -{ - uint32_t usb4_payload; - - print_current_state(port); - - if (!IS_ENABLED(CONFIG_USB_PD_USB4)) { - pe_set_ready_state(port); - return; - } - - /* Port is already in USB4 mode, do not send enter USB message again */ - if (enter_usb_entry_is_done(port)) { - pe_set_ready_state(port); - return; - } - - if ((pe[port].tx_type == TCPCI_MSG_SOP_PRIME || - pe[port].tx_type == TCPCI_MSG_SOP_PRIME_PRIME) && - !tc_is_vconn_src(port)) { - if (port_try_vconn_swap(port)) - return; - } - - pe[port].tx_type = TCPCI_MSG_SOP; - usb4_payload = enter_usb_setup_next_msg(port, &pe[port].tx_type); - - if (!usb4_payload) { - enter_usb_failed(port); - pe_set_ready_state(port); - return; - } - - tx_emsg[port].len = sizeof(usb4_payload); - - memcpy(tx_emsg[port].buf, &usb4_payload, tx_emsg[port].len); - send_data_msg(port, pe[port].tx_type, PD_DATA_ENTER_USB); - pe_sender_response_msg_entry(port); -} - -static void pe_enter_usb_run(int port) -{ - enum pe_msg_check msg_check; - - if (!IS_ENABLED(CONFIG_USB_PD_USB4)) { - pe_set_ready_state(port); - return; - } - - /* - * Check the state of the message sent - */ - msg_check = pe_sender_response_msg_run(port); - - /* - * Handle Discarded message, return to PE_SNK/SRC_READY - */ - if (msg_check & PE_MSG_DISCARDED) { - pe_set_ready_state(port); - return; - } else if (msg_check == PE_MSG_SEND_PENDING) { - /* Wait until message is sent */ - return; - } - - if (pd_timer_is_expired(port, PE_TIMER_SENDER_RESPONSE)) { - pe_set_ready_state(port); - enter_usb_failed(port); - return; - } - - if (PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - int cnt = PD_HEADER_CNT(rx_emsg[port].header); - int type = PD_HEADER_TYPE(rx_emsg[port].header); - int sop = PD_HEADER_GET_SOP(rx_emsg[port].header); - - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - - /* Only look at control messages */ - if (cnt == 0) { - /* Accept message received */ - if (type == PD_CTRL_ACCEPT) { - enter_usb_accepted(port, sop); - } else if (type == PD_CTRL_REJECT) { - enter_usb_rejected(port, sop); - } else { - /* - * Unexpected control message received. - * Send Soft Reset. - */ - pe_send_soft_reset(port, sop); - return; - } - } else { - /* Unexpected data message received. Send Soft reset */ - pe_send_soft_reset(port, sop); - return; - } - pe_set_ready_state(port); - } -} - -static void pe_enter_usb_exit(int port) -{ - pe_sender_response_msg_exit(port); -} - -#ifdef CONFIG_USBC_VCONN -/* - * PE_VCS_Evaluate_Swap - */ -static void pe_vcs_evaluate_swap_entry(int port) -{ - print_current_state(port); - - /* - * Request the DPM for an evaluation of the VCONN Swap request. - * Note: Ports that are presently the VCONN Source must always - * accept a VCONN - */ - - /* - * Transition to the PE_VCS_Accept_Swap state when: - * 1) The Device Policy Manager indicates that a VCONN Swap is ok. - * - * Transition to the PE_VCS_Reject_Swap state when: - * 1) Port is not presently the VCONN Source and - * 2) The DPM indicates that a VCONN Swap is not ok or - * 3) The DPM indicates that a VCONN Swap cannot be done at this time. - */ - - /* DPM rejects a VCONN Swap and port is not a VCONN source*/ - if (!tc_check_vconn_swap(port) && tc_is_vconn_src(port) < 1) { - /* NOTE: PE_VCS_Reject_Swap State embedded here */ - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_REJECT); - } - /* Port is not ready to perform a VCONN swap */ - else if (tc_is_vconn_src(port) < 0) { - /* NOTE: PE_VCS_Reject_Swap State embedded here */ - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_WAIT); - } - /* Port is ready to perform a VCONN swap */ - else { - /* NOTE: PE_VCS_Accept_Swap State embedded here */ - PE_SET_FLAG(port, PE_FLAGS_ACCEPT); - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_ACCEPT); - } -} - -static void pe_vcs_evaluate_swap_run(int port) -{ - /* Wait for ACCEPT, WAIT or Reject message to send. */ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - - if (PE_CHK_FLAG(port, PE_FLAGS_ACCEPT)) { - PE_CLR_FLAG(port, PE_FLAGS_ACCEPT); - /* Accept Message sent and Presently VCONN Source */ - if (tc_is_vconn_src(port)) - set_state_pe(port, PE_VCS_WAIT_FOR_VCONN_SWAP); - /* Accept Message sent and Not presently VCONN Source */ - else - set_state_pe(port, PE_VCS_TURN_ON_VCONN_SWAP); - } else { - /* - * Message sent. Transition back to PE_SRC_Ready or - * PE_SINK_Ready - */ - pe_set_ready_state(port); - } - return; - } - - if (pe_check_outgoing_discard(port)) - return; -} - -/* - * PE_VCS_Send_Swap - */ -static void pe_vcs_send_swap_entry(int port) -{ - print_current_state(port); - - /* Send a VCONN_Swap Message */ - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_VCONN_SWAP); - pe_sender_response_msg_entry(port); -} - -static void pe_vcs_send_swap_run(int port) -{ - uint8_t type; - uint8_t cnt; - enum tcpci_msg_type sop; - enum pe_msg_check msg_check; - - /* - * Check the state of the message sent - */ - msg_check = pe_sender_response_msg_run(port); - - if ((msg_check & PE_MSG_SENT) && - PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - - /* Increment once message has successfully sent */ - pe[port].vconn_swap_counter++; - - type = PD_HEADER_TYPE(rx_emsg[port].header); - cnt = PD_HEADER_CNT(rx_emsg[port].header); - sop = PD_HEADER_GET_SOP(rx_emsg[port].header); - - /* Only look at control messages */ - if (cnt == 0) { - /* - * Transition to the PE_VCS_Wait_For_VCONN state when: - * 1) Accept Message Received and - * 2) The Port is presently the VCONN Source. - * - * Transition to the PE_VCS_Turn_On_VCONN state when: - * 1) Accept Message Received and - * 2) The Port is not presently the VCONN Source. - */ - if (type == PD_CTRL_ACCEPT) { - if (tc_is_vconn_src(port)) { - set_state_pe(port, - PE_VCS_WAIT_FOR_VCONN_SWAP); - } else { - set_state_pe(port, - PE_VCS_TURN_ON_VCONN_SWAP); - } - return; - } - /* - * Transition back to either the PE_SRC_Ready or - * PE_SNK_Ready state when: - * 2) Reject message is received or - * 3) Wait message Received. - */ - if (type == PD_CTRL_REJECT || type == PD_CTRL_WAIT) { - pe_set_ready_state(port); - return; - } - - /* - * The Policy Engine May transition to the - * PE_VCS_Force_Vconn state when: - * - A Not_Supported Message is received and - * - The Port is not presently the VCONN Source - */ - if (type == PD_CTRL_NOT_SUPPORTED) { - if (IS_ENABLED(CONFIG_USB_PD_REV30) && - !tc_is_vconn_src(port)) - set_state_pe(port, PE_VCS_FORCE_VCONN); - else - pe_set_ready_state(port); - return; - } - } - /* - * Unexpected Message Received, send soft reset with SOP* of - * incoming message. - */ - pe_send_soft_reset(port, sop); - return; - } - - /* - * Transition back to either the PE_SRC_Ready or - * PE_SNK_Ready state when: - * 1) SenderResponseTimer Timeout - * 2) Message was discarded. - */ - if ((msg_check & PE_MSG_DISCARDED) || - pd_timer_is_expired(port, PE_TIMER_SENDER_RESPONSE)) - pe_set_ready_state(port); -} - -static void pe_vcs_send_swap_exit(int port) -{ - pe_sender_response_msg_exit(port); -} - -/* - * PE_VCS_Wait_for_VCONN_Swap - */ -static void pe_vcs_wait_for_vconn_swap_entry(int port) -{ - print_current_state(port); - - /* Start the VCONNOnTimer */ - pd_timer_enable(port, PE_TIMER_VCONN_ON, PD_T_VCONN_SOURCE_ON); - - /* - * The USB PD 3.0 spec indicates that the initial VCONN source - * shall cease sourcing VCONN within tVCONNSourceOff (25ms) - * after receiving the PS_RDY message. However, some partners - * begin sending SOP' messages only 1 ms after sending PS_RDY - * during VCONN swap. - * - * Preemptively disable receipt of SOP' and SOP'' messages while - * we wait for PS_RDY so we don't attempt to process messages - * directed at the cable. - * - * We continue to source VCONN while we wait as required by the - * spec. - */ - tcpm_sop_prime_enable(port, false); -} - -static void pe_vcs_wait_for_vconn_swap_run(int port) -{ - /* - * Transition to the PE_VCS_Turn_Off_VCONN state when: - * 1) A PS_RDY Message is received. - */ - if (PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - /* - * PS_RDY message received - * - * Note: intentionally leave the receive flag set to indicate - * our route on exit when PS_RDY is received. - */ - if ((PD_HEADER_CNT(rx_emsg[port].header) == 0) && - (PD_HEADER_EXT(rx_emsg[port].header) == 0) && - (PD_HEADER_TYPE(rx_emsg[port].header) == PD_CTRL_PS_RDY)) { - set_state_pe(port, PE_VCS_TURN_OFF_VCONN_SWAP); - return; - } else { - /* - * Unexpected message received - reset with the SOP* of - * the incoming message. - */ - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - pe_send_soft_reset(port, - PD_HEADER_GET_SOP(rx_emsg[port].header)); - return; - } - } - - /* - * Transition to either the PE_SRC_Hard_Reset or - * PE_SNK_Hard_Reset state when: - * 1) The VCONNOnTimer times out. - */ - if (pd_timer_is_expired(port, PE_TIMER_VCONN_ON)) { - if (pe[port].power_role == PD_ROLE_SOURCE) - set_state_pe(port, PE_SRC_HARD_RESET); - else - set_state_pe(port, PE_SNK_HARD_RESET); - } -} - -static void pe_vcs_wait_for_vconn_swap_exit(int port) -{ - /* - * If we exited without getting PS_RDY, re-enable SOP' messaging since - * we are still the Vconn source. - */ - if (PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - else - tcpm_sop_prime_enable(port, true); - - pd_timer_disable(port, PE_TIMER_VCONN_ON); -} - -/* - * PE_VCS_Turn_On_VCONN_Swap - */ -static void pe_vcs_turn_on_vconn_swap_entry(int port) -{ - print_current_state(port); - - /* Request DPM to turn on VCONN */ - pd_request_vconn_swap_on(port); -} - -static void pe_vcs_turn_on_vconn_swap_run(int port) -{ - - /* - * Transition to the PE_VCS_Send_Ps_Rdy state when: - * 1) The Port’s VCONN is on. - */ - if (pd_timer_is_disabled(port, PE_TIMER_TIMEOUT) && - PE_CHK_FLAG(port, PE_FLAGS_VCONN_SWAP_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_VCONN_SWAP_COMPLETE); - pd_timer_enable(port, PE_TIMER_TIMEOUT, - CONFIG_USBC_VCONN_SWAP_DELAY_US); - } - - if (pd_timer_is_expired(port, PE_TIMER_TIMEOUT)) - set_state_pe(port, PE_VCS_SEND_PS_RDY_SWAP); -} - -static void pe_vcs_turn_on_vconn_swap_exit(int port) -{ - pd_timer_disable(port, PE_TIMER_TIMEOUT); -} - -/* - * PE_VCS_Turn_Off_VCONN_Swap - */ -static void pe_vcs_turn_off_vconn_swap_entry(int port) -{ - print_current_state(port); - - /* Request DPM to turn off VCONN */ - pd_request_vconn_swap_off(port); -} - -static void pe_vcs_turn_off_vconn_swap_run(int port) -{ - /* Wait for VCONN to turn off */ - if (PE_CHK_FLAG(port, PE_FLAGS_VCONN_SWAP_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_VCONN_SWAP_COMPLETE); - - /* - * A VCONN Swap Shall reset the DiscoverIdentityCounter - * to zero - */ - pe[port].discover_identity_counter = 0; - pe[port].dr_swap_attempt_counter = 0; - - pe_set_ready_state(port); - return; - } -} - -/* - * PE_VCS_Send_PS_Rdy_Swap - */ -static void pe_vcs_send_ps_rdy_swap_entry(int port) -{ - print_current_state(port); - - /* Check for any interruptions to this non-interruptible AMS */ - if (PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - enum tcpci_msg_type sop = - PD_HEADER_GET_SOP(rx_emsg[port].header); - - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - - /* Soft reset with the SOP* of the incoming message */ - pe_send_soft_reset(port, sop); - return; - } - - /* Send a PS_RDY Message */ - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_PS_RDY); -} - -static void pe_vcs_send_ps_rdy_swap_run(int port) -{ - /* - * After a VCONN Swap the VCONN Source needs to reset - * the Cable Plug’s Protocol Layer in order to ensure - * MessageID synchronization. - */ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - /* - * A VCONN Swap Shall reset the - * DiscoverIdentityCounter to zero - */ - pe[port].discover_identity_counter = 0; - pe[port].dr_swap_attempt_counter = 0; - - /* A SOP' soft reset is required after VCONN swap */ - pd_dpm_request(port, DPM_REQUEST_SOP_PRIME_SOFT_RESET_SEND); - pe_set_ready_state(port); - } - - if (pe_check_outgoing_discard(port)) - return; - - if (PE_CHK_FLAG(port, PE_FLAGS_PROTOCOL_ERROR)) { - PE_CLR_FLAG(port, PE_FLAGS_PROTOCOL_ERROR); - /* PS_RDY didn't send, soft reset */ - pe_send_soft_reset(port, TCPCI_MSG_SOP); - } -} - -/* - * PE_VCS_Force_Vconn - */ -__maybe_unused static void pe_vcs_force_vconn_entry(int port) -{ - print_current_state(port); - - /* Request DPM to turn on VCONN */ - pd_request_vconn_swap_on(port); -} - -__maybe_unused static void pe_vcs_force_vconn_run(int port) -{ - /* - * The Policy Engine Shall transition back to either the PE_SRC_Ready - * or PE_SNK_Ready state when: - * 1) The Port’s VCONN is on. - * - * Note we'll wait CONFIG_USBC_VCONN_SWAP_DELAY_US, as defined by the - * board, to ensure Vconn is on. - */ - if (pd_timer_is_disabled(port, PE_TIMER_TIMEOUT) && - PE_CHK_FLAG(port, PE_FLAGS_VCONN_SWAP_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_VCONN_SWAP_COMPLETE); - pd_timer_enable(port, PE_TIMER_TIMEOUT, - CONFIG_USBC_VCONN_SWAP_DELAY_US); - } - - if (pd_timer_is_expired(port, PE_TIMER_TIMEOUT)) { - /* - * Note: A cable soft reset shouldn't be necessary as a - * Not_Supported reply means the partner doesn't support - * sourcing Vconn and did not communicate with the cable. - */ - pe_set_ready_state(port); - return; - } -} - -__maybe_unused static void pe_vcs_force_vconn_exit(int port) -{ - pd_timer_disable(port, PE_TIMER_TIMEOUT); -} - -/* - * PE_VCS_CBL_SEND_SOFT_RESET - * Note - Entry is only when directed by the DPM. Protocol errors are handled - * by the PE_SEND_SOFT_RESET state. - */ -static void pe_vcs_cbl_send_soft_reset_entry(int port) -{ - print_current_state(port); - - if (!pe_can_send_sop_prime(port)) { - /* - * If we're not VCONN source, return the appropriate state. - * A VCONN swap re-triggers sending SOP' soft reset - */ - if (pe_is_explicit_contract(port)) { - /* Return to PE_{SRC,SNK}_Ready state */ - pe_set_ready_state(port); - } else { - /* - * Not in Explicit Contract, so we must be a SRC, - * return to PE_Src_Send_Capabilities. - */ - set_state_pe(port, PE_SRC_SEND_CAPABILITIES); - } - return; - } - - send_ctrl_msg(port, TCPCI_MSG_SOP_PRIME, PD_CTRL_SOFT_RESET); - pe_sender_response_msg_entry(port); -} - -static void pe_vcs_cbl_send_soft_reset_run(int port) -{ - bool cable_soft_reset_complete = false; - enum pe_msg_check msg_check; - - msg_check = pe_sender_response_msg_run(port); - - /* Got ACCEPT or REJECT from Cable Plug */ - if ((msg_check & PE_MSG_SENT) && - PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - cable_soft_reset_complete = true; - - /* - * Note: If port partner runs PD 2.0, we must use PD 2.0 to - * communicate with the cable plug when in an explicit contract. - * - * PD Spec Table 6-2: Revision Interoperability during an - * Explicit Contract - */ - if (prl_get_rev(port, TCPCI_MSG_SOP) != PD_REV20) - prl_set_rev(port, TCPCI_MSG_SOP_PRIME, - PD_HEADER_REV(rx_emsg[port].header)); - } - - /* No GoodCRC received, cable is not present */ - if (PE_CHK_FLAG(port, PE_FLAGS_PROTOCOL_ERROR)) { - PE_CLR_FLAG(port, PE_FLAGS_PROTOCOL_ERROR); - /* - * TODO(b/171823328): TCPMv2: Implement cable reset - * Cable reset will only be done here if we know for certain - * a cable is present (we've received the SOP' DiscId response). - */ - cable_soft_reset_complete = true; - } - - if (cable_soft_reset_complete || - pd_timer_is_expired(port, PE_TIMER_SENDER_RESPONSE) || - (msg_check & PE_MSG_DISCARDED)) { - if (pe_is_explicit_contract(port)) { - /* Return to PE_{SRC,SNK}_Ready state */ - pe_set_ready_state(port); - } else { - /* - * Not in Explicit Contract, so we must be a SRC, - * return to PE_Src_Send_Capabilities. - */ - set_state_pe(port, PE_SRC_SEND_CAPABILITIES); - } - } -} - -static void pe_vcs_cbl_send_soft_reset_exit(int port) -{ - pe_sender_response_msg_exit(port); -} - -#endif /* CONFIG_USBC_VCONN */ - -/* - * PE_DR_SNK_Get_Sink_Cap and PE_SRC_Get_Sink_Cap State (shared) - */ -static void pe_dr_get_sink_cap_entry(int port) -{ - print_current_state(port); - - /* Send a Get Sink Cap Message */ - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_GET_SINK_CAP); - pe_sender_response_msg_entry(port); -} - -static void pe_dr_get_sink_cap_run(int port) -{ - int type; - int cnt; - int ext; - enum pe_msg_check msg_check; - enum tcpci_msg_type sop; - - /* - * Check the state of the message sent - */ - msg_check = pe_sender_response_msg_run(port); - - /* - * Transition to PE_[SRC,SNK]_Ready when: - * 1) A Sink_Capabilities Message is received - * 2) Or SenderResponseTimer times out - * 3) Or a Reject Message is received. - * - * Transition to PE_SEND_SOFT_RESET state when: - * 1) An unexpected message is received - */ - if ((msg_check & PE_MSG_SENT) && - PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - - type = PD_HEADER_TYPE(rx_emsg[port].header); - cnt = PD_HEADER_CNT(rx_emsg[port].header); - ext = PD_HEADER_EXT(rx_emsg[port].header); - sop = PD_HEADER_GET_SOP(rx_emsg[port].header); - - if (ext == 0 && sop == TCPCI_MSG_SOP) { - if ((cnt > 0) && (type == PD_DATA_SINK_CAP)) { - uint32_t *payload = - (uint32_t *)rx_emsg[port].buf; - uint8_t cap_cnt = rx_emsg[port].len / - sizeof(uint32_t); - - pe_set_snk_caps(port, cap_cnt, payload); - - dpm_evaluate_sink_fixed_pdo(port, payload[0]); - pe_set_ready_state(port); - return; - } else if (cnt == 0 && (type == PD_CTRL_REJECT || - type == PD_CTRL_NOT_SUPPORTED)) { - pe_set_ready_state(port); - return; - } - /* Unexpected messages fall through to soft reset */ - } - - pe_send_soft_reset(port, sop); - return; - } - - /* - * Transition to PE_[SRC,SNK]_Ready state when: - * 1) SenderResponseTimer times out. - * 2) Message was discarded. - */ - if ((msg_check & PE_MSG_DISCARDED) || - pd_timer_is_expired(port, PE_TIMER_SENDER_RESPONSE)) - pe_set_ready_state(port); -} - -static void pe_dr_get_sink_cap_exit(int port) -{ - pe_sender_response_msg_exit(port); -} - -/* - * PE_DR_SNK_Give_Source_Cap - */ -static void pe_dr_snk_give_source_cap_entry(int port) -{ - print_current_state(port); - - /* Send source capabilities. */ - send_source_cap(port); -} - -static void pe_dr_snk_give_source_cap_run(int port) -{ - /* - * Transition back to PE_SNK_Ready when the Source_Capabilities message - * has been successfully sent. - * - * Get Source Capabilities AMS is uninterruptible, but in case the - * partner violates the spec then send a soft reset rather than get - * stuck here. - */ - if (PE_CHK_FLAG(port, PE_FLAGS_TX_COMPLETE)) { - PE_CLR_FLAG(port, PE_FLAGS_TX_COMPLETE); - set_state_pe(port, PE_SNK_READY); - } else if (PE_CHK_FLAG(port, PE_FLAGS_MSG_DISCARDED)) { - pe_send_soft_reset(port, TCPCI_MSG_SOP); - } -} - -/* - * PE_DR_SRC_Get_Source_Cap - */ -static void pe_dr_src_get_source_cap_entry(int port) -{ - print_current_state(port); - - /* Send a Get_Source_Cap Message */ - tx_emsg[port].len = 0; - send_ctrl_msg(port, TCPCI_MSG_SOP, PD_CTRL_GET_SOURCE_CAP); - pe_sender_response_msg_entry(port); -} - -static void pe_dr_src_get_source_cap_run(int port) -{ - int type; - int cnt; - int ext; - enum pe_msg_check msg_check; - - /* - * Check the state of the message sent - */ - msg_check = pe_sender_response_msg_run(port); - - /* - * Transition to PE_SRC_Ready when: - * 1) A Source Capabilities Message is received. - * 2) A Reject Message is received. - */ - if ((msg_check & PE_MSG_SENT) && - PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { - PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); - - type = PD_HEADER_TYPE(rx_emsg[port].header); - cnt = PD_HEADER_CNT(rx_emsg[port].header); - ext = PD_HEADER_EXT(rx_emsg[port].header); - - if (ext == 0) { - if ((cnt > 0) && (type == PD_DATA_SOURCE_CAP)) { - uint32_t *payload = - (uint32_t *)rx_emsg[port].buf; - - pd_set_src_caps(port, cnt, payload); - - /* - * If we'd prefer to charge from this partner, - * then propose a PR swap. - */ - if (pd_can_charge_from_device(port, cnt, - payload)) - pd_request_power_swap(port); - - /* - * Report dual role power capability to the - * charge manager if present - */ - if (IS_ENABLED(CONFIG_CHARGE_MANAGER) && - pd_get_partner_dual_role_power(port)) - charge_manager_update_dualrole(port, - CAP_DUALROLE); - - set_state_pe(port, PE_SRC_READY); - } else if ((cnt == 0) && (type == PD_CTRL_REJECT || - type == PD_CTRL_NOT_SUPPORTED)) { - pd_set_src_caps(port, -1, NULL); - set_state_pe(port, PE_SRC_READY); - } else { - /* - * On protocol error, consider source cap - * retrieval a failure - */ - pd_set_src_caps(port, -1, NULL); - set_state_pe(port, PE_SEND_SOFT_RESET); - } - return; - } else { - pd_set_src_caps(port, -1, NULL); - set_state_pe(port, PE_SEND_SOFT_RESET); - return; - } - } - - /* - * Transition to PE_SRC_Ready state when: - * 1) the SenderResponseTimer times out. - * 2) Message was discarded. - */ - if ((msg_check & PE_MSG_DISCARDED) || - pd_timer_is_expired(port, PE_TIMER_SENDER_RESPONSE)) - set_state_pe(port, PE_SRC_READY); -} - -static void pe_dr_src_get_source_cap_exit(int port) -{ - pe_sender_response_msg_exit(port); -} - -const uint32_t * const pd_get_src_caps(int port) -{ - return pe[port].src_caps; -} - -void pd_set_src_caps(int port, int cnt, uint32_t *src_caps) -{ - int i; - - pe[port].src_cap_cnt = cnt; - - for (i = 0; i < cnt; i++) - pe[port].src_caps[i] = *src_caps++; -} - -uint8_t pd_get_src_cap_cnt(int port) -{ - if (pe[port].src_cap_cnt > 0) - return pe[port].src_cap_cnt; - - return 0; -} - -/* Track access to the PD discovery structures during HC execution */ -uint32_t task_access[CONFIG_USB_PD_PORT_MAX_COUNT][DISCOVERY_TYPE_COUNT]; - -void pd_dfp_discovery_init(int port) -{ - atomic_or(&task_access[port][TCPCI_MSG_SOP], BIT(task_get_current())); - atomic_or(&task_access[port][TCPCI_MSG_SOP_PRIME], - BIT(task_get_current())); - - memset(pe[port].discovery, 0, sizeof(pe[port].discovery)); - -} - -void pd_dfp_mode_init(int port) -{ - /* - * Clear the VDM Setup Done and Modal Operation flags so we will - * have a fresh discovery - */ - PE_CLR_FLAG(port, PE_FLAGS_VDM_SETUP_DONE | - PE_FLAGS_MODAL_OPERATION); - - memset(pe[port].partner_amodes, 0, sizeof(pe[port].partner_amodes)); - - /* Reset the DPM and DP modules to enable alternate mode entry. */ - dpm_init(port); - dp_init(port); - - if (IS_ENABLED(CONFIG_USB_PD_TBT_COMPAT_MODE)) - tbt_init(port); - - if (IS_ENABLED(CONFIG_USB_PD_USB4)) - enter_usb_init(port); - - if (IS_ENABLED(CONFIG_USB_PD_ALT_MODE_UFP_DP)) - pd_ufp_set_dp_opos(port, 0); -} - -__maybe_unused void pd_discovery_access_clear(int port, - enum tcpci_msg_type type) -{ - if (!IS_ENABLED(CONFIG_USB_PD_ALT_MODE_DFP)) - assert(0); - - atomic_clear_bits(&task_access[port][type], 0xFFFFFFFF); -} - -__maybe_unused bool pd_discovery_access_validate(int port, - enum tcpci_msg_type type) -{ - if (!IS_ENABLED(CONFIG_USB_PD_ALT_MODE_DFP)) - assert(0); - - return !(task_access[port][type] & ~BIT(task_get_current())); -} - -__maybe_unused struct pd_discovery *pd_get_am_discovery_and_notify_access( - int port, enum tcpci_msg_type type) -{ - atomic_or(&task_access[port][type], BIT(task_get_current())); - return (struct pd_discovery *)pd_get_am_discovery(port, type); -} - -__maybe_unused const struct pd_discovery *pd_get_am_discovery(int port, - enum tcpci_msg_type type) -{ - if (!IS_ENABLED(CONFIG_USB_PD_ALT_MODE_DFP)) - assert(0); - ASSERT(type < DISCOVERY_TYPE_COUNT); - - return &pe[port].discovery[type]; -} - -__maybe_unused struct partner_active_modes *pd_get_partner_active_modes( - int port, enum tcpci_msg_type type) -{ - if (!IS_ENABLED(CONFIG_USB_PD_ALT_MODE_DFP)) - assert(0); - ASSERT(type < AMODE_TYPE_COUNT); - return &pe[port].partner_amodes[type]; -} - -__maybe_unused void pd_set_dfp_enter_mode_flag(int port, bool set) -{ - if (!IS_ENABLED(CONFIG_USB_PD_ALT_MODE_DFP)) - assert(0); - - if (set) - PE_SET_FLAG(port, PE_FLAGS_MODAL_OPERATION); - else - PE_CLR_FLAG(port, PE_FLAGS_MODAL_OPERATION); -} - -const char *pe_get_current_state(int port) -{ - if (pe_is_running(port) && IS_ENABLED(USB_PD_DEBUG_LABELS)) - return pe_state_names[get_state_pe(port)]; - else - return ""; -} - -uint32_t pe_get_flags(int port) -{ - return pe[port].flags; -} - -static __const_data const struct usb_state pe_states[] = { - /* Super States */ -#ifdef CONFIG_USB_PD_REV30 - [PE_PRS_FRS_SHARED] = { - .entry = pe_prs_frs_shared_entry, - .exit = pe_prs_frs_shared_exit, - }, -#endif - [PE_VDM_SEND_REQUEST] = { - .entry = pe_vdm_send_request_entry, - .run = pe_vdm_send_request_run, - .exit = pe_vdm_send_request_exit, - }, - - /* Normal States */ - [PE_SRC_STARTUP] = { - .entry = pe_src_startup_entry, - .run = pe_src_startup_run, - .exit = pe_src_startup_exit, - }, - [PE_SRC_DISCOVERY] = { - .entry = pe_src_discovery_entry, - .run = pe_src_discovery_run, - }, - [PE_SRC_SEND_CAPABILITIES] = { - .entry = pe_src_send_capabilities_entry, - .run = pe_src_send_capabilities_run, - .exit = pe_src_send_capabilities_exit, - }, - [PE_SRC_NEGOTIATE_CAPABILITY] = { - .entry = pe_src_negotiate_capability_entry, - }, - [PE_SRC_TRANSITION_SUPPLY] = { - .entry = pe_src_transition_supply_entry, - .run = pe_src_transition_supply_run, - .exit = pe_src_transition_supply_exit, - }, - [PE_SRC_READY] = { - .entry = pe_src_ready_entry, - .run = pe_src_ready_run, - }, - [PE_SRC_DISABLED] = { - .entry = pe_src_disabled_entry, - }, - [PE_SRC_CAPABILITY_RESPONSE] = { - .entry = pe_src_capability_response_entry, - .run = pe_src_capability_response_run, - }, - [PE_SRC_HARD_RESET] = { - .entry = pe_src_hard_reset_entry, - .run = pe_src_hard_reset_run, - .exit = pe_src_hard_reset_exit, - }, - [PE_SRC_HARD_RESET_RECEIVED] = { - .entry = pe_src_hard_reset_received_entry, - .run = pe_src_hard_reset_received_run, - .exit = pe_src_hard_reset_received_exit, - }, - [PE_SRC_TRANSITION_TO_DEFAULT] = { - .entry = pe_src_transition_to_default_entry, - .run = pe_src_transition_to_default_run, - }, - [PE_SNK_STARTUP] = { - .entry = pe_snk_startup_entry, - .run = pe_snk_startup_run, - }, - [PE_SNK_DISCOVERY] = { - .entry = pe_snk_discovery_entry, - .run = pe_snk_discovery_run, - }, - [PE_SNK_WAIT_FOR_CAPABILITIES] = { - .entry = pe_snk_wait_for_capabilities_entry, - .run = pe_snk_wait_for_capabilities_run, - .exit = pe_snk_wait_for_capabilities_exit, - }, - [PE_SNK_EVALUATE_CAPABILITY] = { - .entry = pe_snk_evaluate_capability_entry, - }, - [PE_SNK_SELECT_CAPABILITY] = { - .entry = pe_snk_select_capability_entry, - .run = pe_snk_select_capability_run, - .exit = pe_snk_select_capability_exit, - }, - [PE_SNK_READY] = { - .entry = pe_snk_ready_entry, - .run = pe_snk_ready_run, - }, - [PE_SNK_HARD_RESET] = { - .entry = pe_snk_hard_reset_entry, - .run = pe_snk_hard_reset_run, - }, - [PE_SNK_TRANSITION_TO_DEFAULT] = { - .entry = pe_snk_transition_to_default_entry, - .run = pe_snk_transition_to_default_run, - }, - [PE_SNK_GIVE_SINK_CAP] = { - .entry = pe_snk_give_sink_cap_entry, - .run = pe_snk_give_sink_cap_run, - }, - [PE_SNK_GET_SOURCE_CAP] = { - .entry = pe_snk_get_source_cap_entry, - .run = pe_snk_get_source_cap_run, - }, - [PE_SNK_TRANSITION_SINK] = { - .entry = pe_snk_transition_sink_entry, - .run = pe_snk_transition_sink_run, - .exit = pe_snk_transition_sink_exit, - }, - [PE_SEND_SOFT_RESET] = { - .entry = pe_send_soft_reset_entry, - .run = pe_send_soft_reset_run, - .exit = pe_send_soft_reset_exit, - }, - [PE_SOFT_RESET] = { - .entry = pe_soft_reset_entry, - .run = pe_soft_reset_run, - }, - [PE_SEND_NOT_SUPPORTED] = { - .entry = pe_send_not_supported_entry, - .run = pe_send_not_supported_run, - }, - [PE_SRC_PING] = { - .entry = pe_src_ping_entry, - .run = pe_src_ping_run, - }, - [PE_DRS_EVALUATE_SWAP] = { - .entry = pe_drs_evaluate_swap_entry, - .run = pe_drs_evaluate_swap_run, - }, - [PE_DRS_CHANGE] = { - .entry = pe_drs_change_entry, - .run = pe_drs_change_run, - }, - [PE_DRS_SEND_SWAP] = { - .entry = pe_drs_send_swap_entry, - .run = pe_drs_send_swap_run, - .exit = pe_drs_send_swap_exit, - }, - [PE_PRS_SRC_SNK_EVALUATE_SWAP] = { - .entry = pe_prs_src_snk_evaluate_swap_entry, - .run = pe_prs_src_snk_evaluate_swap_run, - }, - [PE_PRS_SRC_SNK_TRANSITION_TO_OFF] = { - .entry = pe_prs_src_snk_transition_to_off_entry, - .run = pe_prs_src_snk_transition_to_off_run, - .exit = pe_prs_src_snk_transition_to_off_exit, - }, - [PE_PRS_SRC_SNK_ASSERT_RD] = { - .entry = pe_prs_src_snk_assert_rd_entry, - .run = pe_prs_src_snk_assert_rd_run, - }, - [PE_PRS_SRC_SNK_WAIT_SOURCE_ON] = { - .entry = pe_prs_src_snk_wait_source_on_entry, - .run = pe_prs_src_snk_wait_source_on_run, - .exit = pe_prs_src_snk_wait_source_on_exit, - }, - [PE_PRS_SRC_SNK_SEND_SWAP] = { - .entry = pe_prs_src_snk_send_swap_entry, - .run = pe_prs_src_snk_send_swap_run, - .exit = pe_prs_src_snk_send_swap_exit, - }, - [PE_PRS_SNK_SRC_EVALUATE_SWAP] = { - .entry = pe_prs_snk_src_evaluate_swap_entry, - .run = pe_prs_snk_src_evaluate_swap_run, - }, - /* - * Some of the Power Role Swap actions are shared with the very - * similar actions of Fast Role Swap. - */ - /* State actions are shared with PE_FRS_SNK_SRC_TRANSITION_TO_OFF */ - [PE_PRS_SNK_SRC_TRANSITION_TO_OFF] = { - .entry = pe_prs_snk_src_transition_to_off_entry, - .run = pe_prs_snk_src_transition_to_off_run, - .exit = pe_prs_snk_src_transition_to_off_exit, -#ifdef CONFIG_USB_PD_REV30 - .parent = &pe_states[PE_PRS_FRS_SHARED], -#endif /* CONFIG_USB_PD_REV30 */ - }, - /* State actions are shared with PE_FRS_SNK_SRC_ASSERT_RP */ - [PE_PRS_SNK_SRC_ASSERT_RP] = { - .entry = pe_prs_snk_src_assert_rp_entry, - .run = pe_prs_snk_src_assert_rp_run, -#ifdef CONFIG_USB_PD_REV30 - .parent = &pe_states[PE_PRS_FRS_SHARED], -#endif /* CONFIG_USB_PD_REV30 */ - }, - /* State actions are shared with PE_FRS_SNK_SRC_SOURCE_ON */ - [PE_PRS_SNK_SRC_SOURCE_ON] = { - .entry = pe_prs_snk_src_source_on_entry, - .run = pe_prs_snk_src_source_on_run, - .exit = pe_prs_snk_src_source_on_exit, -#ifdef CONFIG_USB_PD_REV30 - .parent = &pe_states[PE_PRS_FRS_SHARED], -#endif /* CONFIG_USB_PD_REV30 */ - }, - /* State actions are shared with PE_FRS_SNK_SRC_SEND_SWAP */ - [PE_PRS_SNK_SRC_SEND_SWAP] = { - .entry = pe_prs_snk_src_send_swap_entry, - .run = pe_prs_snk_src_send_swap_run, - .exit = pe_prs_snk_src_send_swap_exit, -#ifdef CONFIG_USB_PD_REV30 - .parent = &pe_states[PE_PRS_FRS_SHARED], -#endif /* CONFIG_USB_PD_REV30 */ - }, -#ifdef CONFIG_USBC_VCONN - [PE_VCS_EVALUATE_SWAP] = { - .entry = pe_vcs_evaluate_swap_entry, - .run = pe_vcs_evaluate_swap_run, - }, - [PE_VCS_SEND_SWAP] = { - .entry = pe_vcs_send_swap_entry, - .run = pe_vcs_send_swap_run, - .exit = pe_vcs_send_swap_exit, - }, - [PE_VCS_WAIT_FOR_VCONN_SWAP] = { - .entry = pe_vcs_wait_for_vconn_swap_entry, - .run = pe_vcs_wait_for_vconn_swap_run, - .exit = pe_vcs_wait_for_vconn_swap_exit, - }, - [PE_VCS_TURN_ON_VCONN_SWAP] = { - .entry = pe_vcs_turn_on_vconn_swap_entry, - .run = pe_vcs_turn_on_vconn_swap_run, - .exit = pe_vcs_turn_on_vconn_swap_exit, - }, - [PE_VCS_TURN_OFF_VCONN_SWAP] = { - .entry = pe_vcs_turn_off_vconn_swap_entry, - .run = pe_vcs_turn_off_vconn_swap_run, - }, - [PE_VCS_SEND_PS_RDY_SWAP] = { - .entry = pe_vcs_send_ps_rdy_swap_entry, - .run = pe_vcs_send_ps_rdy_swap_run, - }, - [PE_VCS_CBL_SEND_SOFT_RESET] = { - .entry = pe_vcs_cbl_send_soft_reset_entry, - .run = pe_vcs_cbl_send_soft_reset_run, - .exit = pe_vcs_cbl_send_soft_reset_exit, - }, -#endif /* CONFIG_USBC_VCONN */ - [PE_VDM_IDENTITY_REQUEST_CBL] = { - .entry = pe_vdm_identity_request_cbl_entry, - .run = pe_vdm_identity_request_cbl_run, - .exit = pe_vdm_identity_request_cbl_exit, - .parent = &pe_states[PE_VDM_SEND_REQUEST], - }, - [PE_INIT_PORT_VDM_IDENTITY_REQUEST] = { - .entry = pe_init_port_vdm_identity_request_entry, - .run = pe_init_port_vdm_identity_request_run, - .exit = pe_init_port_vdm_identity_request_exit, - .parent = &pe_states[PE_VDM_SEND_REQUEST], - }, - [PE_INIT_VDM_SVIDS_REQUEST] = { - .entry = pe_init_vdm_svids_request_entry, - .run = pe_init_vdm_svids_request_run, - .exit = pe_init_vdm_svids_request_exit, - .parent = &pe_states[PE_VDM_SEND_REQUEST], - }, - [PE_INIT_VDM_MODES_REQUEST] = { - .entry = pe_init_vdm_modes_request_entry, - .run = pe_init_vdm_modes_request_run, - .exit = pe_init_vdm_modes_request_exit, - .parent = &pe_states[PE_VDM_SEND_REQUEST], - }, - [PE_VDM_REQUEST_DPM] = { - .entry = pe_vdm_request_dpm_entry, - .run = pe_vdm_request_dpm_run, - .exit = pe_vdm_request_dpm_exit, - .parent = &pe_states[PE_VDM_SEND_REQUEST], - }, - [PE_VDM_RESPONSE] = { - .entry = pe_vdm_response_entry, - .run = pe_vdm_response_run, - .exit = pe_vdm_response_exit, - }, - [PE_HANDLE_CUSTOM_VDM_REQUEST] = { - .entry = pe_handle_custom_vdm_request_entry, - .run = pe_handle_custom_vdm_request_run, - .exit = pe_handle_custom_vdm_request_exit, - }, - [PE_DEU_SEND_ENTER_USB] = { - .entry = pe_enter_usb_entry, - .run = pe_enter_usb_run, - .exit = pe_enter_usb_exit, - }, - [PE_WAIT_FOR_ERROR_RECOVERY] = { - .entry = pe_wait_for_error_recovery_entry, - .run = pe_wait_for_error_recovery_run, - }, - [PE_BIST_TX] = { - .entry = pe_bist_tx_entry, - .run = pe_bist_tx_run, - .exit = pe_bist_tx_exit, - }, - [PE_DR_GET_SINK_CAP] = { - .entry = pe_dr_get_sink_cap_entry, - .run = pe_dr_get_sink_cap_run, - .exit = pe_dr_get_sink_cap_exit, - }, - [PE_DR_SNK_GIVE_SOURCE_CAP] = { - .entry = pe_dr_snk_give_source_cap_entry, - .run = pe_dr_snk_give_source_cap_run, - }, - [PE_DR_SRC_GET_SOURCE_CAP] = { - .entry = pe_dr_src_get_source_cap_entry, - .run = pe_dr_src_get_source_cap_run, - .exit = pe_dr_src_get_source_cap_exit, - }, -#ifdef CONFIG_USB_PD_REV30 - [PE_FRS_SNK_SRC_START_AMS] = { - .entry = pe_frs_snk_src_start_ams_entry, - .parent = &pe_states[PE_PRS_FRS_SHARED], - }, -#ifdef CONFIG_USB_PD_EXTENDED_MESSAGES - [PE_GIVE_BATTERY_CAP] = { - .entry = pe_give_battery_cap_entry, - .run = pe_give_battery_cap_run, - }, - [PE_GIVE_BATTERY_STATUS] = { - .entry = pe_give_battery_status_entry, - .run = pe_give_battery_status_run, - }, - [PE_SEND_ALERT] = { - .entry = pe_send_alert_entry, - .run = pe_send_alert_run, - }, -#else - [PE_SRC_CHUNK_RECEIVED] = { - .entry = pe_chunk_received_entry, - .run = pe_chunk_received_run, - .exit = pe_chunk_received_exit, - }, - [PE_SNK_CHUNK_RECEIVED] = { - .entry = pe_chunk_received_entry, - .run = pe_chunk_received_run, - .exit = pe_chunk_received_exit, - }, -#endif /* CONFIG_USB_PD_EXTENDED_MESSAGES */ -#ifdef CONFIG_USBC_VCONN - [PE_VCS_FORCE_VCONN] = { - .entry = pe_vcs_force_vconn_entry, - .run = pe_vcs_force_vconn_run, - .exit = pe_vcs_force_vconn_exit, - }, -#endif /* CONFIG_USBC_VCONN */ -#endif /* CONFIG_USB_PD_REV30 */ -}; - -#ifdef TEST_BUILD -/* TODO(b/173791979): Unit tests shouldn't need to access internal states */ -const struct test_sm_data test_pe_sm_data[] = { - { - .base = pe_states, - .size = ARRAY_SIZE(pe_states), - .names = pe_state_names, - .names_size = ARRAY_SIZE(pe_state_names), - }, -}; -BUILD_ASSERT(ARRAY_SIZE(pe_states) == ARRAY_SIZE(pe_state_names)); -const int test_pe_sm_data_size = ARRAY_SIZE(test_pe_sm_data); - -void pe_set_flag(int port, int flag) -{ - PE_SET_FLAG(port, flag); -} -void pe_clr_flag(int port, int flag) -{ - PE_CLR_FLAG(port, flag); -} -int pe_chk_flag(int port, int flag) -{ - return PE_CHK_FLAG(port, flag); -} -int pe_get_all_flags(int port) -{ - return pe[port].flags; -} -void pe_set_all_flags(int port, int flags) -{ - pe[port].flags = flags; -} -void pe_clr_dpm_requests(int port) -{ - pe[port].dpm_request = 0; -} -#endif |