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