diff options
Diffstat (limited to 'common/usb_pd_protocol.c')
-rw-r--r-- | common/usb_pd_protocol.c | 5449 |
1 files changed, 0 insertions, 5449 deletions
diff --git a/common/usb_pd_protocol.c b/common/usb_pd_protocol.c deleted file mode 100644 index abf75e8004..0000000000 --- a/common/usb_pd_protocol.c +++ /dev/null @@ -1,5449 +0,0 @@ -/* Copyright 2014 The Chromium OS Authors. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#include "atomic.h" -#include "battery.h" -#include "battery_smart.h" -#include "board.h" -#include "charge_manager.h" -#include "charge_state.h" -#include "chipset.h" -#include "common.h" -#include "console.h" -#include "cros_version.h" -#include "ec_commands.h" -#include "gpio.h" -#include "hooks.h" -#include "host_command.h" -#include "printf.h" -#include "registers.h" -#include "system.h" -#include "task.h" -#include "tcpm/tcpci.h" -#include "tcpm/tcpm.h" -#include "timer.h" -#include "util.h" -#include "usb_charge.h" -#include "usb_common.h" -#include "usb_mux.h" -#include "usb_pd.h" -#include "usb_pd_flags.h" -#include "usb_pd_tcpm.h" -#include "usb_pd_tcpc.h" -#include "usbc_ocp.h" -#include "usbc_ppc.h" -#include "vboot.h" - -/* Flags to clear on a disconnect */ -#define PD_FLAGS_RESET_ON_DISCONNECT_MASK (PD_FLAGS_PARTNER_DR_POWER | \ - PD_FLAGS_PARTNER_DR_DATA | \ - PD_FLAGS_CHECK_IDENTITY | \ - PD_FLAGS_SNK_CAP_RECVD | \ - PD_FLAGS_TCPC_DRP_TOGGLE | \ - PD_FLAGS_EXPLICIT_CONTRACT | \ - PD_FLAGS_PREVIOUS_PD_CONN | \ - PD_FLAGS_CHECK_PR_ROLE | \ - PD_FLAGS_CHECK_DR_ROLE | \ - PD_FLAGS_PARTNER_UNCONSTR | \ - PD_FLAGS_VCONN_ON | \ - PD_FLAGS_TRY_SRC | \ - PD_FLAGS_PARTNER_USB_COMM | \ - PD_FLAGS_UPDATE_SRC_CAPS | \ - PD_FLAGS_TS_DTS_PARTNER | \ - PD_FLAGS_SNK_WAITING_BATT | \ - PD_FLAGS_CHECK_VCONN_STATE) - -#ifdef CONFIG_COMMON_RUNTIME -#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) -#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) - -static int tcpc_prints(const char *string, int port) -{ - return CPRINTS("TCPC p%d %s", port, string); -} - -BUILD_ASSERT(CONFIG_USB_PD_PORT_MAX_COUNT <= EC_USB_PD_MAX_PORTS); - -/* - * Debug log level - higher number == more log - * Level 0: Log state transitions - * Level 1: Level 0, plus state name - * Level 2: Level 1, plus packet info - * Level 3: Level 2, plus ping packet and packet dump on error - * - * Note that higher log level causes timing changes and thus may affect - * performance. - * - * Can be limited to constant debug_level by CONFIG_USB_PD_DEBUG_LEVEL - */ -#ifdef CONFIG_USB_PD_DEBUG_LEVEL -static const int debug_level = CONFIG_USB_PD_DEBUG_LEVEL; -#else -static int debug_level; -#endif - -/* - * PD communication enabled flag. When false, PD state machine still - * detects source/sink connection and disconnection, and will still - * provide VBUS, but never sends any PD communication. - */ -static uint8_t pd_comm_enabled[CONFIG_USB_PD_PORT_MAX_COUNT]; -#else /* CONFIG_COMMON_RUNTIME */ -#define CPRINTF(format, args...) -#define CPRINTS(format, args...) -#define tcpc_prints(string, port) -static const int debug_level; -#endif - -#ifdef CONFIG_USB_PD_DUAL_ROLE -#define DUAL_ROLE_IF_ELSE(port, sink_clause, src_clause) \ - (pd[port].power_role == PD_ROLE_SINK ? (sink_clause) : (src_clause)) -#else -#define DUAL_ROLE_IF_ELSE(port, sink_clause, src_clause) (src_clause) -#endif - -#define READY_RETURN_STATE(port) DUAL_ROLE_IF_ELSE(port, PD_STATE_SNK_READY, \ - PD_STATE_SRC_READY) - -/* Type C supply voltage (mV) */ -#define TYPE_C_VOLTAGE 5000 /* mV */ - -/* PD counter definitions */ -#define PD_MESSAGE_ID_COUNT 7 -#define PD_HARD_RESET_COUNT 2 -#define PD_CAPS_COUNT 50 -#define PD_SNK_CAP_RETRIES 3 - -/* - * The time that we allow the port partner to send any messages after an - * explicit contract is established. 200ms was chosen somewhat arbitrarily as - * it should be long enough for sources to decide to send a message if they were - * going to, but not so long that a "low power charger connected" notification - * would be shown in the chrome OS UI. - */ -#define SNK_READY_HOLD_OFF_US (200 * MSEC) -/* - * For the same purpose as SNK_READY_HOLD_OFF_US, but this delay can be longer - * since the concern over "low power charger" is not relevant when connected as - * a source and the additional delay avoids a race condition where the partner - * port sends a power role swap request close to when the VDM discover identity - * message gets sent. - */ -#define SRC_READY_HOLD_OFF_US (400 * MSEC) - -enum ams_seq { - AMS_START, - AMS_RESPONSE, -}; - -enum vdm_states { - VDM_STATE_ERR_BUSY = -3, - VDM_STATE_ERR_SEND = -2, - VDM_STATE_ERR_TMOUT = -1, - VDM_STATE_DONE = 0, - /* Anything >0 represents an active state */ - VDM_STATE_READY = 1, - VDM_STATE_BUSY = 2, - VDM_STATE_WAIT_RSP_BUSY = 3, -}; - -#ifdef CONFIG_USB_PD_DUAL_ROLE -/* Port dual-role state */ -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}; - -/* Enable variable for Try.SRC states */ -static bool pd_try_src_enable; -#endif - -#ifdef CONFIG_USB_PD_REV30 -/* - * The spec. revision is the argument for this macro. - * Rev 0 (PD 1.0) - return PD_CTRL_REJECT - * Rev 1 (PD 2.0) - return PD_CTRL_REJECT - * Rev 2 (PD 3.0) - return PD_CTRL_NOT_SUPPORTED - * - * Note: this should only be used in locations where responding on a lower - * revision with a Reject is valid (ex. a source refusing a PR_Swap). For - * other uses of Not_Supported, use PD_CTRL_NOT_SUPPORTED directly. - */ -#define NOT_SUPPORTED(r) (r < 2 ? PD_CTRL_REJECT : PD_CTRL_NOT_SUPPORTED) -#else -#define NOT_SUPPORTED(r) PD_CTRL_REJECT -#endif - -#ifdef CONFIG_USB_PD_REV30 -/* - * The spec. revision is used to index into this array. - * Rev 0 (VDO 1.0) - return VDM_VER10 - * Rev 1 (VDO 1.0) - return VDM_VER10 - * Rev 2 (VDO 2.0) - return VDM_VER20 - */ -static const uint8_t vdo_ver[] = { - VDM_VER10, VDM_VER10, VDM_VER20}; -#define VDO_VER(v) vdo_ver[v] -#else -#define VDO_VER(v) VDM_VER10 -#endif - -static struct pd_protocol { - /* 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; - /* 3-bit rolling message ID counter */ - uint8_t msg_id; - /* port polarity */ - enum tcpc_cc_polarity polarity; - /* PD state for port */ - enum pd_states task_state; - /* PD state when we run state handler the last time */ - enum pd_states last_state; - /* bool: request state change to SUSPENDED */ - uint8_t req_suspend_state; - /* The state to go to after timeout */ - enum pd_states timeout_state; - /* port flags, see PD_FLAGS_* */ - uint32_t flags; - /* Timeout for the current state. Set to 0 for no timeout. */ - uint64_t timeout; - /* Time for source recovery after hard reset */ - uint64_t src_recover; - /* Time for CC debounce end */ - uint64_t cc_debounce; - /* The cc state */ - enum pd_cc_states cc_state; - /* status of last transmit */ - uint8_t tx_status; - - /* Last received */ - uint8_t last_msg_id; - - /* last requested voltage PDO index */ - int requested_idx; -#ifdef CONFIG_USB_PD_DUAL_ROLE - /* Current limit / voltage based on the last request message */ - uint32_t curr_limit; - uint32_t supply_voltage; - /* Signal charging update that affects the port */ - int new_power_request; - /* Store previously requested voltage request */ - int prev_request_mv; - /* Time for Try.SRC states */ - uint64_t try_src_marker; - uint64_t try_timeout; -#endif - -#ifdef CONFIG_USB_PD_TCPC_LOW_POWER - /* Time to enter low power mode */ - uint64_t low_power_time; - /* Time to debounce exit low power mode */ - uint64_t low_power_exit_time; - /* Tasks to notify after TCPC has been reset */ - int tasks_waiting_on_reset; - /* Tasks preventing TCPC from entering low power mode */ - int tasks_preventing_lpm; -#endif - -#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE - /* - * 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; -#endif - - /* - * Time to ignore Vbus absence due to external IC debounce detection - * logic immediately after a power role swap. - */ - uint64_t vbus_debounce_time; - - /* PD state for Vendor Defined Messages */ - enum vdm_states vdm_state; - /* Timeout for the current vdm state. Set to 0 for no timeout. */ - timestamp_t vdm_timeout; - /* next Vendor Defined Message to send */ - uint32_t vdo_data[VDO_MAX_SIZE]; - /* type of transmit message (SOP/SOP'/SOP'') */ - enum tcpci_msg_type xmit_type; - uint8_t vdo_count; - /* VDO to retry if UFP responder replied busy. */ - uint32_t vdo_retry; - - /* Attached ChromeOS device id, RW hash, and current RO / RW image */ - uint16_t dev_id; - uint32_t dev_rw_hash[PD_RW_HASH_SIZE/4]; - enum ec_image current_image; -#ifdef CONFIG_USB_PD_REV30 - /* protocol revision */ - uint8_t rev; -#endif - /* - * Some port partners are really chatty after an explicit contract is - * established. Therefore, we allow this time for the port partner to - * send any messages in order to avoid a collision of sending messages - * of our own. - */ - uint64_t ready_state_holdoff_timer; - /* - * PD 2.0 spec, section 6.5.11.1 - * When we can give up on a HARD_RESET transmission. - */ - uint64_t hard_reset_complete_timer; -} pd[CONFIG_USB_PD_PORT_MAX_COUNT]; - -#ifdef CONFIG_USB_PD_TCPMV1_DEBUG -static const char * const pd_state_names[] = { - "DISABLED", "SUSPENDED", - "SNK_DISCONNECTED", "SNK_DISCONNECTED_DEBOUNCE", - "SNK_HARD_RESET_RECOVER", - "SNK_DISCOVERY", "SNK_REQUESTED", "SNK_TRANSITION", "SNK_READY", - "SNK_SWAP_INIT", "SNK_SWAP_SNK_DISABLE", - "SNK_SWAP_SRC_DISABLE", "SNK_SWAP_STANDBY", "SNK_SWAP_COMPLETE", - "SRC_DISCONNECTED", "SRC_DISCONNECTED_DEBOUNCE", - "SRC_HARD_RESET_RECOVER", "SRC_STARTUP", - "SRC_DISCOVERY", "SRC_NEGOCIATE", "SRC_ACCEPTED", "SRC_POWERED", - "SRC_TRANSITION", "SRC_READY", "SRC_GET_SNK_CAP", "DR_SWAP", - "SRC_SWAP_INIT", "SRC_SWAP_SNK_DISABLE", "SRC_SWAP_SRC_DISABLE", - "SRC_SWAP_STANDBY", - "VCONN_SWAP_SEND", "VCONN_SWAP_INIT", "VCONN_SWAP_READY", - "SOFT_RESET", "HARD_RESET_SEND", "HARD_RESET_EXECUTE", "BIST_RX", - "BIST_TX", - "DRP_AUTO_TOGGLE", - "ENTER_USB", -}; -BUILD_ASSERT(ARRAY_SIZE(pd_state_names) == PD_STATE_COUNT); -#endif - -int pd_comm_is_enabled(int port) -{ -#ifdef CONFIG_COMMON_RUNTIME - return pd_comm_enabled[port]; -#else - return 1; -#endif -} - -bool pd_alt_mode_capable(int port) -{ - /* - * PD is alternate mode capable only if PD communication is enabled and - * the port is not suspended. - */ - return pd_comm_is_enabled(port) && - !(pd[port].task_state == PD_STATE_SUSPENDED); -} - -static inline void set_state_timeout(int port, - uint64_t timeout, - enum pd_states timeout_state) -{ - pd[port].timeout = timeout; - pd[port].timeout_state = timeout_state; -} - -int pd_get_rev(int port, enum tcpci_msg_type type) -{ -#ifdef CONFIG_USB_PD_REV30 - /* TCPMv1 Only stores PD revision for SOP and SOP' types */ - ASSERT(type < NUM_SOP_STAR_TYPES - 1); - - if (type == TCPCI_MSG_SOP_PRIME) - return get_usb_pd_cable_revision(port); - - return pd[port].rev; -#else - return PD_REV20; -#endif -} - -int pd_get_vdo_ver(int port, enum tcpci_msg_type type) -{ -#ifdef CONFIG_USB_PD_REV30 - if (type == TCPCI_MSG_SOP_PRIME) - return vdo_ver[get_usb_pd_cable_revision(port)]; - - return vdo_ver[pd[port].rev]; -#else - return VDM_VER10; -#endif -} - -/* Return flag for pd state is connected */ -int pd_is_connected(int port) -{ - if (pd[port].task_state == PD_STATE_DISABLED) - return 0; - -#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE - if (pd[port].task_state == PD_STATE_DRP_AUTO_TOGGLE) - return 0; -#endif - - return DUAL_ROLE_IF_ELSE(port, - /* sink */ - pd[port].task_state != PD_STATE_SNK_DISCONNECTED && - pd[port].task_state != PD_STATE_SNK_DISCONNECTED_DEBOUNCE, - /* source */ - pd[port].task_state != PD_STATE_SRC_DISCONNECTED && - pd[port].task_state != PD_STATE_SRC_DISCONNECTED_DEBOUNCE); -} - -/* Return true if partner port is known to be PD capable. */ -bool pd_capable(int port) -{ - return !!(pd[port].flags & PD_FLAGS_PREVIOUS_PD_CONN); -} - -/* - * For TCPMv1, this routine always returns false so that the USB3 signals - * are connected without delay when the initial connection is UFP. - */ -bool pd_waiting_on_partner_src_caps(int port) -{ - return false; -} - -/* - * Return true if partner port is capable of communication over USB data - * lines. - */ -bool pd_get_partner_usb_comm_capable(int port) -{ - return !!(pd[port].flags & PD_FLAGS_PARTNER_USB_COMM); -} - -#ifdef CONFIG_USB_PD_DUAL_ROLE -void pd_vbus_low(int port) -{ - pd[port].flags &= ~PD_FLAGS_VBUS_NEVER_LOW; -} -#endif - - -#ifdef CONFIG_USBC_VCONN -static void set_vconn(int port, int enable) -{ - /* - * Disable PPC Vconn first then TCPC in case the voltage feeds back - * to TCPC and damages. - */ - if (IS_ENABLED(CONFIG_USBC_PPC_VCONN) && !enable) - ppc_set_vconn(port, 0); - - /* - * Some TCPCs/PPC combinations can trigger OVP if the TCPC doesn't - * source VCONN. This happens if the TCPC will trip OVP with 5V, and the - * PPC doesn't isolate the TCPC from VCONN when sourcing. But, some PPCs - * which do isolate the TCPC can't handle 5V on its host-side CC pins, - * so the TCPC shouldn't source VCONN in those cases. - * - * In the first case, both TCPC and PPC will potentially source Vconn, - * but that should be okay since Vconn has "make before break" - * electrical requirements when swapping anyway. - * - * See b/72961003 and b/180973460 - */ - tcpm_set_vconn(port, enable); - - if (IS_ENABLED(CONFIG_USBC_PPC_VCONN) && enable) - ppc_set_vconn(port, 1); -} -#endif /* defined(CONFIG_USBC_VCONN) */ - -#ifdef CONFIG_USB_PD_REV30 -/* Note: rp should be set to either SINK_TX_OK or SINK_TX_NG */ -static void sink_can_xmit(int port, int rp) -{ - tcpm_select_rp_value(port, rp); - tcpm_set_cc(port, TYPEC_CC_RP); - - /* We must wait tSinkTx before sending a message */ - if (rp == SINK_TX_NG) - usleep(PD_T_SINK_TX); -} -#endif - -#ifdef CONFIG_USB_PD_TCPC_LOW_POWER - -/* 10 ms is enough time for any TCPC transaction to complete. */ -#define PD_LPM_DEBOUNCE_US (10 * MSEC) -/* 25 ms on LPM exit to ensure TCPC is settled */ -#define PD_LPM_EXIT_DEBOUNCE_US (25 * MSEC) - -/* This is only called from the PD tasks that owns the port. */ -static void handle_device_access(int port) -{ - /* This should only be called from the PD task */ - assert(port == TASK_ID_TO_PD_PORT(task_get_current())); - - pd[port].low_power_time = get_time().val + PD_LPM_DEBOUNCE_US; - if (pd[port].flags & PD_FLAGS_LPM_ENGAGED) { - tcpc_prints("Exit Low Power Mode", port); - pd[port].flags &= ~(PD_FLAGS_LPM_ENGAGED | - PD_FLAGS_LPM_REQUESTED); - pd[port].flags |= PD_FLAGS_LPM_EXIT; - - pd[port].low_power_exit_time = get_time().val - + PD_LPM_EXIT_DEBOUNCE_US; - /* - * Wake to ensure we make another pass through the main task - * loop after clearing the flags. - */ - task_wake(PD_PORT_TO_TASK_ID(port)); - } -} - -static int pd_device_in_low_power(int port) -{ - /* - * If we are actively waking the device up in the PD task, do not - * let TCPC operation wait or retry because we are in low power mode. - */ - if (port == TASK_ID_TO_PD_PORT(task_get_current()) && - (pd[port].flags & PD_FLAGS_LPM_TRANSITION)) - return 0; - - return pd[port].flags & PD_FLAGS_LPM_ENGAGED; -} - -static 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())); - - pd[port].flags |= PD_FLAGS_LPM_TRANSITION; - rv = tcpm_init(port); - pd[port].flags &= ~PD_FLAGS_LPM_TRANSITION; - - if (rv == EC_SUCCESS) - tcpc_prints("init ready", port); - else - tcpc_prints("init failed!", port); - - /* - * 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(&pd[port].tasks_waiting_on_reset); - - /* - * Now that we are done waking up the device, handle device access - * manually because we ignored it while waking up device. - */ - handle_device_access(port); - - /* Clear SW LPM state; the state machine will set it again if needed */ - pd[port].flags &= ~PD_FLAGS_LPM_REQUESTED; - - /* 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; -} - -static void pd_wait_for_wakeup(int port) -{ - if (port == TASK_ID_TO_PD_PORT(task_get_current())) { - /* If we are in the PD task, we can directly reset */ - reset_device_and_notify(port); - } else { - /* Otherwise, we need to wait for the TCPC reset to complete */ - atomic_or(&pd[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); - } -} - -void pd_wait_exit_low_power(int port) -{ - if (pd_device_in_low_power(port)) - pd_wait_for_wakeup(port); -} - -/* - * 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())) { - /* Ignore any access to device while it is waking up */ - if (pd[port].flags & PD_FLAGS_LPM_TRANSITION) - return; - - handle_device_access(port); - } else { - task_set_event(PD_PORT_TO_TASK_ID(port), - PD_EVENT_DEVICE_ACCESSED); - } -} - -void pd_prevent_low_power_mode(int port, int prevent) -{ - const int current_task_mask = (1 << task_get_current()); - - if (prevent) - atomic_or(&pd[port].tasks_preventing_lpm, current_task_mask); - else - atomic_clear_bits(&pd[port].tasks_preventing_lpm, - current_task_mask); -} - -/* This is only called from the PD tasks that owns the port. */ -static void exit_low_power_mode(int port) -{ - if (pd[port].flags & PD_FLAGS_LPM_ENGAGED) - reset_device_and_notify(port); - else - pd[port].flags &= ~PD_FLAGS_LPM_REQUESTED; -} - -#else /* !CONFIG_USB_PD_TCPC_LOW_POWER */ - -/* We don't need to notify anyone if low power mode isn't involved. */ -static int reset_device_and_notify(int port) -{ - const int rv = tcpm_init(port); - - if (rv == EC_SUCCESS) - tcpc_prints("init ready", port); - else - tcpc_prints("init failed!", port); - - return rv; -} - -#endif /* CONFIG_USB_PD_TCPC_LOW_POWER */ - -/** - * Invalidate last message received at the port when the port gets disconnected - * or reset(soft/hard). This is used to identify and handle the duplicate - * messages. - * - * @param port USB PD TCPC port number - */ -static void invalidate_last_message_id(int port) -{ - pd[port].last_msg_id = INVALID_MSG_ID_COUNTER; -} - -static bool consume_sop_repeat_message(int port, uint8_t msg_id) -{ - if (pd[port].last_msg_id != msg_id) { - pd[port].last_msg_id = msg_id; - return false; - } - CPRINTF("C%d Repeat msg_id %d\n", port, msg_id); - return true; -} - -/** - * Identify and drop any duplicate messages received at the port. - * - * @param port USB PD TCPC port number - * @param msg_header Message Header containing the RX message ID - * @return True if the received message is a duplicate one, False otherwise. - * - * From USB PD version 1.3 section 6.7.1, the port which communicates - * using SOP* Packets Shall maintain copies of the last MessageID for - * each type of SOP* it uses. - */ -static bool consume_repeat_message(int port, uint32_t msg_header) -{ - uint8_t msg_id = PD_HEADER_ID(msg_header); - enum tcpci_msg_type sop = PD_HEADER_GET_SOP(msg_header); - - /* If repeat message ignore, except softreset control request. */ - if (PD_HEADER_TYPE(msg_header) == PD_CTRL_SOFT_RESET && - PD_HEADER_CNT(msg_header) == 0) { - return false; - } else if (sop == TCPCI_MSG_SOP_PRIME) { - return consume_sop_prime_repeat_msg(port, msg_id); - } else if (sop == TCPCI_MSG_SOP_PRIME_PRIME) { - return consume_sop_prime_prime_repeat_msg(port, msg_id); - } else { - return consume_sop_repeat_message(port, msg_id); - } - -} - -/** - * Returns true if the port is currently in the try src state. - */ -static inline int is_try_src(int port) -{ - return pd[port].flags & PD_FLAGS_TRY_SRC; -} - -static inline void set_state(int port, enum pd_states next_state) -{ - enum pd_states last_state = pd[port].task_state; -#if defined(CONFIG_LOW_POWER_IDLE) && !defined(CONFIG_USB_PD_TCPC_ON_CHIP) - int i; -#endif - int not_auto_toggling = 1; - - set_state_timeout(port, 0, 0); - pd[port].task_state = next_state; - - if (last_state == next_state) - return; - -#if defined(CONFIG_USBC_PPC) && defined(CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE) - /* If we're entering DRP_AUTO_TOGGLE, there is no sink connected. */ - if (next_state == PD_STATE_DRP_AUTO_TOGGLE) { - ppc_dev_is_connected(port, PPC_DEV_DISCONNECTED); - /* Disable Auto Discharge Disconnect */ - tcpm_enable_auto_discharge_disconnect(port, 0); - - if (IS_ENABLED(CONFIG_USBC_OCP)) { - usbc_ocp_snk_is_connected(port, false); - /* - * Clear the overcurrent event counter - * since we've detected a disconnect. - */ - usbc_ocp_clear_event_counter(port); - } - } -#endif /* CONFIG_USBC_PPC && CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE */ - -#ifdef CONFIG_USB_PD_DUAL_ROLE -#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE - if (last_state != PD_STATE_DRP_AUTO_TOGGLE) - /* Clear flag to allow DRP auto toggle when possible */ - pd[port].flags &= ~PD_FLAGS_TCPC_DRP_TOGGLE; - else - /* This is an auto toggle instead of disconnect */ - not_auto_toggling = 0; -#endif - - /* Ignore dual-role toggling between sink and source */ - if ((last_state == PD_STATE_SNK_DISCONNECTED && - next_state == PD_STATE_SRC_DISCONNECTED) || - (last_state == PD_STATE_SRC_DISCONNECTED && - next_state == PD_STATE_SNK_DISCONNECTED)) - return; - - if (next_state == PD_STATE_SRC_DISCONNECTED || - next_state == PD_STATE_SNK_DISCONNECTED) { -#ifdef CONFIG_USBC_PPC - enum tcpc_cc_voltage_status cc1, cc2; - - tcpm_get_cc(port, &cc1, &cc2); - /* - * Neither a debug accessory nor UFP attached. - * Tell the PPC module that there is no device connected. - */ - if (!cc_is_at_least_one_rd(cc1, cc2)) { - ppc_dev_is_connected(port, PPC_DEV_DISCONNECTED); - - if (IS_ENABLED(CONFIG_USBC_OCP)) { - usbc_ocp_snk_is_connected(port, false); - /* - * Clear the overcurrent event counter - * since we've detected a disconnect. - */ - usbc_ocp_clear_event_counter(port); - } - } -#endif /* CONFIG_USBC_PPC */ - - /* Clear the holdoff timer since the port is disconnected. */ - pd[port].ready_state_holdoff_timer = 0; - - /* - * We should not clear any flags when transitioning back to the - * disconnected state from the debounce state as the two states - * here are really the same states in the state diagram. - */ - if (last_state != PD_STATE_SNK_DISCONNECTED_DEBOUNCE && - last_state != PD_STATE_SRC_DISCONNECTED_DEBOUNCE) { - pd[port].flags &= ~PD_FLAGS_RESET_ON_DISCONNECT_MASK; - reset_pd_cable(port); - } - - /* Clear the input current limit */ - pd_set_input_current_limit(port, 0, 0); -#ifdef CONFIG_CHARGE_MANAGER - typec_set_input_current_limit(port, 0, 0); - charge_manager_set_ceil(port, - CEIL_REQUESTOR_PD, - CHARGE_CEIL_NONE); -#endif -#ifdef CONFIG_BC12_DETECT_DATA_ROLE_TRIGGER - /* - * 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. - */ - task_set_event(USB_CHG_PORT_TO_TASK_ID(port), - USB_CHG_EVENT_CC_OPEN); -#endif /* CONFIG_BC12_DETECT_DATA_ROLE_TRIGGER */ -#ifdef CONFIG_USBC_VCONN - set_vconn(port, 0); -#endif /* defined(CONFIG_USBC_VCONN) */ - pd_update_saved_port_flags(port, PD_BBRMFLG_EXPLICIT_CONTRACT, - 0); -#else /* CONFIG_USB_PD_DUAL_ROLE */ - if (next_state == PD_STATE_SRC_DISCONNECTED) { -#ifdef CONFIG_USBC_VCONN - set_vconn(port, 0); -#endif /* CONFIG_USBC_VCONN */ -#endif /* !CONFIG_USB_PD_DUAL_ROLE */ - /* If we are source, make sure VBUS is off and restore RP */ - if (pd[port].power_role == PD_ROLE_SOURCE) { - /* Restore non-active ports to CONFIG_USB_PD_PULLUP */ - pd_power_supply_reset(port); - tcpm_set_cc(port, TYPEC_CC_RP); - } -#ifdef CONFIG_USB_PD_REV30 - /* Adjust rev to highest level*/ - pd[port].rev = PD_REV30; -#endif - pd[port].dev_id = 0; -#ifdef CONFIG_CHARGE_MANAGER - charge_manager_update_dualrole(port, CAP_UNKNOWN); -#endif -#ifdef CONFIG_USB_PD_ALT_MODE_DFP - if (pd_dfp_exit_mode(port, TCPCI_MSG_SOP, 0, 0)) - usb_mux_set_safe_mode(port); -#endif - /* - * Indicate that the port is disconnected by setting role to - * DFP as SoCs have special signals when they are the UFP ports - * (e.g. OTG signals) - */ - pd_execute_data_swap(port, PD_ROLE_DFP); -#ifdef CONFIG_USBC_SS_MUX - usb_mux_set(port, USB_PD_MUX_NONE, USB_SWITCH_DISCONNECT, - pd[port].polarity); -#endif - /* Disable TCPC RX */ - tcpm_set_rx_enable(port, 0); - - /* Invalidate message IDs. */ - invalidate_last_message_id(port); - - if (not_auto_toggling) - /* Disable Auto Discharge Disconnect */ - tcpm_enable_auto_discharge_disconnect(port, 0); - - /* detect USB PD cc disconnect */ - if (IS_ENABLED(CONFIG_COMMON_RUNTIME)) - hook_notify(HOOK_USB_PD_DISCONNECT); - } - -#ifdef CONFIG_USB_PD_REV30 - /* Upon entering SRC_READY, it is safe for the sink to transmit */ - if (next_state == PD_STATE_SRC_READY) { - if (pd[port].rev == PD_REV30 && - pd[port].flags & PD_FLAGS_EXPLICIT_CONTRACT) - sink_can_xmit(port, SINK_TX_OK); - } -#endif - -#if defined(CONFIG_LOW_POWER_IDLE) && !defined(CONFIG_USB_PD_TCPC_ON_CHIP) - /* If a PD device is attached then disable deep 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); - else - disable_sleep(SLEEP_MASK_USB_PD); -#endif - -#ifdef CONFIG_USB_PD_TCPMV1_DEBUG - if (debug_level > 0) - CPRINTF("C%d st%d %s\n", port, next_state, - pd_state_names[next_state]); - else -#endif - CPRINTF("C%d st%d\n", port, next_state); -} - -/* increment message ID counter */ -static void inc_id(int port) -{ - pd[port].msg_id = (pd[port].msg_id + 1) & PD_MESSAGE_ID_COUNT; -} - -void pd_transmit_complete(int port, int status) -{ - if (status == TCPC_TX_COMPLETE_SUCCESS) - inc_id(port); - - pd[port].tx_status = status; - task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_TX); -} - -static int pd_transmit(int port, enum tcpci_msg_type type, - uint16_t header, const uint32_t *data, enum ams_seq ams) -{ - int evt; - int res; -#ifdef CONFIG_USB_PD_REV30 - int sink_ng = 0; -#endif - - /* If comms are disabled, do not transmit, return error */ - if (!pd_comm_is_enabled(port)) - return -1; - - /* Don't try to transmit anything until we have processed - * all RX messages. - */ - if (tcpm_has_pending_message(port)) - return -1; - -#ifdef CONFIG_USB_PD_REV30 - /* Source-coordinated collision avoidance */ - /* - * USB PD Rev 3.0, Version 2.0: Section 2.7.3.2 - * Collision Avoidance - Protocol Layer - * - * In order to avoid message collisions due to asynchronous Messaging - * sent from the Sink, the Source sets Rp to SinkTxOk (3A) to indicate - * to the Sink that it is ok to initiate an AMS. When the Source wishes - * to initiate an AMS, it sets Rp to SinkTxNG (1.5A). - * When the Sink detects that Rp is set to SinkTxOk, it May initiate an - * AMS. When the Sink detects that Rp is set to SinkTxNG it Shall Not - * initiate an AMS and Shall only send Messages that are part of an AMS - * the Source has initiated. - * Note that this restriction applies to SOP* AMS’s i.e. for both Port - * to Port and Port to Cable Plug communications. - * - * This starts after an Explicit Contract is in place (see section 2.5.2 - * SOP* Collision Avoidance). - * - * Note: a Sink can still send Hard Reset signaling at any time. - */ - if ((pd[port].rev == PD_REV30) && ams == AMS_START && - (pd[port].flags & PD_FLAGS_EXPLICIT_CONTRACT)) { - if (pd[port].power_role == PD_ROLE_SOURCE) { - /* - * Inform Sink that it can't transmit. If a sink - * transmission is in progress and a collision occurs, - * a reset is generated. This should be rare because - * all extended messages are chunked. This effectively - * defaults to PD REV 2.0 collision avoidance. - */ - sink_can_xmit(port, SINK_TX_NG); - sink_ng = 1; - } else if (type != TCPCI_MSG_TX_HARD_RESET) { - enum tcpc_cc_voltage_status cc1, cc2; - - tcpm_get_cc(port, &cc1, &cc2); - if (cc1 == TYPEC_CC_VOLT_RP_1_5 || - cc2 == TYPEC_CC_VOLT_RP_1_5) { - /* Sink can't transmit now. */ - /* Return failure, pd_task can retry later */ - return -1; - } - } - } -#endif - tcpm_transmit(port, type, header, data); - - /* Wait until TX is complete */ - evt = task_wait_event_mask(PD_EVENT_TX, PD_T_TCPC_TX_TIMEOUT); - - if (evt & TASK_EVENT_TIMER) - return -1; - - /* TODO: give different error condition for failed vs discarded */ - res = pd[port].tx_status == TCPC_TX_COMPLETE_SUCCESS ? 1 : -1; - -#ifdef CONFIG_USB_PD_REV30 - /* If the AMS transaction failed to start, reset CC to OK */ - if (res < 0 && sink_ng) - sink_can_xmit(port, SINK_TX_OK); -#endif - return res; -} - -static void pd_update_roles(int port) -{ - /* Notify TCPC of role update */ - tcpm_set_msg_header(port, pd[port].power_role, pd[port].data_role); -} - -static int send_control(int port, int type) -{ - int bit_len; - uint16_t header = PD_HEADER(type, pd[port].power_role, - pd[port].data_role, pd[port].msg_id, 0, - pd_get_rev(port, TCPCI_MSG_SOP), 0); - /* - * For PD 3.0, collision avoidance logic needs to know if this message - * will begin a new Atomic Message Sequence (AMS) - */ - enum ams_seq ams = ((1 << type) & PD_CTRL_AMS_START_MASK) - ? AMS_START : AMS_RESPONSE; - - - bit_len = pd_transmit(port, TCPCI_MSG_SOP, header, NULL, ams); - if (debug_level >= 2) - CPRINTF("C%d CTRL[%d]>%d\n", port, type, bit_len); - - return bit_len; -} - -/* - * Note: Source capabilities may either be in an existing AMS (ex. as a - * response to Get_Source_Cap), or the beginning of an AMS for a power - * negotiation. - */ -static int send_source_cap(int port, enum ams_seq ams) -{ - int bit_len; -#if defined(CONFIG_USB_PD_DYNAMIC_SRC_CAP) || \ - defined(CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT) - const uint32_t *src_pdo; - const int src_pdo_cnt = charge_manager_get_source_pdo(&src_pdo, port); -#else - const uint32_t *src_pdo = pd_src_pdo; - const int src_pdo_cnt = pd_src_pdo_cnt; -#endif - uint16_t header; - - if (src_pdo_cnt == 0) - /* No source capabilities defined, sink only */ - header = PD_HEADER(PD_CTRL_REJECT, pd[port].power_role, - pd[port].data_role, pd[port].msg_id, 0, - pd_get_rev(port, TCPCI_MSG_SOP), 0); - else - header = PD_HEADER(PD_DATA_SOURCE_CAP, pd[port].power_role, - pd[port].data_role, pd[port].msg_id, src_pdo_cnt, - pd_get_rev(port, TCPCI_MSG_SOP), 0); - - bit_len = pd_transmit(port, TCPCI_MSG_SOP, header, src_pdo, ams); - if (debug_level >= 2) - CPRINTF("C%d srcCAP>%d\n", port, bit_len); - - return bit_len; -} - -#ifdef CONFIG_USB_PD_REV30 -static int send_battery_cap(int port, uint32_t *payload) -{ - int bit_len; - uint16_t msg[6] = {0, 0, 0, 0, 0, 0}; - uint16_t header = PD_HEADER(PD_EXT_BATTERY_CAP, - pd[port].power_role, - pd[port].data_role, - pd[port].msg_id, - 3, /* Number of Data Objects */ - pd[port].rev, - 1 /* This is an exteded message */ - ); - - /* Set extended header */ - msg[0] = PD_EXT_HEADER(0, /* Chunk Number */ - 0, /* Request Chunk */ - 9 /* Data Size in bytes */ - ); - /* Set VID */ - msg[1] = USB_VID_GOOGLE; - - /* Set PID */ - msg[2] = CONFIG_USB_PID; - - if (battery_is_present()) { - /* - * We only have one fixed battery, - * so make sure batt cap ref is 0. - */ - if (BATT_CAP_REF(payload[0]) != 0) { - /* Invalid battery reference */ - msg[5] = 1; - } else { - uint32_t v; - uint32_t c; - - /* - * The Battery Design Capacity field shall return the - * Battery’s design capacity in tenths of Wh. If the - * Battery is Hot Swappable and is not present, the - * Battery Design Capacity field shall be set to 0. If - * the Battery is unable to report its Design Capacity, - * it shall return 0xFFFF - */ - msg[3] = 0xffff; - - /* - * The Battery Last Full Charge Capacity field shall - * return the Battery’s last full charge capacity in - * tenths of Wh. If the Battery is Hot Swappable and - * is not present, the Battery Last Full Charge Capacity - * field shall be set to 0. If the Battery is unable to - * report its Design Capacity, the Battery Last Full - * Charge Capacity field shall be set to 0xFFFF. - */ - msg[4] = 0xffff; - - if (battery_design_voltage(&v) == 0) { - if (battery_design_capacity(&c) == 0) { - /* - * Wh = (c * v) / 1000000 - * 10th of a Wh = Wh * 10 - */ - msg[3] = DIV_ROUND_NEAREST((c * v), - 100000); - } - - if (battery_full_charge_capacity(&c) == 0) { - /* - * Wh = (c * v) / 1000000 - * 10th of a Wh = Wh * 10 - */ - msg[4] = DIV_ROUND_NEAREST((c * v), - 100000); - } - } - } - } - - bit_len = pd_transmit(port, TCPCI_MSG_SOP, header, (uint32_t *)msg, - AMS_RESPONSE); - if (debug_level >= 2) - CPRINTF("C%d batCap>%d\n", port, bit_len); - return bit_len; -} - -static int send_battery_status(int port, uint32_t *payload) -{ - int bit_len; - uint32_t msg = 0; - uint16_t header = PD_HEADER(PD_DATA_BATTERY_STATUS, - pd[port].power_role, - pd[port].data_role, - pd[port].msg_id, - 1, /* Number of Data Objects */ - pd[port].rev, - 0 /* This is NOT an extended message */ - ); - - if (battery_is_present()) { - /* - * We only have one fixed battery, - * so make sure batt cap ref is 0. - */ - if (BATT_CAP_REF(payload[0]) != 0) { - /* Invalid battery reference */ - msg |= BSDO_INVALID; - } else { - uint32_t v; - uint32_t c; - - if (battery_design_voltage(&v) != 0 || - battery_remaining_capacity(&c) != 0) { - msg |= BSDO_CAP(BSDO_CAP_UNKNOWN); - } else { - /* - * Wh = (c * v) / 1000000 - * 10th of a Wh = Wh * 10 - */ - msg |= BSDO_CAP(DIV_ROUND_NEAREST((c * v), - 100000)); - } - - /* Battery is present */ - msg |= BSDO_PRESENT; - - /* - * For drivers that are not smart battery compliant, - * battery_status() returns EC_ERROR_UNIMPLEMENTED and - * the battery is assumed to be idle. - */ - if (battery_status(&c) != 0) { - msg |= BSDO_IDLE; /* assume idle */ - } else { - if (c & STATUS_FULLY_CHARGED) - /* Fully charged */ - msg |= BSDO_IDLE; - else if (c & STATUS_DISCHARGING) - /* Discharging */ - msg |= BSDO_DISCHARGING; - /* else battery is charging.*/ - } - } - } else { - msg = BSDO_CAP(BSDO_CAP_UNKNOWN); - } - - bit_len = pd_transmit(port, TCPCI_MSG_SOP, header, &msg, AMS_RESPONSE); - if (debug_level >= 2) - CPRINTF("C%d batStat>%d\n", port, bit_len); - - return bit_len; -} -#endif - -#ifdef CONFIG_USB_PD_DUAL_ROLE -static void send_sink_cap(int port) -{ - int bit_len; - uint16_t header = PD_HEADER(PD_DATA_SINK_CAP, pd[port].power_role, - pd[port].data_role, pd[port].msg_id, pd_snk_pdo_cnt, - pd_get_rev(port, TCPCI_MSG_SOP), 0); - - bit_len = pd_transmit(port, TCPCI_MSG_SOP, header, pd_snk_pdo, - AMS_RESPONSE); - if (debug_level >= 2) - CPRINTF("C%d snkCAP>%d\n", port, bit_len); -} - -static int send_request(int port, uint32_t rdo) -{ - int bit_len; - uint16_t header = PD_HEADER(PD_DATA_REQUEST, pd[port].power_role, - pd[port].data_role, pd[port].msg_id, 1, - pd_get_rev(port, TCPCI_MSG_SOP), 0); - - /* Note: ams will need to be AMS_START if used for PPS keep alive */ - bit_len = pd_transmit(port, TCPCI_MSG_SOP, header, &rdo, AMS_RESPONSE); - if (debug_level >= 2) - CPRINTF("C%d REQ>%d\n", port, bit_len); - - return bit_len; -} - -#endif /* CONFIG_USB_PD_DUAL_ROLE */ - -#ifdef CONFIG_COMMON_RUNTIME -static int send_bist_cmd(int port) -{ - /* currently only support sending bist carrier 2 */ - uint32_t bdo = BDO(BDO_MODE_CARRIER2, 0); - int bit_len; - uint16_t header = PD_HEADER(PD_DATA_BIST, pd[port].power_role, - pd[port].data_role, pd[port].msg_id, 1, - pd_get_rev(port, TCPCI_MSG_SOP), 0); - - bit_len = pd_transmit(port, TCPCI_MSG_SOP, header, &bdo, AMS_START); - CPRINTF("C%d BIST>%d\n", port, bit_len); - - return bit_len; -} -#endif - -static void queue_vdm(int port, uint32_t *header, const uint32_t *data, - int data_cnt, enum tcpci_msg_type type) -{ - pd[port].vdo_count = data_cnt + 1; - pd[port].vdo_data[0] = header[0]; - pd[port].xmit_type = type; - memcpy(&pd[port].vdo_data[1], data, - sizeof(uint32_t) * data_cnt); - /* Set ready, pd task will actually send */ - pd[port].vdm_state = VDM_STATE_READY; -} - -static void handle_vdm_request(int port, int cnt, uint32_t *payload, - uint32_t head) -{ - int rlen = 0; - uint32_t *rdata; - enum tcpci_msg_type rtype = TCPCI_MSG_SOP; - - if (pd[port].vdm_state == VDM_STATE_BUSY) { - /* If UFP responded busy retry after timeout */ - if (PD_VDO_CMDT(payload[0]) == CMDT_RSP_BUSY) { - pd[port].vdm_timeout.val = get_time().val + - PD_T_VDM_BUSY; - pd[port].vdm_state = VDM_STATE_WAIT_RSP_BUSY; - pd[port].vdo_retry = (payload[0] & ~VDO_CMDT_MASK) | - CMDT_INIT; - return; - } else { - pd[port].vdm_state = VDM_STATE_DONE; -#ifdef CONFIG_USB_PD_REV30 - if (pd[port].rev == PD_REV30 && - pd[port].power_role == PD_ROLE_SOURCE && - pd[port].flags & PD_FLAGS_EXPLICIT_CONTRACT) - sink_can_xmit(port, SINK_TX_OK); -#endif - } - } - - if (PD_VDO_SVDM(payload[0])) - rlen = pd_svdm(port, cnt, payload, &rdata, head, &rtype); - else - rlen = pd_custom_vdm(port, cnt, payload, &rdata); - - if (rlen > 0) { - queue_vdm(port, rdata, &rdata[1], rlen - 1, rtype); - return; - } - - if (debug_level >= 2) - CPRINTF("C%d Unhandled VDM VID %04x CMD %04x\n", - port, PD_VDO_VID(payload[0]), payload[0] & 0xFFFF); -} - -bool pd_is_disconnected(int port) -{ - return pd[port].task_state == PD_STATE_SRC_DISCONNECTED -#ifdef CONFIG_USB_PD_DUAL_ROLE - || pd[port].task_state == PD_STATE_SNK_DISCONNECTED -#endif - ; -} - -static void pd_set_data_role(int port, enum pd_data_role role) -{ - pd[port].data_role = role; -#ifdef CONFIG_USB_PD_DUAL_ROLE - pd_update_saved_port_flags(port, PD_BBRMFLG_DATA_ROLE, role); -#endif /* defined(CONFIG_USB_PD_DUAL_ROLE) */ - pd_execute_data_swap(port, role); - - set_usb_mux_with_current_data_role(port); - pd_update_roles(port); -#ifdef CONFIG_BC12_DETECT_DATA_ROLE_TRIGGER - /* - * 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. - */ - if (role == PD_ROLE_UFP) - task_set_event(USB_CHG_PORT_TO_TASK_ID(port), - USB_CHG_EVENT_DR_UFP); - else if (role == PD_ROLE_DFP) - task_set_event(USB_CHG_PORT_TO_TASK_ID(port), - USB_CHG_EVENT_DR_DFP); -#endif /* CONFIG_BC12_DETECT_DATA_ROLE_TRIGGER */ -} - -#ifdef CONFIG_USBC_VCONN -static void pd_set_vconn_role(int port, int role) -{ - if (role == PD_ROLE_VCONN_ON) - pd[port].flags |= PD_FLAGS_VCONN_ON; - else - pd[port].flags &= ~PD_FLAGS_VCONN_ON; - -#ifdef CONFIG_USB_PD_DUAL_ROLE - pd_update_saved_port_flags(port, PD_BBRMFLG_VCONN_ROLE, role); -#endif -} -#endif /* CONFIG_USBC_VCONN */ - -void pd_execute_hard_reset(int port) -{ - int hard_rst_tx = pd[port].last_state == PD_STATE_HARD_RESET_SEND; - - CPRINTF("C%d HARD RST %cX\n", port, hard_rst_tx ? 'T' : 'R'); - - pd[port].msg_id = 0; - invalidate_last_message_id(port); - tcpm_set_rx_enable(port, 0); -#ifdef CONFIG_USB_PD_ALT_MODE_DFP - if (pd_dfp_exit_mode(port, TCPCI_MSG_SOP, 0, 0)) - usb_mux_set_safe_mode(port); -#endif - -#ifdef CONFIG_USB_PD_REV30 - pd[port].rev = PD_REV30; -#endif - /* - * Fake set last state to hard reset to make sure that the next - * state to run knows that we just did a hard reset. - */ - pd[port].last_state = PD_STATE_HARD_RESET_EXECUTE; - -#ifdef CONFIG_USB_PD_DUAL_ROLE - /* - * If we are swapping to a source and have changed to Rp, restore back - * to Rd and turn off vbus to match our power_role. - */ - if (pd[port].task_state == PD_STATE_SNK_SWAP_STANDBY || - pd[port].task_state == PD_STATE_SNK_SWAP_COMPLETE) { - tcpm_set_cc(port, TYPEC_CC_RD); - pd_power_supply_reset(port); - } - - if (pd[port].power_role == PD_ROLE_SINK) { - /* Initial data role for sink is UFP */ - pd_set_data_role(port, PD_ROLE_UFP); - - /* Clear the input current limit */ - pd_set_input_current_limit(port, 0, 0); -#ifdef CONFIG_CHARGE_MANAGER - charge_manager_set_ceil(port, - CEIL_REQUESTOR_PD, - CHARGE_CEIL_NONE); -#endif /* CONFIG_CHARGE_MANAGER */ - -#ifdef CONFIG_USBC_VCONN - /* - * Sink must turn off Vconn after a hard reset if it was being - * sourced previously - */ - if (pd[port].flags & PD_FLAGS_VCONN_ON) { - set_vconn(port, 0); - pd_set_vconn_role(port, PD_ROLE_VCONN_OFF); - } -#endif - - set_state(port, PD_STATE_SNK_HARD_RESET_RECOVER); - return; - } else { - /* Initial data role for source is DFP */ - pd_set_data_role(port, PD_ROLE_DFP); - } - -#endif /* CONFIG_USB_PD_DUAL_ROLE */ - - if (!hard_rst_tx) - usleep(PD_T_PS_HARD_RESET); - - /* We are a source, cut power */ - pd_power_supply_reset(port); - pd[port].src_recover = get_time().val + PD_T_SRC_RECOVER; -#ifdef CONFIG_USBC_VCONN - set_vconn(port, 0); -#endif - set_state(port, PD_STATE_SRC_HARD_RESET_RECOVER); -} - -static void execute_soft_reset(int port) -{ - invalidate_last_message_id(port); - set_state(port, DUAL_ROLE_IF_ELSE(port, PD_STATE_SNK_DISCOVERY, - PD_STATE_SRC_DISCOVERY)); - CPRINTF("C%d Soft Rst\n", port); -} - -void pd_soft_reset(void) -{ - int i; - - for (i = 0; i < board_get_usb_pd_port_count(); ++i) - if (pd_is_connected(i)) { - set_state(i, PD_STATE_SOFT_RESET); - task_wake(PD_PORT_TO_TASK_ID(i)); - } -} - -#ifdef CONFIG_USB_PD_DUAL_ROLE -/* - * Request desired charge voltage from source. - * Returns EC_SUCCESS on success or non-zero on failure. - */ -static int pd_send_request_msg(int port, int always_send_request) -{ - uint32_t rdo, curr_limit, supply_voltage; - int res; - - /* Clear new power request */ - pd[port].new_power_request = 0; - - /* Build and send request RDO */ - pd_build_request(0, &rdo, &curr_limit, &supply_voltage, port); - - if (!always_send_request) { - /* Don't re-request the same voltage */ - if (pd[port].prev_request_mv == supply_voltage) - return EC_SUCCESS; -#ifdef CONFIG_CHARGE_MANAGER - /* Limit current to PD_MIN_MA during transition */ - else - charge_manager_force_ceil(port, PD_MIN_MA); -#endif - } - - CPRINTF("C%d Req [%d] %dmV %dmA", port, RDO_POS(rdo), - supply_voltage, curr_limit); - if (rdo & RDO_CAP_MISMATCH) - CPRINTF(" Mismatch"); - CPRINTF("\n"); - - pd[port].curr_limit = curr_limit; - pd[port].supply_voltage = supply_voltage; - pd[port].prev_request_mv = supply_voltage; - res = send_request(port, rdo); - if (res < 0) - return res; - set_state(port, PD_STATE_SNK_REQUESTED); - return EC_SUCCESS; -} -#endif - -static void pd_update_pdo_flags(int port, int pdo_cnt, uint32_t *pdos) -{ - /* can only parse PDO flags if type is fixed */ - if ((pdos[0] & PDO_TYPE_MASK) != PDO_TYPE_FIXED) - return; - -#ifdef CONFIG_USB_PD_DUAL_ROLE - if (pdos[0] & PDO_FIXED_DUAL_ROLE) - pd[port].flags |= PD_FLAGS_PARTNER_DR_POWER; - else - pd[port].flags &= ~PD_FLAGS_PARTNER_DR_POWER; - - if (pdos[0] & PDO_FIXED_UNCONSTRAINED) - pd[port].flags |= PD_FLAGS_PARTNER_UNCONSTR; - else - pd[port].flags &= ~PD_FLAGS_PARTNER_UNCONSTR; - - if (pdos[0] & PDO_FIXED_COMM_CAP) - pd[port].flags |= PD_FLAGS_PARTNER_USB_COMM; - else - pd[port].flags &= ~PD_FLAGS_PARTNER_USB_COMM; -#endif - - if (pdos[0] & PDO_FIXED_DATA_SWAP) - pd[port].flags |= PD_FLAGS_PARTNER_DR_DATA; - else - pd[port].flags &= ~PD_FLAGS_PARTNER_DR_DATA; - - /* - * Treat device as a dedicated charger (meaning we should charge - * from it) if: - * - it does not support power swap, or - * - it is unconstrained power, or - * - it presents at least 27 W of available power - */ - if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) { - uint32_t max_ma, max_mv, max_pdo, max_mw, unused; - - /* - * Get max power that the partner offers (not necessarily what - * this board will request) - */ - pd_find_pdo_index(pdo_cnt, pdos, PD_REV3_MAX_VOLTAGE, - &max_pdo); - pd_extract_pdo_power(max_pdo, &max_ma, &max_mv, &unused); - max_mw = max_ma * max_mv / 1000; - - if (!(pdos[0] & PDO_FIXED_DUAL_ROLE) || - (pdos[0] & PDO_FIXED_UNCONSTRAINED) || - max_mw >= PD_DRP_CHARGE_POWER_MIN) - charge_manager_update_dualrole(port, CAP_DEDICATED); - else - charge_manager_update_dualrole(port, CAP_DUALROLE); - } -} - -static void handle_data_request(int port, uint32_t head, - uint32_t *payload) -{ - int type = PD_HEADER_TYPE(head); - int cnt = PD_HEADER_CNT(head); - - switch (type) { -#ifdef CONFIG_USB_PD_DUAL_ROLE - case PD_DATA_SOURCE_CAP: - if ((pd[port].task_state == PD_STATE_SNK_DISCOVERY) - || (pd[port].task_state == PD_STATE_SNK_TRANSITION) - || (pd[port].task_state == PD_STATE_SNK_REQUESTED) - || ((get_usb_pd_vbus_detect() == - USB_PD_VBUS_DETECT_NONE) - && (pd[port].task_state == - PD_STATE_SNK_HARD_RESET_RECOVER)) - || (pd[port].task_state == PD_STATE_SNK_READY)) { -#ifdef CONFIG_USB_PD_REV30 - /* - * Only adjust sink rev if source rev is higher. - */ - if (PD_HEADER_REV(head) < pd[port].rev) - pd[port].rev = PD_HEADER_REV(head); -#endif - /* Port partner is now known to be PD capable */ - pd[port].flags |= PD_FLAGS_PREVIOUS_PD_CONN; - - /* src cap 0 should be fixed PDO */ - pd_update_pdo_flags(port, cnt, payload); - - pd_process_source_cap(port, cnt, payload); - - /* Source will resend source cap on failure */ - pd_send_request_msg(port, 1); - } - break; -#endif /* CONFIG_USB_PD_DUAL_ROLE */ - case PD_DATA_REQUEST: - if ((pd[port].power_role == PD_ROLE_SOURCE) && (cnt == 1)) { -#ifdef CONFIG_USB_PD_REV30 - /* - * Adjust the rev level to what the sink supports. If - * they're equal, no harm done. - */ - pd[port].rev = PD_HEADER_REV(head); -#endif - if (!pd_check_requested_voltage(payload[0], port)) { - if (send_control(port, PD_CTRL_ACCEPT) < 0) - /* - * if we fail to send accept, do - * nothing and let sink timeout and - * send hard reset - */ - return; - - /* explicit contract is now in place */ - pd[port].flags |= PD_FLAGS_EXPLICIT_CONTRACT; -#ifdef CONFIG_USB_PD_DUAL_ROLE - pd_update_saved_port_flags( - port, PD_BBRMFLG_EXPLICIT_CONTRACT, 1); -#endif /* CONFIG_USB_PD_DUAL_ROLE */ - pd[port].requested_idx = RDO_POS(payload[0]); - set_state(port, PD_STATE_SRC_ACCEPTED); - return; - } - } - /* the message was incorrect or cannot be satisfied */ - send_control(port, PD_CTRL_REJECT); - /* keep last contract in place (whether implicit or explicit) */ - set_state(port, PD_STATE_SRC_READY); - break; - case PD_DATA_BIST: - /* If not in READY state, then don't start BIST */ - if (DUAL_ROLE_IF_ELSE(port, - pd[port].task_state == PD_STATE_SNK_READY, - pd[port].task_state == PD_STATE_SRC_READY)) { - /* currently only support sending bist carrier mode 2 */ - if ((payload[0] >> 28) == 5) { - /* bist data object mode is 2 */ - pd_transmit(port, TCPCI_MSG_TX_BIST_MODE_2, 0, - NULL, AMS_RESPONSE); - /* Set to appropriate port disconnected state */ - set_state(port, DUAL_ROLE_IF_ELSE(port, - PD_STATE_SNK_DISCONNECTED, - PD_STATE_SRC_DISCONNECTED)); - } - } - break; - case PD_DATA_SINK_CAP: - pd[port].flags |= PD_FLAGS_SNK_CAP_RECVD; - /* snk cap 0 should be fixed PDO */ - pd_update_pdo_flags(port, cnt, payload); - if (pd[port].task_state == PD_STATE_SRC_GET_SINK_CAP) - set_state(port, PD_STATE_SRC_READY); - break; -#ifdef CONFIG_USB_PD_REV30 - case PD_DATA_BATTERY_STATUS: - break; - /* TODO : Add case PD_DATA_RESET for exiting USB4 */ - - /* - * TODO : Add case PD_DATA_ENTER_USB to accept or reject - * Enter_USB request from port partner. - */ -#endif - case PD_DATA_VENDOR_DEF: - handle_vdm_request(port, cnt, payload, head); - break; - default: - CPRINTF("C%d Unhandled data message type %d\n", port, type); - } -} - -#ifdef CONFIG_USB_PD_DUAL_ROLE -void pd_request_power_swap(int port) -{ - if (pd[port].task_state == PD_STATE_SRC_READY) - set_state(port, PD_STATE_SRC_SWAP_INIT); - else if (pd[port].task_state == PD_STATE_SNK_READY) - set_state(port, PD_STATE_SNK_SWAP_INIT); - task_wake(PD_PORT_TO_TASK_ID(port)); -} - -#ifdef CONFIG_USBC_VCONN_SWAP -void pd_request_vconn_swap(int port) -{ - if (pd[port].task_state == PD_STATE_SRC_READY || - pd[port].task_state == PD_STATE_SNK_READY) - set_state(port, PD_STATE_VCONN_SWAP_SEND); - task_wake(PD_PORT_TO_TASK_ID(port)); -} - -void pd_try_vconn_src(int port) -{ - /* - * If we don't currently provide vconn, and we can supply it, send - * a vconn swap request. - */ - if (!(pd[port].flags & PD_FLAGS_VCONN_ON)) { - if (pd_check_vconn_swap(port)) - pd_request_vconn_swap(port); - } -} -#endif -#endif /* CONFIG_USB_PD_DUAL_ROLE */ - -void pd_request_data_swap(int port) -{ - if (DUAL_ROLE_IF_ELSE(port, - pd[port].task_state == PD_STATE_SNK_READY, - pd[port].task_state == PD_STATE_SRC_READY)) - set_state(port, PD_STATE_DR_SWAP); - task_wake(PD_PORT_TO_TASK_ID(port)); -} - -static void pd_set_power_role(int port, enum pd_power_role role) -{ - pd[port].power_role = role; -#ifdef CONFIG_USB_PD_DUAL_ROLE - pd_update_saved_port_flags(port, PD_BBRMFLG_POWER_ROLE, role); -#endif /* defined(CONFIG_USB_PD_DUAL_ROLE) */ -} - -static void pd_dr_swap(int port) -{ - pd_set_data_role(port, !pd[port].data_role); - pd[port].flags |= PD_FLAGS_CHECK_IDENTITY; -} - -static void handle_ctrl_request(int port, uint32_t head, - uint32_t *payload) -{ - int type = PD_HEADER_TYPE(head); - int res; - - switch (type) { - case PD_CTRL_GOOD_CRC: - /* should not get it */ - break; - case PD_CTRL_PING: - /* Nothing else to do */ - break; - case PD_CTRL_GET_SOURCE_CAP: - if (pd[port].task_state == PD_STATE_SRC_READY) - set_state(port, PD_STATE_SRC_DISCOVERY); - else { - res = send_source_cap(port, AMS_RESPONSE); - if ((res >= 0) && - (pd[port].task_state == PD_STATE_SRC_DISCOVERY)) - set_state(port, PD_STATE_SRC_NEGOCIATE); - } - break; - case PD_CTRL_GET_SINK_CAP: -#ifdef CONFIG_USB_PD_DUAL_ROLE - send_sink_cap(port); -#else - send_control(port, NOT_SUPPORTED(pd[port].rev)); -#endif - break; -#ifdef CONFIG_USB_PD_DUAL_ROLE - case PD_CTRL_GOTO_MIN: -#ifdef CONFIG_USB_PD_GIVE_BACK - if (pd[port].task_state == PD_STATE_SNK_READY) { - /* - * Reduce power consumption now! - * - * The source will restore power to this sink - * by sending a new source cap message at a - * later time. - */ - pd_snk_give_back(port, &pd[port].curr_limit, - &pd[port].supply_voltage); - set_state(port, PD_STATE_SNK_TRANSITION); - } -#endif - - break; - case PD_CTRL_PS_RDY: - if (pd[port].task_state == PD_STATE_SNK_SWAP_SRC_DISABLE) { - set_state(port, PD_STATE_SNK_SWAP_STANDBY); - } else if (pd[port].task_state == PD_STATE_SRC_SWAP_STANDBY) { - /* reset message ID and swap roles */ - pd[port].msg_id = 0; - invalidate_last_message_id(port); - pd_set_power_role(port, PD_ROLE_SINK); - pd_update_roles(port); - /* - * Give the state machine time to read VBUS as high. - * Note: This is empirically determined, not strictly - * part of the USB PD spec. - */ - pd[port].vbus_debounce_time = - get_time().val + PD_T_DEBOUNCE; - set_state(port, PD_STATE_SNK_DISCOVERY); -#ifdef CONFIG_USBC_VCONN_SWAP - } else if (pd[port].task_state == PD_STATE_VCONN_SWAP_INIT) { - /* - * If VCONN is on, then this PS_RDY tells us it's - * ok to turn VCONN off - */ - if (pd[port].flags & PD_FLAGS_VCONN_ON) - set_state(port, PD_STATE_VCONN_SWAP_READY); -#endif - } else if (pd[port].task_state == PD_STATE_SNK_DISCOVERY) { - /* Don't know what power source is ready. Reset. */ - set_state(port, PD_STATE_HARD_RESET_SEND); - } else if (pd[port].task_state == PD_STATE_SNK_SWAP_STANDBY) { - /* Do nothing, assume this is a redundant PD_RDY */ - } else if (pd[port].power_role == PD_ROLE_SINK) { - /* - * Give the source some time to send any messages before - * we start our interrogation. Add some jitter of up to - * ~192ms to prevent multiple collisions. - */ - if (pd[port].task_state == PD_STATE_SNK_TRANSITION) - pd[port].ready_state_holdoff_timer = - get_time().val + SNK_READY_HOLD_OFF_US - + (get_time().le.lo & 0xf) * 12 * MSEC; - - set_state(port, PD_STATE_SNK_READY); - pd_set_input_current_limit(port, pd[port].curr_limit, - pd[port].supply_voltage); -#ifdef CONFIG_CHARGE_MANAGER - /* Set ceiling based on what's negotiated */ - charge_manager_set_ceil(port, - CEIL_REQUESTOR_PD, - pd[port].curr_limit); -#endif - } - break; -#endif - case PD_CTRL_REJECT: - if (pd[port].task_state == PD_STATE_ENTER_USB) { - if (!IS_ENABLED(CONFIG_USBC_SS_MUX)) - break; - - /* - * Since Enter USB sets the mux state to SAFE mode, - * resetting the mux state back to USB mode on - * recieveing a NACK. - */ - usb_mux_set(port, USB_PD_MUX_USB_ENABLED, - USB_SWITCH_CONNECT, pd[port].polarity); - - set_state(port, READY_RETURN_STATE(port)); - break; - } - case PD_CTRL_WAIT: - if (pd[port].task_state == PD_STATE_DR_SWAP) { - if (type == PD_CTRL_WAIT) /* try again ... */ - pd[port].flags |= PD_FLAGS_CHECK_DR_ROLE; - set_state(port, READY_RETURN_STATE(port)); - } -#ifdef CONFIG_USBC_VCONN_SWAP - else if (pd[port].task_state == PD_STATE_VCONN_SWAP_SEND) - set_state(port, READY_RETURN_STATE(port)); -#endif -#ifdef CONFIG_USB_PD_DUAL_ROLE - else if (pd[port].task_state == PD_STATE_SRC_SWAP_INIT) - set_state(port, PD_STATE_SRC_READY); - else if (pd[port].task_state == PD_STATE_SNK_SWAP_INIT) - set_state(port, PD_STATE_SNK_READY); - else if (pd[port].task_state == PD_STATE_SNK_REQUESTED) { - /* - * On reception of a WAIT message, transition to - * PD_STATE_SNK_READY after PD_T_SINK_REQUEST ms to - * send another request. - * - * On reception of a REJECT message, transition to - * PD_STATE_SNK_READY but don't resend the request if - * we already have a contract in place. - * - * On reception of a REJECT message without a contract, - * transition to PD_STATE_SNK_DISCOVERY instead. - */ - if (type == PD_CTRL_WAIT) { - /* - * Trigger a new power request when - * we enter PD_STATE_SNK_READY - */ - pd[port].new_power_request = 1; - - /* - * After the request is triggered, - * make sure the request is sent. - */ - pd[port].prev_request_mv = 0; - - /* - * Transition to PD_STATE_SNK_READY - * after PD_T_SINK_REQUEST ms. - */ - set_state_timeout(port, - get_time().val + - PD_T_SINK_REQUEST, - PD_STATE_SNK_READY); - } else { - /* The request was rejected */ - const int in_contract = - pd[port].flags & - PD_FLAGS_EXPLICIT_CONTRACT; - set_state(port, - in_contract ? PD_STATE_SNK_READY - : PD_STATE_SNK_DISCOVERY); - } - } -#endif - break; - case PD_CTRL_ACCEPT: - if (pd[port].task_state == PD_STATE_ENTER_USB) { - if (!IS_ENABLED(CONFIG_USBC_SS_MUX)) - break; - - /* Connect the SBU and USB lines to the connector */ - if (IS_ENABLED(CONFIG_USBC_PPC_SBU)) - ppc_set_sbu(port, 1); - - /* Set usb mux to USB4 mode */ - usb_mux_set(port, USB_PD_MUX_USB4_ENABLED, - USB_SWITCH_CONNECT, pd[port].polarity); - - set_state(port, READY_RETURN_STATE(port)); - } else if (pd[port].task_state == PD_STATE_SOFT_RESET) { - /* - * For the case that we sent soft reset in SNK_DISCOVERY - * on startup due to VBUS never low, clear the flag. - */ - pd[port].flags &= ~PD_FLAGS_VBUS_NEVER_LOW; - execute_soft_reset(port); - } else if (pd[port].task_state == PD_STATE_DR_SWAP) { - /* switch data role */ - pd_dr_swap(port); - set_state(port, READY_RETURN_STATE(port)); -#ifdef CONFIG_USB_PD_DUAL_ROLE -#ifdef CONFIG_USBC_VCONN_SWAP - } else if (pd[port].task_state == PD_STATE_VCONN_SWAP_SEND) { - /* switch vconn */ - set_state(port, PD_STATE_VCONN_SWAP_INIT); -#endif - } else if (pd[port].task_state == PD_STATE_SRC_SWAP_INIT) { - /* explicit contract goes away for power swap */ - pd[port].flags &= ~PD_FLAGS_EXPLICIT_CONTRACT; - pd_update_saved_port_flags(port, - PD_BBRMFLG_EXPLICIT_CONTRACT, - 0); - set_state(port, PD_STATE_SRC_SWAP_SNK_DISABLE); - } else if (pd[port].task_state == PD_STATE_SNK_SWAP_INIT) { - /* explicit contract goes away for power swap */ - pd[port].flags &= ~PD_FLAGS_EXPLICIT_CONTRACT; - pd_update_saved_port_flags(port, - PD_BBRMFLG_EXPLICIT_CONTRACT, - 0); - set_state(port, PD_STATE_SNK_SWAP_SNK_DISABLE); - } else if (pd[port].task_state == PD_STATE_SNK_REQUESTED) { - /* explicit contract is now in place */ - pd[port].flags |= PD_FLAGS_EXPLICIT_CONTRACT; - pd_update_saved_port_flags(port, - PD_BBRMFLG_EXPLICIT_CONTRACT, - 1); - set_state(port, PD_STATE_SNK_TRANSITION); -#endif - } - break; - case PD_CTRL_SOFT_RESET: - execute_soft_reset(port); - pd[port].msg_id = 0; - /* We are done, acknowledge with an Accept packet */ - send_control(port, PD_CTRL_ACCEPT); - break; - case PD_CTRL_PR_SWAP: -#ifdef CONFIG_USB_PD_DUAL_ROLE - if (pd_check_power_swap(port)) { - send_control(port, PD_CTRL_ACCEPT); - /* - * Clear flag for checking power role to avoid - * immediately requesting another swap. - */ - pd[port].flags &= ~PD_FLAGS_CHECK_PR_ROLE; - set_state(port, - DUAL_ROLE_IF_ELSE(port, - PD_STATE_SNK_SWAP_SNK_DISABLE, - PD_STATE_SRC_SWAP_SNK_DISABLE)); - } else { - send_control(port, PD_CTRL_REJECT); - } -#else - send_control(port, NOT_SUPPORTED(pd[port].rev)); -#endif - break; - case PD_CTRL_DR_SWAP: - if (pd_check_data_swap(port, pd[port].data_role)) { - /* - * Accept switch and perform data swap. Clear - * flag for checking data role to avoid - * immediately requesting another swap. - */ - pd[port].flags &= ~PD_FLAGS_CHECK_DR_ROLE; - if (send_control(port, PD_CTRL_ACCEPT) >= 0) - pd_dr_swap(port); - } else { - send_control(port, PD_CTRL_REJECT); - - } - break; - case PD_CTRL_VCONN_SWAP: -#ifdef CONFIG_USBC_VCONN_SWAP - if (pd[port].task_state == PD_STATE_SRC_READY || - pd[port].task_state == PD_STATE_SNK_READY) { - if (pd_check_vconn_swap(port)) { - if (send_control(port, PD_CTRL_ACCEPT) > 0) - set_state(port, - PD_STATE_VCONN_SWAP_INIT); - } else { - send_control(port, PD_CTRL_REJECT); - } - } -#else - send_control(port, NOT_SUPPORTED(pd[port].rev)); -#endif - break; - default: -#ifdef CONFIG_USB_PD_REV30 - send_control(port, PD_CTRL_NOT_SUPPORTED); -#endif - CPRINTF("C%d Unhandled ctrl message type %d\n", port, type); - } -} - -#ifdef CONFIG_USB_PD_REV30 -static void handle_ext_request(int port, uint16_t head, uint32_t *payload) -{ - int type = PD_HEADER_TYPE(head); - - switch (type) { - case PD_EXT_GET_BATTERY_CAP: - send_battery_cap(port, payload); - break; - case PD_EXT_GET_BATTERY_STATUS: - send_battery_status(port, payload); - break; - case PD_EXT_BATTERY_CAP: - break; - default: - send_control(port, PD_CTRL_NOT_SUPPORTED); - } -} -#endif - -static void handle_request(int port, uint32_t head, - uint32_t *payload) -{ - int cnt = PD_HEADER_CNT(head); - int data_role = PD_HEADER_DROLE(head); - int p; - - /* dump received packet content (only dump ping at debug level 3) */ - if ((debug_level == 2 && PD_HEADER_TYPE(head) != PD_CTRL_PING) || - debug_level >= 3) { - CPRINTF("C%d RECV %04x/%d ", port, head, cnt); - for (p = 0; p < cnt; p++) - CPRINTF("[%d]%08x ", p, payload[p]); - CPRINTF("\n"); - } - - /* - * If we are in disconnected state, we shouldn't get a request. Do - * a hard reset if we get one. - */ - if (!pd_is_connected(port)) - set_state(port, PD_STATE_HARD_RESET_SEND); - - /* - * When a data role conflict is detected, USB-C ErrorRecovery - * actions shall be performed, and transitioning to unattached state - * is one such legal action. - */ - if (pd[port].data_role == data_role) { - /* - * If the port doesn't support removing the terminations, just - * go to the unattached state. - */ - if (tcpm_set_cc(port, TYPEC_CC_OPEN) == EC_SUCCESS) { - /* Do not drive VBUS or VCONN. */ - pd_power_supply_reset(port); -#ifdef CONFIG_USBC_VCONN - set_vconn(port, 0); -#endif /* defined(CONFIG_USBC_VCONN) */ - usleep(PD_T_ERROR_RECOVERY); - - /* Restore terminations. */ - tcpm_set_cc(port, DUAL_ROLE_IF_ELSE(port, TYPEC_CC_RD, - TYPEC_CC_RP)); - } - set_state(port, - DUAL_ROLE_IF_ELSE(port, - PD_STATE_SNK_DISCONNECTED, - PD_STATE_SRC_DISCONNECTED)); - return; - } - -#ifdef CONFIG_USB_PD_REV30 - /* Check if this is an extended chunked data message. */ - if (pd[port].rev == PD_REV30 && PD_HEADER_EXT(head)) { - handle_ext_request(port, head, payload); - return; - } -#endif - if (cnt) - handle_data_request(port, head, payload); - else - handle_ctrl_request(port, head, payload); -} - -void pd_send_vdm(int port, uint32_t vid, int cmd, const uint32_t *data, - int count) -{ - if (count > VDO_MAX_SIZE - 1) { - CPRINTF("C%d VDM over max size\n", port); - return; - } - - /* set VDM header with VID & CMD */ - pd[port].vdo_data[0] = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ? - 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), cmd); -#ifdef CONFIG_USB_PD_REV30 - pd[port].vdo_data[0] |= VDO_SVDM_VERS(vdo_ver[pd[port].rev]); -#endif - queue_vdm(port, pd[port].vdo_data, data, count, TCPCI_MSG_SOP); - - task_wake(PD_PORT_TO_TASK_ID(port)); -} - -static inline int pdo_busy(int port) -{ - /* - * Note, main PDO state machine (pd_task) uses READY state exclusively - * to denote port partners have successfully negociated a contract. All - * other protocol actions force state transitions. - */ - int rv = (pd[port].task_state != PD_STATE_SRC_READY); -#ifdef CONFIG_USB_PD_DUAL_ROLE - rv &= (pd[port].task_state != PD_STATE_SNK_READY); -#endif - return rv; -} - -static uint64_t vdm_get_ready_timeout(uint32_t vdm_hdr) -{ - uint64_t timeout; - int cmd = PD_VDO_CMD(vdm_hdr); - - /* its not a structured VDM command */ - if (!PD_VDO_SVDM(vdm_hdr)) - return 500*MSEC; - - switch (PD_VDO_CMDT(vdm_hdr)) { - case CMDT_INIT: - if ((cmd == CMD_ENTER_MODE) || (cmd == CMD_EXIT_MODE)) - timeout = PD_T_VDM_WAIT_MODE_E; - else - timeout = PD_T_VDM_SNDR_RSP; - break; - default: - if ((cmd == CMD_ENTER_MODE) || (cmd == CMD_EXIT_MODE)) - timeout = PD_T_VDM_E_MODE; - else - timeout = PD_T_VDM_RCVR_RSP; - break; - } - return timeout; -} - -static void exit_tbt_mode_sop_prime(int port) -{ - /* Exit Thunderbolt-Compatible mode SOP' */ - uint16_t header; - int opos; - - if (!IS_ENABLED(CONFIG_USB_PD_TBT_COMPAT_MODE)) - return; - - opos = pd_alt_mode(port, TCPCI_MSG_SOP, USB_VID_INTEL); - if (opos <= 0) - return; - - CPRINTS("C%d Cable exiting TBT Compat mode", port); - /* - * Note: TCPMv2 contemplates separate discovery structures for each SOP - * type. TCPMv1 only uses one discovery structure, so all accesses - * specify TCPCI_MSG_SOP. - */ - if (pd_dfp_exit_mode(port, TCPCI_MSG_SOP, USB_VID_INTEL, opos)) - usb_mux_set_safe_mode(port); - else - return; - - header = PD_HEADER(PD_DATA_VENDOR_DEF, pd[port].power_role, - pd[port].data_role, pd[port].msg_id, - (int)pd[port].vdo_count, - pd_get_rev(port, TCPCI_MSG_SOP), 0); - - pd[port].vdo_data[0] = VDO(USB_VID_INTEL, 1, - CMD_EXIT_MODE | VDO_OPOS(opos)); - - pd_transmit(port, TCPCI_MSG_SOP_PRIME, header, pd[port].vdo_data, - AMS_START); - - usb_mux_set(port, USB_PD_MUX_USB_ENABLED, USB_SWITCH_CONNECT, - polarity_rm_dts(pd_get_polarity(port))); -} - -static void pd_vdm_send_state_machine(int port) -{ - int res; - uint16_t header; - enum tcpci_msg_type msg_type = pd[port].xmit_type; - - switch (pd[port].vdm_state) { - case VDM_STATE_READY: - /* Only transmit VDM if connected. */ - if (!pd_is_connected(port)) { - pd[port].vdm_state = VDM_STATE_ERR_BUSY; - break; - } - - /* - * if there's traffic or we're not in PDO ready state don't send - * a VDM. - */ - if (pdo_busy(port)) - break; - - /* - * To communicate with the cable plug, an explicit contract - * should be established, VCONN should be enabled and data role - * that can communicate with the cable plug should be in place. - * For USB3.0, UFP/DFP can communicate whereas in case of - * USB2.0 only DFP can talk to the cable plug. - * - * For communication between USB2.0 UFP and cable plug, - * data role swap takes place during source and sink - * negotiation and in case of failure, a soft reset is issued. - */ - if ((msg_type == TCPCI_MSG_SOP_PRIME) || - (msg_type == TCPCI_MSG_SOP_PRIME_PRIME)) { - /* Prepare SOP'/SOP'' header and send VDM */ - header = PD_HEADER( - PD_DATA_VENDOR_DEF, - PD_PLUG_FROM_DFP_UFP, - 0, - pd[port].msg_id, - (int)pd[port].vdo_count, - pd_get_rev(port, TCPCI_MSG_SOP), - 0); - res = pd_transmit(port, msg_type, header, - pd[port].vdo_data, AMS_START); - /* - * In the case of SOP', if there is no response from - * the cable, it's a non-emark cable and therefore the - * pd flow should continue irrespective of cable - * response, sending discover_identity so the pd flow - * remains intact. - * - * In the case of SOP'', if there is no response from - * the cable, exit Thunderbolt-Compatible mode - * discovery, reset the mux state since, the mux will - * be set to a safe state before entering - * Thunderbolt-Compatible mode and enter the default - * mode. - */ - if (res < 0) { - header = PD_HEADER(PD_DATA_VENDOR_DEF, - pd[port].power_role, - pd[port].data_role, - pd[port].msg_id, - (int)pd[port].vdo_count, - pd_get_rev - (port, TCPCI_MSG_SOP), - 0); - - if ((msg_type == TCPCI_MSG_SOP_PRIME_PRIME) && - IS_ENABLED(CONFIG_USBC_SS_MUX)) { - exit_tbt_mode_sop_prime(port); - } else if (msg_type == TCPCI_MSG_SOP_PRIME) { - pd[port].vdo_data[0] = VDO(USB_SID_PD, - 1, CMD_DISCOVER_SVID); - } - res = pd_transmit(port, TCPCI_MSG_SOP, header, - pd[port].vdo_data, AMS_START); - reset_pd_cable(port); - } - } else { - /* Prepare SOP header and send VDM */ - header = PD_HEADER(PD_DATA_VENDOR_DEF, - pd[port].power_role, - pd[port].data_role, - pd[port].msg_id, - (int)pd[port].vdo_count, - pd_get_rev(port, TCPCI_MSG_SOP), 0); - res = pd_transmit(port, TCPCI_MSG_SOP, header, - pd[port].vdo_data, AMS_START); - } - - if (res < 0) { - pd[port].vdm_state = VDM_STATE_ERR_SEND; - } else { - pd[port].vdm_state = VDM_STATE_BUSY; - pd[port].vdm_timeout.val = get_time().val + - vdm_get_ready_timeout(pd[port].vdo_data[0]); - } - break; - case VDM_STATE_WAIT_RSP_BUSY: - /* wait and then initiate request again */ - if (get_time().val > pd[port].vdm_timeout.val) { - pd[port].vdo_data[0] = pd[port].vdo_retry; - pd[port].vdo_count = 1; - pd[port].vdm_state = VDM_STATE_READY; - } - break; - case VDM_STATE_BUSY: - /* Wait for VDM response or timeout */ - if (pd[port].vdm_timeout.val && - (get_time().val > pd[port].vdm_timeout.val)) { - pd[port].vdm_state = VDM_STATE_ERR_TMOUT; - } - break; - case VDM_STATE_ERR_SEND: - /* Sending the VDM failed, so try again. */ - CPRINTF("C%d VDMretry\n", port); - pd[port].vdm_state = VDM_STATE_READY; - break; - default: - break; - } -} - -#ifdef CONFIG_CMD_PD_DEV_DUMP_INFO -static inline void pd_dev_dump_info(uint16_t dev_id, uint8_t *hash) -{ - int j; - ccprintf("DevId:%d.%d Hash:", HW_DEV_ID_MAJ(dev_id), - HW_DEV_ID_MIN(dev_id)); - for (j = 0; j < PD_RW_HASH_SIZE; j += 4) { - ccprintf(" 0x%02x%02x%02x%02x", hash[j + 3], hash[j + 2], - hash[j + 1], hash[j]); - } - ccprintf("\n"); -} -#endif /* CONFIG_CMD_PD_DEV_DUMP_INFO */ - -int pd_dev_store_rw_hash(int port, uint16_t dev_id, uint32_t *rw_hash, - uint32_t current_image) -{ -#ifdef CONFIG_COMMON_RUNTIME - int i; -#endif - - pd[port].dev_id = dev_id; - memcpy(pd[port].dev_rw_hash, rw_hash, PD_RW_HASH_SIZE); -#ifdef CONFIG_CMD_PD_DEV_DUMP_INFO - if (debug_level >= 2) - pd_dev_dump_info(dev_id, (uint8_t *)rw_hash); -#endif - pd[port].current_image = current_image; - -#ifdef CONFIG_COMMON_RUNTIME - /* Search table for matching device / hash */ - for (i = 0; i < RW_HASH_ENTRIES; i++) - if (dev_id == rw_hash_table[i].dev_id) - return !memcmp(rw_hash, - rw_hash_table[i].dev_rw_hash, - PD_RW_HASH_SIZE); -#endif - return 0; -} - -void pd_dev_get_rw_hash(int port, uint16_t *dev_id, uint8_t *rw_hash, - uint32_t *current_image) -{ - *dev_id = pd[port].dev_id; - *current_image = pd[port].current_image; - if (*dev_id) - memcpy(rw_hash, pd[port].dev_rw_hash, PD_RW_HASH_SIZE); -} - -__maybe_unused static void exit_supported_alt_mode(int port) -{ - int i; - - if (!IS_ENABLED(CONFIG_USB_PD_ALT_MODE_DFP)) - return; - - for (i = 0; i < supported_modes_cnt; i++) { - int opos = pd_alt_mode(port, TCPCI_MSG_SOP, - supported_modes[i].svid); - - if (opos > 0 && pd_dfp_exit_mode(port, TCPCI_MSG_SOP, - supported_modes[i].svid, opos)) { - CPRINTS("C%d Exiting ALT mode with SVID = 0x%x", port, - supported_modes[i].svid); - usb_mux_set_safe_mode(port); - pd_send_vdm(port, supported_modes[i].svid, - CMD_EXIT_MODE | VDO_OPOS(opos), NULL, 0); - /* Wait for an ACK from port-partner */ - pd_vdm_send_state_machine(port); - } - } -} - -#ifdef CONFIG_POWER_COMMON -static void handle_new_power_state(int port) -{ - - if (chipset_in_or_transitioning_to_state(CHIPSET_STATE_ANY_OFF)) { - /* - * The SoC will negotiate the alternate mode again when - * it boots up. - */ - exit_supported_alt_mode(port); - } -#ifdef CONFIG_USBC_VCONN_SWAP - else { - /* Request for Vconn Swap */ - pd_try_vconn_src(port); - } -#endif - /* Ensure mux is set properly after chipset transition */ - set_usb_mux_with_current_data_role(port); -} -#endif /* CONFIG_POWER_COMMON */ - -#ifdef CONFIG_USB_PD_DUAL_ROLE -enum pd_dual_role_states pd_get_dual_role(int port) -{ - return drp_state[port]; -} - -#ifdef CONFIG_USB_PD_TRY_SRC -static void pd_update_try_source(void) -{ - int i; - - pd_try_src_enable = pd_is_try_source_capable(); - - /* - * Clear this flag to cover case where a TrySrc - * mode went from enabled to disabled and trying_source - * was active at that time. - */ - for (i = 0; i < board_get_usb_pd_port_count(); i++) - pd[i].flags &= ~PD_FLAGS_TRY_SRC; -} -#endif /* CONFIG_USB_PD_TRY_SRC */ - -#ifdef CONFIG_USB_PD_RESET_MIN_BATT_SOC -static void pd_update_snk_reset(void) -{ - int i; - int batt_soc = usb_get_battery_soc(); - - if (batt_soc < CONFIG_USB_PD_RESET_MIN_BATT_SOC || - battery_get_disconnect_state() != BATTERY_NOT_DISCONNECTED) - return; - - for (i = 0; i < board_get_usb_pd_port_count(); i++) { - if (pd[i].flags & PD_FLAGS_SNK_WAITING_BATT) { - /* - * Battery has gained sufficient charge to kick off PD - * negotiation and withstand a hard reset. Clear the - * flag and let reset begin if task is waiting in - * SNK_DISCOVERY. - */ - pd[i].flags &= ~PD_FLAGS_SNK_WAITING_BATT; - - if (pd[i].task_state == PD_STATE_SNK_DISCOVERY) { - CPRINTS("C%d: Starting soft reset timer", i); - set_state_timeout(i, - get_time().val + PD_T_SINK_WAIT_CAP, - PD_STATE_SOFT_RESET); - } - } - } -} -#endif - -#if defined(CONFIG_USB_PD_TRY_SRC) || defined(CONFIG_USB_PD_RESET_MIN_BATT_SOC) -static void pd_update_battery_soc_change(void) -{ -#ifdef CONFIG_USB_PD_TRY_SRC - pd_update_try_source(); -#endif - -#ifdef CONFIG_USB_PD_RESET_MIN_BATT_SOC - pd_update_snk_reset(); -#endif -} -DECLARE_HOOK(HOOK_BATTERY_SOC_CHANGE, pd_update_battery_soc_change, - HOOK_PRIO_DEFAULT); -#endif /* CONFIG_USB_PD_TRY_SRC || CONFIG_USB_PD_RESET_MIN_BATT_SOC */ - -static inline void pd_set_dual_role_no_wakeup(int port, - enum pd_dual_role_states state) -{ - drp_state[port] = state; - -#ifdef CONFIG_USB_PD_TRY_SRC - pd_update_try_source(); -#endif -} - -void pd_set_dual_role(int port, enum pd_dual_role_states state) -{ - pd_set_dual_role_no_wakeup(port, state); - - /* Wake task up to process change */ - task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_UPDATE_DUAL_ROLE); -} - -static int pd_is_power_swapping(int port) -{ - /* return true if in the act of swapping power roles */ - return pd[port].task_state == PD_STATE_SNK_SWAP_SNK_DISABLE || - pd[port].task_state == PD_STATE_SNK_SWAP_SRC_DISABLE || - pd[port].task_state == PD_STATE_SNK_SWAP_STANDBY || - pd[port].task_state == PD_STATE_SNK_SWAP_COMPLETE || - pd[port].task_state == PD_STATE_SRC_SWAP_SNK_DISABLE || - pd[port].task_state == PD_STATE_SRC_SWAP_SRC_DISABLE || - pd[port].task_state == PD_STATE_SRC_SWAP_STANDBY; -} - -/* This must only be called from the PD task */ -static void pd_update_dual_role_config(int port) -{ - /* - * 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). - */ - if (pd[port].power_role == PD_ROLE_SOURCE && - (drp_state[port] == PD_DRP_FORCE_SINK - || (drp_state[port] == PD_DRP_TOGGLE_OFF - && pd[port].task_state == PD_STATE_SRC_DISCONNECTED))) { - pd_set_power_role(port, PD_ROLE_SINK); - set_state(port, PD_STATE_SNK_DISCONNECTED); - tcpm_set_cc(port, TYPEC_CC_RD); - /* Make sure we're not sourcing VBUS. */ - pd_power_supply_reset(port); - } - - /* - * Change to source if port is currently a sink and the - * new DRP state is force source. If we are performing - * power swap we won't change anything because - * changing state will disrupt power swap process - * and we are power swapping to desired power role. - */ - if (pd[port].power_role == PD_ROLE_SINK && - drp_state[port] == PD_DRP_FORCE_SOURCE && - !pd_is_power_swapping(port)) { - pd_set_power_role(port, PD_ROLE_SOURCE); - set_state(port, PD_STATE_SRC_DISCONNECTED); - tcpm_set_cc(port, TYPEC_CC_RP); - } -} - -/* - * Provide Rp to ensure the partner port is in a known state (eg. not - * PD negotiated, not sourcing 20V). - */ -static void pd_partner_port_reset(int port) -{ - uint64_t timeout; - uint8_t flags; - - /* - * If there is no contract in place (or if we fail to read the BBRAM - * flags), there is no need to reset the partner. - */ - if (pd_get_saved_port_flags(port, &flags) != EC_SUCCESS || - !(flags & PD_BBRMFLG_EXPLICIT_CONTRACT)) - return; - - /* - * If we reach here, an explicit contract is in place. - * - * If PD communications are allowed, don't apply Rp. We'll issue a - * SoftReset later on and renegotiate our contract. This particular - * condition only applies to unlocked RO images with an explicit - * contract in place. - */ - if (pd_comm_is_enabled(port)) - return; - - /* If we just lost power, don't apply Rp. */ - if (system_get_reset_flags() & - (EC_RESET_FLAG_BROWNOUT | EC_RESET_FLAG_POWER_ON)) - return; - - /* - * Clear the active contract bit before we apply Rp in case we - * intentionally brown out because we cut off our only power supply. - */ - pd_update_saved_port_flags(port, PD_BBRMFLG_EXPLICIT_CONTRACT, 0); - - /* Provide Rp for 200 msec. or until we no longer have VBUS. */ - CPRINTF("C%d Apply Rp!\n", port); - cflush(); - tcpm_set_cc(port, TYPEC_CC_RP); - timeout = get_time().val + 200 * MSEC; - - while (get_time().val < timeout && pd_is_vbus_present(port)) - msleep(10); -} -#endif /* CONFIG_USB_PD_DUAL_ROLE */ - -enum pd_power_role pd_get_power_role(int port) -{ - return pd[port].power_role; -} - -enum pd_data_role pd_get_data_role(int port) -{ - return pd[port].data_role; -} - -enum pd_cc_states pd_get_task_cc_state(int port) -{ - return pd[port].cc_state; -} - -uint8_t pd_get_task_state(int port) -{ - return pd[port].task_state; -} - -#ifdef CONFIG_USB_PD_DUAL_ROLE -uint32_t pd_get_requested_voltage(int port) -{ - return pd[port].supply_voltage; -} - -uint32_t pd_get_requested_current(int port) -{ - return pd[port].curr_limit; -} -#endif - -const char *pd_get_task_state_name(int port) -{ -#ifdef CONFIG_USB_PD_TCPMV1_DEBUG - if (debug_level > 0) - return pd_state_names[pd[port].task_state]; -#endif - return ""; -} - -bool pd_get_vconn_state(int port) -{ - return !!(pd[port].flags & PD_FLAGS_VCONN_ON); -} - -bool pd_get_partner_dual_role_power(int port) -{ - return !!(pd[port].flags & PD_FLAGS_PARTNER_DR_POWER); -} - -bool pd_get_partner_unconstr_power(int port) -{ - return !!(pd[port].flags & PD_FLAGS_PARTNER_UNCONSTR); -} - -enum tcpc_cc_polarity pd_get_polarity(int port) -{ - return pd[port].polarity; -} - -bool pd_get_partner_data_swap_capable(int port) -{ - /* return data swap capable status of port partner */ - return !!(pd[port].flags & PD_FLAGS_PARTNER_DR_DATA); -} - -#ifdef CONFIG_COMMON_RUNTIME -void pd_comm_enable(int port, int enable) -{ - /* We don't check port >= CONFIG_USB_PD_PORT_MAX_COUNT deliberately */ - pd_comm_enabled[port] = enable; - - /* If type-C connection, then update the TCPC RX enable */ - if (pd_is_connected(port)) - tcpm_set_rx_enable(port, enable); - -#ifdef CONFIG_USB_PD_DUAL_ROLE - /* - * If communications are enabled, start hard reset timer for - * any port in PD_SNK_DISCOVERY. - */ - if (enable && pd[port].task_state == PD_STATE_SNK_DISCOVERY) - set_state_timeout(port, - get_time().val + PD_T_SINK_WAIT_CAP, - PD_STATE_HARD_RESET_SEND); -#endif -} -#endif - -void pd_ping_enable(int port, int enable) -{ - if (enable) - pd[port].flags |= PD_FLAGS_PING_ENABLED; - else - pd[port].flags &= ~PD_FLAGS_PING_ENABLED; -} - -__overridable uint8_t board_get_src_dts_polarity(int port) -{ - /* - * If the port in SRC DTS, the polarity is determined by the board, - * i.e. what Rp impedance the CC lines are pulled. If this function - * is not overridden, assume CC1 is primary. - */ - return 0; -} - -#if defined(CONFIG_CHARGE_MANAGER) - -/** - * Signal power request to indicate a charger update that affects the port. - */ -void pd_set_new_power_request(int port) -{ - pd[port].new_power_request = 1; - task_wake(PD_PORT_TO_TASK_ID(port)); -} -#endif /* CONFIG_CHARGE_MANAGER */ - -#if defined(CONFIG_USBC_BACKWARDS_COMPATIBLE_DFP) && defined(CONFIG_USBC_SS_MUX) -/* - * Backwards compatible DFP does not support USB SS because it applies VBUS - * before debouncing CC and setting USB SS muxes, but SS detection will fail - * before we are done debouncing CC. - */ -#error "Backwards compatible DFP does not support USB" -#endif - -#ifdef CONFIG_COMMON_RUNTIME - -/* Initialize globals based on system state. */ -static void pd_init_tasks(void) -{ - static int initialized; - int enable = 1; - int i; - - /* Initialize globals once, for all PD tasks. */ - if (initialized) - return; - -#if defined(HAS_TASK_CHIPSET) && defined(CONFIG_USB_PD_DUAL_ROLE) - /* Set dual-role state based on chipset power state */ - if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) - for (i = 0; i < board_get_usb_pd_port_count(); i++) - drp_state[i] = PD_DRP_FORCE_SINK; - else if (chipset_in_state(CHIPSET_STATE_ANY_SUSPEND)) - for (i = 0; i < board_get_usb_pd_port_count(); i++) - drp_state[i] = PD_DRP_TOGGLE_OFF; - else /* CHIPSET_STATE_ON */ - for (i = 0; i < board_get_usb_pd_port_count(); i++) - drp_state[i] = PD_DRP_TOGGLE_ON; -#endif - -#if defined(CONFIG_USB_PD_COMM_DISABLED) - enable = 0; -#elif defined(CONFIG_USB_PD_COMM_LOCKED) - /* Disable PD communication if we're in RO, WP is enabled, and EFS - * didn't register NO_BOOT. */ - if (!system_is_in_rw() && system_is_locked() && !vboot_allow_usb_pd()) - enable = 0; -#endif - for (i = 0; i < board_get_usb_pd_port_count(); i++) - pd_comm_enabled[i] = enable; - CPRINTS("PD comm %sabled", enable ? "en" : "dis"); - - initialized = 1; -} -#endif /* CONFIG_COMMON_RUNTIME */ - -#if !defined(CONFIG_USB_PD_TCPC) && defined(CONFIG_USB_PD_DUAL_ROLE) -static int pd_restart_tcpc(int port) -{ - if (board_set_tcpc_power_mode) { - /* force chip reset */ - board_set_tcpc_power_mode(port, 0); - } - return tcpm_init(port); -} -#endif - -static void pd_send_enter_usb(int port, int *timeout) -{ - uint32_t usb4_payload; - uint16_t header; - int res; - - /* - * TODO: Enable Enter USB for cables (SOP'). - * This is needed for active cables - */ - if (!IS_ENABLED(CONFIG_USBC_SS_MUX) || - !IS_ENABLED(CONFIG_USB_PD_USB4) || - !IS_ENABLED(CONFIG_USB_PD_ALT_MODE_DFP)) - return; - - usb4_payload = get_enter_usb_msg_payload(port); - - header = PD_HEADER(PD_DATA_ENTER_USB, - pd[port].power_role, - pd[port].data_role, - pd[port].msg_id, - 1, - PD_REV30, - 0); - - res = pd_transmit(port, TCPCI_MSG_SOP, header, &usb4_payload, - AMS_START); - if (res < 0) { - *timeout = 10*MSEC; - /* - * If failed to get goodCRC, send soft reset, otherwise ignore - * failure. - */ - set_state(port, res == -1 ? - PD_STATE_SOFT_RESET : - READY_RETURN_STATE(port)); - return; - } - - /* Disable Enter USB4 mode prevent re-entry */ - disable_enter_usb4_mode(port); - - set_state(port, PD_STATE_ENTER_USB); -} - -void pd_task(void *u) -{ - uint32_t head; - int port = TASK_ID_TO_PD_PORT(task_get_current()); - uint32_t payload[7]; - int timeout = 10*MSEC; - enum tcpc_cc_voltage_status cc1, cc2; - int res, incoming_packet = 0; - int hard_reset_count = 0; -#ifdef CONFIG_USB_PD_DUAL_ROLE - uint64_t next_role_swap = PD_T_DRP_SNK; - uint8_t saved_flgs = 0; -#ifndef CONFIG_USB_PD_VBUS_DETECT_NONE - int snk_hard_reset_vbus_off = 0; -#endif -#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE - const int auto_toggle_supported = tcpm_auto_toggle_supported(port); -#endif -#if defined(CONFIG_CHARGE_MANAGER) - typec_current_t typec_curr = 0, typec_curr_change = 0; -#endif /* CONFIG_CHARGE_MANAGER */ -#endif /* CONFIG_USB_PD_DUAL_ROLE */ - enum pd_states this_state; - enum pd_cc_states new_cc_state; - timestamp_t now; - uint64_t next_src_cap = 0; - int caps_count = 0, hard_reset_sent = 0; - int snk_cap_count = 0; - int evt; - -#ifdef CONFIG_USB_PD_TCPC_LOW_POWER - /* - * Set the ports in Low Power Mode so that other tasks wait until - * TCPC is initialized and ready. - */ - pd[port].flags |= PD_FLAGS_LPM_ENGAGED; -#endif - -#ifdef CONFIG_COMMON_RUNTIME - pd_init_tasks(); -#endif - - /* - * Ensure the power supply is in the default state and ensure we are not - * sourcing Vconn - */ - pd_power_supply_reset(port); -#ifdef CONFIG_USBC_VCONN -#ifdef CONFIG_USB_PD_DUAL_ROLE - /* - * If we were previously a sink but also the VCONN source, we should - * still continue to source VCONN. Otherwise, we should turn off VCONN - * since we are also going to turn off VBUS. - */ - if (pd_comm_is_enabled(port) && - (pd_get_saved_port_flags(port, &saved_flgs) == EC_SUCCESS) && - ((saved_flgs & PD_BBRMFLG_POWER_ROLE) == PD_ROLE_SINK) && - (saved_flgs & PD_BBRMFLG_EXPLICIT_CONTRACT) && - (saved_flgs & PD_BBRMFLG_VCONN_ROLE)) - set_vconn(port, 1); - else -#endif - set_vconn(port, 0); -#endif - -#ifdef CONFIG_USB_PD_TCPC_BOARD_INIT - /* Board specific TCPC init */ - board_tcpc_init(); -#endif - - /* Initialize TCPM driver and wait for TCPC to be ready */ - res = reset_device_and_notify(port); - invalidate_last_message_id(port); - -#ifdef CONFIG_USB_PD_DUAL_ROLE - pd_partner_port_reset(port); -#endif - - this_state = res ? PD_STATE_SUSPENDED : PD_DEFAULT_STATE(port); -#ifndef CONFIG_USB_PD_TCPC - if (!res) { - struct ec_response_pd_chip_info_v1 info; - - if (tcpm_get_chip_info(port, 0, &info) == - EC_SUCCESS) { - CPRINTS("TCPC p%d VID:0x%x PID:0x%x DID:0x%x " - "FWV:0x%" PRIx64, - port, info.vendor_id, info.product_id, - info.device_id, info.fw_version_number); - } - } -#endif - -#ifdef CONFIG_USB_PD_REV30 - /* Set Revision to highest */ - pd[port].rev = PD_REV30; -#endif - -#ifdef CONFIG_USB_PD_DUAL_ROLE - /* - * If VBUS is high, then initialize flag for VBUS has always been - * present. This flag is used to maintain a PD connection after a - * reset by sending a soft reset. - */ - pd[port].flags |= - pd_is_vbus_present(port) ? PD_FLAGS_VBUS_NEVER_LOW : 0; -#endif - - /* Disable TCPC RX until connection is established */ - tcpm_set_rx_enable(port, 0); - -#ifdef CONFIG_USBC_SS_MUX - /* Initialize USB mux to its default state */ - usb_mux_init(port); -#endif - -#ifdef CONFIG_USB_PD_DUAL_ROLE - /* - * If there's an explicit contract in place, let's restore the data and - * power roles such that any messages we send to the port partner will - * still be valid. - */ - if (pd_comm_is_enabled(port) && - (pd_get_saved_port_flags(port, &saved_flgs) == EC_SUCCESS) && - (saved_flgs & PD_BBRMFLG_EXPLICIT_CONTRACT)) { - /* Only attempt to maintain previous sink contracts */ - if ((saved_flgs & PD_BBRMFLG_POWER_ROLE) == PD_ROLE_SINK) { - pd_set_power_role(port, - (saved_flgs & PD_BBRMFLG_POWER_ROLE) ? - PD_ROLE_SOURCE : PD_ROLE_SINK); - pd_set_data_role(port, - (saved_flgs & PD_BBRMFLG_DATA_ROLE) ? - PD_ROLE_DFP : PD_ROLE_UFP); -#ifdef CONFIG_USBC_VCONN - pd_set_vconn_role(port, - (saved_flgs & PD_BBRMFLG_VCONN_ROLE) ? - PD_ROLE_VCONN_ON : PD_ROLE_VCONN_OFF); -#endif /* CONFIG_USBC_VCONN */ - - /* - * Since there is an explicit contract in place, let's - * issue a SoftReset such that we can renegotiate with - * our port partner in order to synchronize our state - * machines. - */ - this_state = PD_STATE_SOFT_RESET; - - /* - * Re-discover any alternate modes we may have been - * using with this port partner. - */ - pd[port].flags |= PD_FLAGS_CHECK_IDENTITY; - } else { - /* - * Vbus was turned off during the power supply reset - * earlier, so clear the contract flag and re-start as - * default role - */ - pd_update_saved_port_flags(port, - PD_BBRMFLG_EXPLICIT_CONTRACT, 0); - - } - /* - * Set the TCPC reset event such that we can set our CC - * terminations, determine polarity, and enable RX so we - * can hear back from our port partner if maintaining our old - * connection. - */ - task_set_event(task_get_current(), PD_EVENT_TCPC_RESET); - } -#endif /* defined(CONFIG_USB_PD_DUAL_ROLE) */ - /* Set the power role if we haven't already. */ - if (this_state != PD_STATE_SOFT_RESET) - pd_set_power_role(port, PD_ROLE_DEFAULT(port)); - - /* Initialize PD protocol state variables for each port. */ - pd[port].vdm_state = VDM_STATE_DONE; - set_state(port, this_state); - tcpm_select_rp_value(port, CONFIG_USB_PD_PULLUP); -#ifdef CONFIG_USB_PD_DUAL_ROLE - /* - * If we're not in an explicit contract, set our terminations to match - * our default power role. - */ - if (!(saved_flgs & PD_BBRMFLG_EXPLICIT_CONTRACT)) -#endif /* CONFIG_USB_PD_DUAL_ROLE */ - tcpm_set_cc(port, PD_ROLE_DEFAULT(port) == PD_ROLE_SOURCE ? - TYPEC_CC_RP : TYPEC_CC_RD); - -#ifdef CONFIG_USBC_PPC - /* - * Wait to initialize the PPC after setting the correct Rd values in - * the TCPC 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); -#endif - -#ifdef CONFIG_USB_PD_ALT_MODE_DFP - /* Initialize PD Policy engine */ - pd_dfp_discovery_init(port); - pd_dfp_mode_init(port); -#endif - -#ifdef CONFIG_CHARGE_MANAGER - /* Initialize PD and type-C supplier current limits to 0 */ - pd_set_input_current_limit(port, 0, 0); - typec_set_input_current_limit(port, 0, 0); - charge_manager_update_dualrole(port, CAP_UNKNOWN); -#endif - - /* - * Since most boards configure the TCPC interrupt as edge - * and it is possible that the interrupt line was asserted between init - * and calling set_state, we need to process any pending interrupts now. - * Otherwise future interrupts will never fire because another edge - * never happens. Note this needs to happen after set_state() is called. - */ - if (IS_ENABLED(CONFIG_HAS_TASK_PD_INT)) - schedule_deferred_pd_interrupt(port); - - while (1) { - /* process VDM messages last */ - pd_vdm_send_state_machine(port); - - /* Verify board specific health status : current, voltages... */ - res = pd_board_checks(); - if (res != EC_SUCCESS) { - /* cut the power */ - pd_execute_hard_reset(port); - /* notify the other side of the issue */ - pd_transmit(port, TCPCI_MSG_TX_HARD_RESET, 0, NULL, - AMS_START); - } - - /* wait for next event/packet or timeout expiration */ - evt = task_wait_event(timeout); - -#ifdef CONFIG_USB_PD_TCPC_LOW_POWER - if (evt & (PD_EXIT_LOW_POWER_EVENT_MASK | TASK_EVENT_WAKE)) - exit_low_power_mode(port); - if (evt & PD_EVENT_DEVICE_ACCESSED) - handle_device_access(port); -#endif -#ifdef CONFIG_POWER_COMMON - if (evt & PD_EVENT_POWER_STATE_CHANGE) - handle_new_power_state(port); -#endif - -#if defined(CONFIG_USB_PD_ALT_MODE_DFP) - if (evt & PD_EVENT_SYSJUMP) { - exit_supported_alt_mode(port); - notify_sysjump_ready(); - } -#endif - -#ifdef CONFIG_USB_PD_DUAL_ROLE - if (evt & PD_EVENT_UPDATE_DUAL_ROLE) - pd_update_dual_role_config(port); -#endif - -#ifdef CONFIG_USB_PD_TCPC - /* - * run port controller task to check CC and/or read incoming - * messages - */ - tcpc_run(port, evt); -#else - /* if TCPC has reset, then need to initialize it again */ - if (evt & PD_EVENT_TCPC_RESET) { - reset_device_and_notify(port); -#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE - } - - if ((evt & PD_EVENT_TCPC_RESET) && - (pd[port].task_state != PD_STATE_DRP_AUTO_TOGGLE)) { -#endif -#ifdef CONFIG_USB_PD_DUAL_ROLE - if (pd[port].task_state == PD_STATE_SOFT_RESET) { - enum tcpc_cc_voltage_status cc1, cc2; - - /* - * Set the terminations to match our power - * role. - */ - tcpm_set_cc(port, pd[port].power_role ? - TYPEC_CC_RP : TYPEC_CC_RD); - - /* Determine the polarity. */ - tcpm_get_cc(port, &cc1, &cc2); - if (pd[port].power_role == PD_ROLE_SINK) { - pd[port].polarity = - get_snk_polarity(cc1, cc2); - } else if (cc_is_snk_dbg_acc(cc1, cc2)) { - pd[port].polarity = - board_get_src_dts_polarity( - port); - } else { - pd[port].polarity = - get_src_polarity(cc1, cc2); - } - } else -#endif /* CONFIG_USB_PD_DUAL_ROLE */ - { - /* Ensure CC termination is default */ - tcpm_set_cc(port, PD_ROLE_DEFAULT(port) == - PD_ROLE_SOURCE ? TYPEC_CC_RP : - TYPEC_CC_RD); - } - - /* - * If we have a stable contract in the default role, - * then simply update TCPC with some missing info - * so that we can continue without resetting PD comms. - * Otherwise, go to the default disconnected state - * and force renegotiation. - */ - if (pd[port].vdm_state == VDM_STATE_DONE && ( -#ifdef CONFIG_USB_PD_DUAL_ROLE - (PD_ROLE_DEFAULT(port) == PD_ROLE_SINK && - pd[port].task_state == PD_STATE_SNK_READY) || - (pd[port].task_state == PD_STATE_SOFT_RESET) || -#endif - (PD_ROLE_DEFAULT(port) == PD_ROLE_SOURCE && - pd[port].task_state == PD_STATE_SRC_READY))) { - pd_set_polarity(port, pd[port].polarity); - tcpm_set_msg_header(port, pd[port].power_role, - pd[port].data_role); - tcpm_set_rx_enable(port, 1); - } else { - /* Ensure state variables are at default */ - pd_set_power_role(port, PD_ROLE_DEFAULT(port)); - pd[port].vdm_state = VDM_STATE_DONE; - set_state(port, PD_DEFAULT_STATE(port)); -#ifdef CONFIG_USB_PD_DUAL_ROLE - pd_update_dual_role_config(port); -#endif - } - } -#endif - -#ifdef CONFIG_USBC_PPC - /* - * TODO: Useful for non-PPC cases as well, but only needed - * for PPC cases right now. Revisit later. - */ - if (evt & PD_EVENT_SEND_HARD_RESET) - set_state(port, PD_STATE_HARD_RESET_SEND); -#endif /* defined(CONFIG_USBC_PPC) */ - - if (evt & PD_EVENT_RX_HARD_RESET) - pd_execute_hard_reset(port); - - /* process any potential incoming message */ - incoming_packet = tcpm_has_pending_message(port); - if (incoming_packet) { - /* Dequeue and consume duplicate message ID. */ - if (tcpm_dequeue_message(port, payload, &head) == - EC_SUCCESS - && !consume_repeat_message(port, head) - ) - handle_request(port, head, payload); - - /* Check if there are any more messages */ - if (tcpm_has_pending_message(port)) - task_set_event(PD_PORT_TO_TASK_ID(port), - TASK_EVENT_WAKE); - } - - if (pd[port].req_suspend_state) - set_state(port, PD_STATE_SUSPENDED); - - /* if nothing to do, verify the state of the world in 500ms */ - this_state = pd[port].task_state; - timeout = 500*MSEC; - switch (this_state) { - case PD_STATE_DISABLED: - /* Nothing to do */ - break; - case PD_STATE_SRC_DISCONNECTED: - timeout = 10*MSEC; - pd_set_src_caps(port, 0, NULL); -#ifdef CONFIG_USB_PD_TCPC_LOW_POWER - /* - * If SW decided we should be in a low power state and - * the CC lines did not change, then don't talk with the - * TCPC otherwise we might wake it up. - */ - if (pd[port].flags & PD_FLAGS_LPM_REQUESTED && - !(evt & PD_EVENT_CC)) - break; -#endif /* CONFIG_USB_PD_TCPC_LOW_POWER */ - - tcpm_get_cc(port, &cc1, &cc2); - -#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE - /* - * Attempt TCPC auto DRP toggle if it is - * not already auto toggling and not try.src - */ - if (auto_toggle_supported && - !(pd[port].flags & PD_FLAGS_TCPC_DRP_TOGGLE) && - !is_try_src(port) && - cc_is_open(cc1, cc2)) { - set_state(port, PD_STATE_DRP_AUTO_TOGGLE); - timeout = 2*MSEC; - break; - } -#endif - /* - * Transition to DEBOUNCE if we detect appropriate - * signals - * - * (from 4.5.2.2.10.2 Exiting from Try.SRC State) - * If try_src -and- - * have only one Rd (not both) => DEBOUNCE - * - * (from 4.5.2.2.7.2 Exiting from Unattached.SRC State) - * If not try_src -and- - * have at least one Rd => DEBOUNCE -or- - * have audio access => DEBOUNCE - * - * try_src should not exit if both pins are Rd - */ - if ((is_try_src(port) && cc_is_only_one_rd(cc1, cc2)) || - (!is_try_src(port) && - (cc_is_at_least_one_rd(cc1, cc2) || - cc_is_audio_acc(cc1, cc2)))) { -#ifdef CONFIG_USBC_BACKWARDS_COMPATIBLE_DFP - /* Enable VBUS */ - if (pd_set_power_supply_ready(port)) - break; -#endif - pd[port].cc_state = PD_CC_NONE; - set_state(port, - PD_STATE_SRC_DISCONNECTED_DEBOUNCE); - break; - } -#if defined(CONFIG_USB_PD_DUAL_ROLE) - now = get_time(); - /* - * Try.SRC state is embedded here. The port - * shall transition to TryWait.SNK after - * tDRPTry (PD_T_DRP_TRY) and Vbus is within - * vSafe0V, or after tTryTimeout - * (PD_T_TRY_TIMEOUT). Otherwise we should stay - * within Try.SRC (break). - */ - if (is_try_src(port)) { - if (now.val < pd[port].try_src_marker) { - break; - } else if (now.val < pd[port].try_timeout) { - if (pd_is_vbus_present(port)) - break; - } - - /* - * Transition to TryWait.SNK now, so set - * state and update src marker time. - */ - set_state(port, PD_STATE_SNK_DISCONNECTED); - pd_set_power_role(port, PD_ROLE_SINK); - tcpm_set_cc(port, TYPEC_CC_RD); - pd[port].try_src_marker = - get_time().val + PD_T_DEBOUNCE; - timeout = 2 * MSEC; - break; - } - - /* - * If Try.SRC state is not active, then handle - * the normal DRP toggle from SRC->SNK. - */ - if (now.val < next_role_swap || - drp_state[port] == PD_DRP_FORCE_SOURCE || - drp_state[port] == PD_DRP_FREEZE) - break; - - /* - * Transition to SNK now, so set state and - * update next role swap time. - */ - set_state(port, PD_STATE_SNK_DISCONNECTED); - pd_set_power_role(port, PD_ROLE_SINK); - tcpm_set_cc(port, TYPEC_CC_RD); - next_role_swap = get_time().val + PD_T_DRP_SNK; - /* Swap states quickly */ - timeout = 2 * MSEC; -#endif - break; - case PD_STATE_SRC_DISCONNECTED_DEBOUNCE: - timeout = 20*MSEC; - tcpm_get_cc(port, &cc1, &cc2); - - if (cc_is_snk_dbg_acc(cc1, cc2)) { - /* Debug accessory */ - new_cc_state = PD_CC_UFP_DEBUG_ACC; - } else if (cc_is_at_least_one_rd(cc1, cc2)) { - /* UFP attached */ - new_cc_state = PD_CC_UFP_ATTACHED; - } else if (cc_is_audio_acc(cc1, cc2)) { - /* Audio accessory */ - new_cc_state = PD_CC_UFP_AUDIO_ACC; - } else { - /* No UFP */ - set_state(port, PD_STATE_SRC_DISCONNECTED); - timeout = 5*MSEC; - break; - } - - /* Set debounce timer */ - if (new_cc_state != pd[port].cc_state) { - pd[port].cc_debounce = - get_time().val + - (is_try_src(port) ? PD_T_DEBOUNCE - : PD_T_CC_DEBOUNCE); - pd[port].cc_state = new_cc_state; - break; - } - - /* Debounce the cc state */ - if (get_time().val < pd[port].cc_debounce) - break; - - /* Debounce complete */ - if (IS_ENABLED(CONFIG_COMMON_RUNTIME)) - hook_notify(HOOK_USB_PD_CONNECT); - -#ifdef CONFIG_USBC_PPC - /* - * If the port is latched off, just continue to - * monitor for a detach. - */ - if (usbc_ocp_is_port_latched_off(port)) - break; -#endif /* CONFIG_USBC_PPC */ - - /* UFP is attached */ - if (new_cc_state == PD_CC_UFP_ATTACHED || - new_cc_state == PD_CC_UFP_DEBUG_ACC) { -#ifdef CONFIG_USBC_PPC - /* Inform PPC that a sink is connected. */ - ppc_dev_is_connected(port, PPC_DEV_SNK); -#endif /* CONFIG_USBC_PPC */ - if (IS_ENABLED(CONFIG_USBC_OCP)) - usbc_ocp_snk_is_connected(port, true); - if (new_cc_state == PD_CC_UFP_DEBUG_ACC) { - pd[port].polarity = - board_get_src_dts_polarity( - port); - } else { - pd[port].polarity = - get_src_polarity(cc1, cc2); - } - pd_set_polarity(port, pd[port].polarity); - - /* initial data role for source is DFP */ - pd_set_data_role(port, PD_ROLE_DFP); - - /* Enable Auto Discharge Disconnect */ - tcpm_enable_auto_discharge_disconnect(port, 1); - - if (new_cc_state == PD_CC_UFP_DEBUG_ACC) - pd[port].flags |= - PD_FLAGS_TS_DTS_PARTNER; - -#ifdef CONFIG_USBC_VCONN - /* - * Do not source Vconn when debug accessory is - * detected. Section 4.5.2.2.17.1 in USB spec - * v1-3 - */ - if (new_cc_state != PD_CC_UFP_DEBUG_ACC) { - /* - * Start sourcing Vconn before Vbus to - * ensure we are within USB Type-C - * Spec 1.3 tVconnON. - */ - set_vconn(port, 1); - pd_set_vconn_role(port, - PD_ROLE_VCONN_ON); - } -#endif - -#ifndef CONFIG_USBC_BACKWARDS_COMPATIBLE_DFP - /* Enable VBUS */ - if (pd_set_power_supply_ready(port)) { -#ifdef CONFIG_USBC_VCONN - /* Stop sourcing Vconn if Vbus failed */ - set_vconn(port, 0); - pd_set_vconn_role(port, - PD_ROLE_VCONN_OFF); -#endif /* CONFIG_USBC_VCONN */ -#ifdef CONFIG_USBC_SS_MUX - usb_mux_set(port, USB_PD_MUX_NONE, - USB_SWITCH_DISCONNECT, - pd[port].polarity); -#endif /* CONFIG_USBC_SS_MUX */ - break; - } - /* - * Set correct Rp value determined during - * pd_set_power_supply_ready. This should be - * safe because Vconn is being sourced, - * preventing incorrect CCD detection. - */ - tcpm_set_cc(port, TYPEC_CC_RP); -#endif /* CONFIG_USBC_BACKWARDS_COMPATIBLE_DFP */ - /* If PD comm is enabled, enable TCPC RX */ - if (pd_comm_is_enabled(port)) - tcpm_set_rx_enable(port, 1); - - pd[port].flags |= PD_FLAGS_CHECK_PR_ROLE | - PD_FLAGS_CHECK_DR_ROLE; - hard_reset_count = 0; - timeout = 5*MSEC; - - set_state(port, PD_STATE_SRC_STARTUP); - } - /* - * AUDIO_ACC will remain in this state indefinitely - * until disconnect. - */ - break; - case PD_STATE_SRC_HARD_RESET_RECOVER: - /* Do not continue until hard reset recovery time */ - if (get_time().val < pd[port].src_recover) { - timeout = 50*MSEC; - break; - } - -#ifdef CONFIG_USBC_VCONN - /* - * Start sourcing Vconn again and set the flag, in case - * it was 0 due to a previous swap - */ - set_vconn(port, 1); - pd_set_vconn_role(port, PD_ROLE_VCONN_ON); -#endif - - /* Enable VBUS */ - timeout = 10*MSEC; - if (pd_set_power_supply_ready(port)) { - set_state(port, PD_STATE_SRC_DISCONNECTED); - break; - } -#if defined(CONFIG_USB_PD_TCPM_TCPCI) || defined(CONFIG_USB_PD_TCPM_STUB) - /* - * After transmitting hard reset, TCPM writes - * to RECEIVE_DETECT register to enable - * PD message passing. - */ - if (pd_comm_is_enabled(port)) - tcpm_set_rx_enable(port, 1); -#endif /* CONFIG_USB_PD_TCPM_TCPCI || CONFIG_USB_PD_TCPM_STUB */ - - pd[port].flags |= PD_FLAGS_CHECK_PR_ROLE | - PD_FLAGS_CHECK_DR_ROLE; - set_state(port, PD_STATE_SRC_STARTUP); - break; - case PD_STATE_SRC_STARTUP: - /* Reset cable attributes and flags */ - reset_pd_cable(port); - /* Wait for power source to enable */ - if (pd[port].last_state != pd[port].task_state) { - pd[port].flags |= PD_FLAGS_CHECK_IDENTITY; - /* reset various counters */ - caps_count = 0; - pd[port].msg_id = 0; - snk_cap_count = 0; - set_state_timeout( - port, -#ifdef CONFIG_USBC_BACKWARDS_COMPATIBLE_DFP - /* - * delay for power supply to start up. - * subtract out debounce time if coming - * from debounce state since vbus is - * on during debounce. - */ - get_time().val + - PD_POWER_SUPPLY_TURN_ON_DELAY - - (pd[port].last_state == - PD_STATE_SRC_DISCONNECTED_DEBOUNCE - ? PD_T_CC_DEBOUNCE : 0), -#else - get_time().val + - PD_POWER_SUPPLY_TURN_ON_DELAY, -#endif - PD_STATE_SRC_DISCOVERY); - } - break; - case PD_STATE_SRC_DISCOVERY: - now = get_time(); - if (pd[port].last_state != pd[port].task_state) { - caps_count = 0; - next_src_cap = now.val; - /* - * If we have had PD connection with this port - * partner, then start NoResponseTimer. - */ - if (pd_capable(port)) - set_state_timeout(port, - get_time().val + - PD_T_NO_RESPONSE, - hard_reset_count < - PD_HARD_RESET_COUNT ? - PD_STATE_HARD_RESET_SEND : - PD_STATE_SRC_DISCONNECTED); - } - - /* Send source cap some minimum number of times */ - if (caps_count < PD_CAPS_COUNT && - next_src_cap <= now.val) { - /* Query capabilities of the other side */ - res = send_source_cap(port, AMS_START); - /* packet was acked => PD capable device) */ - if (res >= 0) { - set_state(port, - PD_STATE_SRC_NEGOCIATE); - timeout = 10*MSEC; - hard_reset_count = 0; - caps_count = 0; - /* Port partner is PD capable */ - pd[port].flags |= - PD_FLAGS_PREVIOUS_PD_CONN; - } else { /* failed, retry later */ - timeout = PD_T_SEND_SOURCE_CAP; - next_src_cap = now.val + - PD_T_SEND_SOURCE_CAP; - caps_count++; - } - } else if (caps_count < PD_CAPS_COUNT) { - timeout = next_src_cap - now.val; - } - break; - case PD_STATE_SRC_NEGOCIATE: - /* wait for a "Request" message */ - if (pd[port].last_state != pd[port].task_state) - set_state_timeout(port, - get_time().val + - PD_T_SENDER_RESPONSE, - PD_STATE_HARD_RESET_SEND); - break; - case PD_STATE_SRC_ACCEPTED: - /* Accept sent, wait for enabling the new voltage */ - if (pd[port].last_state != pd[port].task_state) - set_state_timeout( - port, - get_time().val + - PD_T_SINK_TRANSITION, - PD_STATE_SRC_POWERED); - break; - case PD_STATE_SRC_POWERED: - /* Switch to the new requested voltage */ - if (pd[port].last_state != pd[port].task_state) { - pd[port].flags |= PD_FLAGS_CHECK_VCONN_STATE; - pd_transition_voltage(pd[port].requested_idx); - set_state_timeout( - port, - get_time().val + - PD_POWER_SUPPLY_TURN_ON_DELAY, - PD_STATE_SRC_TRANSITION); - } - break; - case PD_STATE_SRC_TRANSITION: - /* the voltage output is good, notify the source */ - res = send_control(port, PD_CTRL_PS_RDY); - if (res >= 0) { - timeout = 10*MSEC; - - /* - * Give the sink some time to send any messages - * before we may send messages of our own. Add - * some jitter of up to ~192ms, to prevent - * multiple collisions. This delay also allows - * the sink device to request power role swap - * and allow the the accept message to be sent - * prior to CMD_DISCOVER_IDENT being sent in the - * SRC_READY state. - */ - pd[port].ready_state_holdoff_timer = - get_time().val + SRC_READY_HOLD_OFF_US - + (get_time().le.lo & 0xf) * 12 * MSEC; - - /* it's time to ping regularly the sink */ - set_state(port, PD_STATE_SRC_READY); - } else { - /* The sink did not ack, cut the power... */ - set_state(port, PD_STATE_SRC_DISCONNECTED); - } - break; - case PD_STATE_SRC_READY: - timeout = PD_T_SOURCE_ACTIVITY; - - /* - * Don't send any traffic yet until our holdoff timer - * has expired. Some devices are chatty once we reach - * the SRC_READY state and we may end up in a collision - * of messages if we try to immediately send our - * interrogations. - */ - if (get_time().val <= - pd[port].ready_state_holdoff_timer) - break; - - /* - * Don't send any PD traffic if we woke up due to - * incoming packet or if VDO response pending to avoid - * collisions. - */ - if (incoming_packet || - (pd[port].vdm_state == VDM_STATE_BUSY)) - break; - - /* Send updated source capabilities to our partner */ - if (pd[port].flags & PD_FLAGS_UPDATE_SRC_CAPS) { - res = send_source_cap(port, AMS_START); - if (res >= 0) { - set_state(port, - PD_STATE_SRC_NEGOCIATE); - pd[port].flags &= - ~PD_FLAGS_UPDATE_SRC_CAPS; - } - break; - } - - /* Send get sink cap if haven't received it yet */ - if (!(pd[port].flags & PD_FLAGS_SNK_CAP_RECVD)) { - if (++snk_cap_count <= PD_SNK_CAP_RETRIES) { - /* Get sink cap to know if dual-role device */ - send_control(port, PD_CTRL_GET_SINK_CAP); - set_state(port, PD_STATE_SRC_GET_SINK_CAP); - break; - } else if (debug_level >= 2 && - snk_cap_count == PD_SNK_CAP_RETRIES+1) { - CPRINTF("C%d ERR SNK_CAP\n", port); - } - } - - /* Check power role policy, which may trigger a swap */ - if (pd[port].flags & PD_FLAGS_CHECK_PR_ROLE) { - pd_check_pr_role(port, PD_ROLE_SOURCE, - pd[port].flags); - pd[port].flags &= ~PD_FLAGS_CHECK_PR_ROLE; - } - - - /* Check data role policy, which may trigger a swap */ - if (pd[port].flags & PD_FLAGS_CHECK_DR_ROLE) { - pd_check_dr_role(port, pd[port].data_role, - pd[port].flags); - pd[port].flags &= ~PD_FLAGS_CHECK_DR_ROLE; - break; - } - - /* Check for Vconn source, which may trigger a swap */ - if (pd[port].flags & PD_FLAGS_CHECK_VCONN_STATE) { - /* - * Ref: Section 2.6.1 of both - * USB-PD Spec Revision 2.0, Version 1.3 & - * USB-PD Spec Revision 3.0, Version 2.0 - * During Explicit contract the Sink can - * initiate or receive a request an exchange - * of VCONN Source. - */ - pd_try_execute_vconn_swap(port, - pd[port].flags); - pd[port].flags &= ~PD_FLAGS_CHECK_VCONN_STATE; - break; - } - - /* Send discovery SVDMs last */ - if (pd[port].data_role == PD_ROLE_DFP && - (pd[port].flags & PD_FLAGS_CHECK_IDENTITY)) { -#ifndef CONFIG_USB_PD_SIMPLE_DFP - pd_send_vdm(port, USB_SID_PD, - CMD_DISCOVER_IDENT, NULL, 0); -#endif - pd[port].flags &= ~PD_FLAGS_CHECK_IDENTITY; - break; - } - - /* - * Enter_USB if port partner and cable are - * USB4 compatible. - */ - if (should_enter_usb4_mode(port)) { - pd_send_enter_usb(port, &timeout); - break; - } - - if (!(pd[port].flags & PD_FLAGS_PING_ENABLED)) - break; - - /* Verify that the sink is alive */ - res = send_control(port, PD_CTRL_PING); - if (res >= 0) - break; - - /* Ping dropped. Try soft reset. */ - set_state(port, PD_STATE_SOFT_RESET); - timeout = 10 * MSEC; - break; - case PD_STATE_SRC_GET_SINK_CAP: - if (pd[port].last_state != pd[port].task_state) - set_state_timeout(port, - get_time().val + - PD_T_SENDER_RESPONSE, - PD_STATE_SRC_READY); - break; - case PD_STATE_DR_SWAP: - if (pd[port].last_state != pd[port].task_state) { - res = send_control(port, PD_CTRL_DR_SWAP); - if (res < 0) { - timeout = 10*MSEC; - /* - * If failed to get goodCRC, send - * soft reset, otherwise ignore - * failure. - */ - set_state(port, res == -1 ? - PD_STATE_SOFT_RESET : - READY_RETURN_STATE(port)); - break; - } - /* Wait for accept or reject */ - set_state_timeout(port, - get_time().val + - PD_T_SENDER_RESPONSE, - READY_RETURN_STATE(port)); - } - break; -#ifdef CONFIG_USB_PD_DUAL_ROLE - case PD_STATE_SRC_SWAP_INIT: - if (pd[port].last_state != pd[port].task_state) { - res = send_control(port, PD_CTRL_PR_SWAP); - if (res < 0) { - timeout = 10*MSEC; - /* - * If failed to get goodCRC, send - * soft reset, otherwise ignore - * failure. - */ - set_state(port, res == -1 ? - PD_STATE_SOFT_RESET : - PD_STATE_SRC_READY); - break; - } - /* Wait for accept or reject */ - set_state_timeout(port, - get_time().val + - PD_T_SENDER_RESPONSE, - PD_STATE_SRC_READY); - } - break; - case PD_STATE_SRC_SWAP_SNK_DISABLE: - /* Give time for sink to stop drawing current */ - if (pd[port].last_state != pd[port].task_state) - set_state_timeout(port, - get_time().val + - PD_T_SINK_TRANSITION, - PD_STATE_SRC_SWAP_SRC_DISABLE); - break; - case PD_STATE_SRC_SWAP_SRC_DISABLE: - if (pd[port].last_state != pd[port].task_state) { - /* Turn power off */ - pd_power_supply_reset(port); - - /* - * Switch to Rd and swap roles to sink - * - * The reason we do this as early as possible is - * to help prevent CC disconnection cases where - * both partners are applying an Rp. Certain PD - * stacks (e.g. qualcomm), reflexively apply - * their Rp once VBUS falls beneath - * ~3.67V. (b/77827528). - */ - tcpm_set_cc(port, TYPEC_CC_RD); - pd_set_power_role(port, PD_ROLE_SINK); - - /* Inform TCPC of power role update. */ - pd_update_roles(port); - - set_state_timeout(port, - get_time().val + - PD_POWER_SUPPLY_TURN_OFF_DELAY, - PD_STATE_SRC_SWAP_STANDBY); - } - break; - case PD_STATE_SRC_SWAP_STANDBY: - /* Send PS_RDY to let sink know our power is off */ - if (pd[port].last_state != pd[port].task_state) { - /* Send PS_RDY */ - res = send_control(port, PD_CTRL_PS_RDY); - if (res < 0) { - timeout = 10*MSEC; - set_state(port, - PD_STATE_SRC_DISCONNECTED); - break; - } - /* Wait for PS_RDY from new source */ - set_state_timeout(port, - get_time().val + - PD_T_PS_SOURCE_ON, - PD_STATE_SNK_DISCONNECTED); - } - break; - case PD_STATE_SUSPENDED: { -#ifndef CONFIG_USB_PD_TCPC - int rstatus; -#endif - tcpc_prints("suspended!", port); - pd[port].req_suspend_state = 0; -#ifdef CONFIG_USB_PD_TCPC - pd_rx_disable_monitoring(port); - pd_hw_release(port); - pd_power_supply_reset(port); -#else - pd_power_supply_reset(port); -#ifdef CONFIG_USBC_VCONN - set_vconn(port, 0); -#endif - rstatus = tcpm_release(port); - if (rstatus != 0 && rstatus != EC_ERROR_UNIMPLEMENTED) - tcpc_prints("release failed!", port); -#endif - /* Drain any outstanding software message queues. */ - tcpm_clear_pending_messages(port); - - /* Wait for resume */ - while (pd[port].task_state == PD_STATE_SUSPENDED) { -#ifdef CONFIG_USB_PD_ALT_MODE_DFP - int evt = task_wait_event(-1); - - if (evt & PD_EVENT_SYSJUMP) - /* Nothing to do for sysjump prep */ - notify_sysjump_ready(); -#else - task_wait_event(-1); -#endif - } -#ifdef CONFIG_USB_PD_TCPC - pd_hw_init(port, PD_ROLE_DEFAULT(port)); - tcpc_prints("resumed!", port); -#else - if (rstatus != EC_ERROR_UNIMPLEMENTED && - pd_restart_tcpc(port) != 0) { - /* stay in PD_STATE_SUSPENDED */ - tcpc_prints("restart failed!", port); - break; - } - /* Set the CC termination and state back to default */ - tcpm_set_cc(port, - PD_ROLE_DEFAULT(port) == PD_ROLE_SOURCE ? - TYPEC_CC_RP : - TYPEC_CC_RD); - set_state(port, PD_DEFAULT_STATE(port)); - tcpc_prints("resumed!", port); -#endif - break; - } - case PD_STATE_SNK_DISCONNECTED: -#ifdef CONFIG_USB_PD_LOW_POWER - timeout = (drp_state[port] != - PD_DRP_TOGGLE_ON ? SECOND : 10*MSEC); -#else - timeout = 10*MSEC; -#endif - pd_set_src_caps(port, 0, NULL); -#ifdef CONFIG_USB_PD_TCPC_LOW_POWER - /* - * If SW decided we should be in a low power state and - * the CC lines did not change, then don't talk with the - * TCPC otherwise we might wake it up. - */ - if (pd[port].flags & PD_FLAGS_LPM_REQUESTED && - !(evt & PD_EVENT_CC)) - break; -#endif /* CONFIG_USB_PD_TCPC_LOW_POWER */ - - tcpm_get_cc(port, &cc1, &cc2); - -#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE - /* - * Attempt TCPC auto DRP toggle if it is not already - * auto toggling and not try.src, and dual role toggling - * is allowed. - */ - if (auto_toggle_supported && - !(pd[port].flags & PD_FLAGS_TCPC_DRP_TOGGLE) && - !is_try_src(port) && - cc_is_open(cc1, cc2) && - (drp_state[port] == PD_DRP_TOGGLE_ON)) { - set_state(port, PD_STATE_DRP_AUTO_TOGGLE); - timeout = 2*MSEC; - break; - } -#endif - - /* Source connection monitoring */ - if (!cc_is_open(cc1, cc2)) { - pd[port].cc_state = PD_CC_NONE; - hard_reset_count = 0; - new_cc_state = PD_CC_NONE; - pd[port].cc_debounce = get_time().val + - PD_T_CC_DEBOUNCE; - set_state(port, - PD_STATE_SNK_DISCONNECTED_DEBOUNCE); - timeout = 10*MSEC; - break; - } - - /* - * If Try.SRC is active and failed to detect a SNK, - * then it transitions to TryWait.SNK. Need to prevent - * normal dual role toggle until tDRPTryWait timer - * expires. - */ - if (pd[port].flags & PD_FLAGS_TRY_SRC) { - if (get_time().val > pd[port].try_src_marker) - pd[port].flags &= ~PD_FLAGS_TRY_SRC; - break; - } - - /* If no source detected, check for role toggle. */ - if (drp_state[port] == PD_DRP_TOGGLE_ON && - get_time().val >= next_role_swap) { - /* Swap roles to source */ - pd_set_power_role(port, PD_ROLE_SOURCE); - set_state(port, PD_STATE_SRC_DISCONNECTED); - tcpm_set_cc(port, TYPEC_CC_RP); - next_role_swap = get_time().val + PD_T_DRP_SRC; - -#ifdef CONFIG_USB_PD_TCPC_LOW_POWER - /* - * Clear low power mode flag as we are swapping - * states quickly. - */ - pd[port].flags &= ~PD_FLAGS_LPM_REQUESTED; -#endif - - /* Swap states quickly */ - timeout = 2*MSEC; - break; - } - -#ifdef CONFIG_USB_PD_TCPC_LOW_POWER - /* - * If we are remaining in the SNK_DISCONNECTED state, - * let's go into low power mode and wait for a change on - * CC status. - */ - pd[port].flags |= PD_FLAGS_LPM_REQUESTED; -#endif/* CONFIG_USB_PD_TCPC_LOW_POWER */ - break; - - case PD_STATE_SNK_DISCONNECTED_DEBOUNCE: - tcpm_get_cc(port, &cc1, &cc2); - - if (cc_is_rp(cc1) && cc_is_rp(cc2)) { - /* Debug accessory */ - new_cc_state = PD_CC_DFP_DEBUG_ACC; - } else if (cc_is_rp(cc1) || cc_is_rp(cc2)) { - new_cc_state = PD_CC_DFP_ATTACHED; - } else { - /* No connection any more */ - set_state(port, PD_STATE_SNK_DISCONNECTED); - timeout = 5*MSEC; - break; - } - - timeout = 20*MSEC; - - /* Debounce the cc state */ - if (new_cc_state != pd[port].cc_state) { - pd[port].cc_debounce = get_time().val + - PD_T_CC_DEBOUNCE; - pd[port].cc_state = new_cc_state; - break; - } - /* Wait for CC debounce and VBUS present */ - if (get_time().val < pd[port].cc_debounce || - !pd_is_vbus_present(port)) - break; - - if (pd_try_src_enable && - !(pd[port].flags & PD_FLAGS_TRY_SRC)) { - /* - * If TRY_SRC is enabled, but not active, - * then force attempt to connect as source. - */ - pd[port].try_src_marker = get_time().val - + PD_T_DRP_TRY; - pd[port].try_timeout = get_time().val - + PD_T_TRY_TIMEOUT; - /* Swap roles to source */ - pd_set_power_role(port, PD_ROLE_SOURCE); - tcpm_set_cc(port, TYPEC_CC_RP); - timeout = 2*MSEC; - set_state(port, PD_STATE_SRC_DISCONNECTED); - /* Set flag after the state change */ - pd[port].flags |= PD_FLAGS_TRY_SRC; - break; - } - - /* We are attached */ - if (IS_ENABLED(CONFIG_COMMON_RUNTIME)) - hook_notify(HOOK_USB_PD_CONNECT); - pd[port].polarity = get_snk_polarity(cc1, cc2); - pd_set_polarity(port, pd[port].polarity); - /* reset message ID on connection */ - pd[port].msg_id = 0; - /* initial data role for sink is UFP */ - pd_set_data_role(port, PD_ROLE_UFP); - /* Enable Auto Discharge Disconnect */ - tcpm_enable_auto_discharge_disconnect(port, 1); -#if defined(CONFIG_CHARGE_MANAGER) - typec_curr = usb_get_typec_current_limit( - pd[port].polarity, cc1, cc2); - typec_set_input_current_limit( - port, typec_curr, TYPE_C_VOLTAGE); -#endif - -#ifdef CONFIG_USBC_PPC - /* Inform PPC that a source is connected. */ - ppc_dev_is_connected(port, PPC_DEV_SRC); -#endif /* CONFIG_USBC_PPC */ - if (IS_ENABLED(CONFIG_USBC_OCP)) - usbc_ocp_snk_is_connected(port, false); - - /* If PD comm is enabled, enable TCPC RX */ - if (pd_comm_is_enabled(port)) - tcpm_set_rx_enable(port, 1); - - /* DFP is attached */ - if (new_cc_state == PD_CC_DFP_ATTACHED || - new_cc_state == PD_CC_DFP_DEBUG_ACC) { - pd[port].flags |= PD_FLAGS_CHECK_PR_ROLE | - PD_FLAGS_CHECK_DR_ROLE | - PD_FLAGS_CHECK_IDENTITY; - /* Reset cable attributes and flags */ - reset_pd_cable(port); - - if (new_cc_state == PD_CC_DFP_DEBUG_ACC) - pd[port].flags |= - PD_FLAGS_TS_DTS_PARTNER; - set_state(port, PD_STATE_SNK_DISCOVERY); - timeout = 10*MSEC; - hook_call_deferred( - &pd_usb_billboard_deferred_data, - PD_T_AME); - } - break; - case PD_STATE_SNK_HARD_RESET_RECOVER: - if (pd[port].last_state != pd[port].task_state) - pd[port].flags |= PD_FLAGS_CHECK_IDENTITY; - - if (get_usb_pd_vbus_detect() == - USB_PD_VBUS_DETECT_NONE) { - /* - * Can't measure vbus state so this is the - * maximum recovery time for the source. - */ - if (pd[port].last_state != pd[port].task_state) - set_state_timeout(port, get_time().val + - PD_T_SAFE_0V + - PD_T_SRC_RECOVER_MAX + - PD_T_SRC_TURN_ON, - PD_STATE_SNK_DISCONNECTED); - } else { -#ifndef CONFIG_USB_PD_VBUS_DETECT_NONE - /* Wait for VBUS to go low and then high*/ - if (pd[port].last_state != - pd[port].task_state) { - snk_hard_reset_vbus_off = 0; - set_state_timeout(port, - get_time().val + - PD_T_SAFE_0V, - hard_reset_count < - PD_HARD_RESET_COUNT ? - PD_STATE_HARD_RESET_SEND : - PD_STATE_SNK_DISCOVERY); - } - - if (!pd_is_vbus_present(port) && - !snk_hard_reset_vbus_off) { - /* VBUS has gone low, reset timeout */ - snk_hard_reset_vbus_off = 1; - set_state_timeout(port, - get_time().val + - PD_T_SRC_RECOVER_MAX + - PD_T_SRC_TURN_ON, - PD_STATE_SNK_DISCONNECTED); - } - if (pd_is_vbus_present(port) && - snk_hard_reset_vbus_off) { - /* VBUS went high again */ - set_state(port, PD_STATE_SNK_DISCOVERY); - timeout = 10*MSEC; - } - - /* - * Don't need to set timeout because VBUS - * changing will trigger an interrupt and - * wake us up. - */ -#endif - } - break; - case PD_STATE_SNK_DISCOVERY: - /* Wait for source cap expired only if we are enabled */ - if ((pd[port].last_state != pd[port].task_state) - && pd_comm_is_enabled(port)) { -#if defined(CONFIG_USB_PD_TCPM_TCPCI) || defined(CONFIG_USB_PD_TCPM_STUB) - /* - * If we come from hard reset recover state, - * then we can process the source capabilities - * form partner now, so enable PHY layer - * receiving function. - */ - if (pd[port].last_state == - PD_STATE_SNK_HARD_RESET_RECOVER) - tcpm_set_rx_enable(port, 1); -#endif /* CONFIG_USB_PD_TCPM_TCPCI || CONFIG_USB_PD_TCPM_STUB */ -#ifdef CONFIG_USB_PD_RESET_MIN_BATT_SOC - /* - * If the battery has not met a configured safe - * level for hard resets, refrain from starting - * reset timers as a hard reset could brown out - * the board. Note this may mean that - * high-power chargers will stay at 15W until a - * reset is sent, depending on boot timing. - */ - int batt_soc = usb_get_battery_soc(); - - if (batt_soc < CONFIG_USB_PD_RESET_MIN_BATT_SOC || - battery_get_disconnect_state() != - BATTERY_NOT_DISCONNECTED) - pd[port].flags |= - PD_FLAGS_SNK_WAITING_BATT; - else - pd[port].flags &= - ~PD_FLAGS_SNK_WAITING_BATT; -#endif - - if (pd[port].flags & - PD_FLAGS_SNK_WAITING_BATT) { -#ifdef CONFIG_CHARGE_MANAGER - /* - * Configure this port as dedicated for - * now, so it won't be de-selected by - * the charge manager leaving safe mode. - */ - charge_manager_update_dualrole(port, - CAP_DEDICATED); -#endif - CPRINTS("C%d: Battery low. " - "Hold reset timer", port); - /* - * If VBUS has never been low, and we timeout - * waiting for source cap, try a soft reset - * first, in case we were already in a stable - * contract before this boot. - */ - } else if (pd[port].flags & - PD_FLAGS_VBUS_NEVER_LOW) { - set_state_timeout(port, - get_time().val + - PD_T_SINK_WAIT_CAP, - PD_STATE_SOFT_RESET); - /* - * If we haven't passed hard reset counter, - * start SinkWaitCapTimer, otherwise start - * NoResponseTimer. - */ - } else if (hard_reset_count < - PD_HARD_RESET_COUNT) { - set_state_timeout(port, - get_time().val + - PD_T_SINK_WAIT_CAP, - PD_STATE_HARD_RESET_SEND); - } else if (pd_capable(port)) { - /* ErrorRecovery */ - set_state_timeout(port, - get_time().val + - PD_T_NO_RESPONSE, - PD_STATE_SNK_DISCONNECTED); - } -#if defined(CONFIG_CHARGE_MANAGER) - /* - * If we didn't come from disconnected, must - * have come from some path that did not set - * typec current limit. So, set to 0 so that - * we guarantee this is revised below. - */ - if (pd[port].last_state != - PD_STATE_SNK_DISCONNECTED_DEBOUNCE) - typec_curr = 0; -#endif - } - -#if defined(CONFIG_CHARGE_MANAGER) - timeout = PD_T_SINK_ADJ - PD_T_DEBOUNCE; - - /* Check if CC pull-up has changed */ - tcpm_get_cc(port, &cc1, &cc2); - if (typec_curr != usb_get_typec_current_limit( - pd[port].polarity, cc1, cc2)) { - /* debounce signal by requiring two reads */ - if (typec_curr_change) { - /* set new input current limit */ - typec_curr = - usb_get_typec_current_limit( - pd[port].polarity, - cc1, cc2); - typec_set_input_current_limit( - port, typec_curr, TYPE_C_VOLTAGE); - } else { - /* delay for debounce */ - timeout = PD_T_DEBOUNCE; - } - typec_curr_change = !typec_curr_change; - } else { - typec_curr_change = 0; - } -#endif - break; - case PD_STATE_SNK_REQUESTED: - /* Wait for ACCEPT or REJECT */ - if (pd[port].last_state != pd[port].task_state) { - pd[port].flags |= PD_FLAGS_CHECK_VCONN_STATE; - hard_reset_count = 0; - set_state_timeout(port, - get_time().val + - PD_T_SENDER_RESPONSE, - PD_STATE_HARD_RESET_SEND); - } - break; - case PD_STATE_SNK_TRANSITION: - /* Wait for PS_RDY */ - if (pd[port].last_state != pd[port].task_state) - set_state_timeout(port, - get_time().val + - PD_T_PS_TRANSITION, - PD_STATE_HARD_RESET_SEND); - break; - case PD_STATE_SNK_READY: - timeout = 20*MSEC; - - /* - * Don't send any traffic yet until our holdoff timer - * has expired. Some devices are chatty once we reach - * the SNK_READY state and we may end up in a collision - * of messages if we try to immediately send our - * interrogations. - */ - if (get_time().val <= - pd[port].ready_state_holdoff_timer) - break; - - /* - * Don't send any PD traffic if we woke up due to - * incoming packet or if VDO response pending to avoid - * collisions. - */ - if (incoming_packet || - (pd[port].vdm_state == VDM_STATE_BUSY)) - break; - - /* Check for new power to request */ - if (pd[port].new_power_request) { - if (pd_send_request_msg(port, 0) != EC_SUCCESS) - set_state(port, PD_STATE_SOFT_RESET); - break; - } - - /* Check power role policy, which may trigger a swap */ - if (pd[port].flags & PD_FLAGS_CHECK_PR_ROLE) { - pd_check_pr_role(port, PD_ROLE_SINK, - pd[port].flags); - pd[port].flags &= ~PD_FLAGS_CHECK_PR_ROLE; - break; - } - - /* Check data role policy, which may trigger a swap */ - if (pd[port].flags & PD_FLAGS_CHECK_DR_ROLE) { - pd_check_dr_role(port, pd[port].data_role, - pd[port].flags); - pd[port].flags &= ~PD_FLAGS_CHECK_DR_ROLE; - break; - } - - /* Check for Vconn source, which may trigger a swap */ - if (pd[port].flags & PD_FLAGS_CHECK_VCONN_STATE) { - /* - * Ref: Section 2.6.2 of both - * USB-PD Spec Revision 2.0, Version 1.3 & - * USB-PD Spec Revision 3.0, Version 2.0 - * During Explicit contract the Sink can - * initiate or receive a request an exchange - * of VCONN Source. - */ - pd_try_execute_vconn_swap(port, - pd[port].flags); - pd[port].flags &= ~PD_FLAGS_CHECK_VCONN_STATE; - break; - } - - /* If DFP, send discovery SVDMs */ - if (pd[port].data_role == PD_ROLE_DFP && - (pd[port].flags & PD_FLAGS_CHECK_IDENTITY)) { - pd_send_vdm(port, USB_SID_PD, - CMD_DISCOVER_IDENT, NULL, 0); - pd[port].flags &= ~PD_FLAGS_CHECK_IDENTITY; - break; - } - - /* - * Enter_USB if port partner and cable are - * USB4 compatible. - */ - if (should_enter_usb4_mode(port)) { - pd_send_enter_usb(port, &timeout); - break; - } - - /* Sent all messages, don't need to wake very often */ - timeout = 200*MSEC; - break; - case PD_STATE_SNK_SWAP_INIT: - if (pd[port].last_state != pd[port].task_state) { - res = send_control(port, PD_CTRL_PR_SWAP); - if (res < 0) { - timeout = 10*MSEC; - /* - * If failed to get goodCRC, send - * soft reset, otherwise ignore - * failure. - */ - set_state(port, res == -1 ? - PD_STATE_SOFT_RESET : - PD_STATE_SNK_READY); - break; - } - /* Wait for accept or reject */ - set_state_timeout(port, - get_time().val + - PD_T_SENDER_RESPONSE, - PD_STATE_SNK_READY); - } - break; - case PD_STATE_SNK_SWAP_SNK_DISABLE: - /* Stop drawing power */ - pd_set_input_current_limit(port, 0, 0); -#ifdef CONFIG_CHARGE_MANAGER - typec_set_input_current_limit(port, 0, 0); - charge_manager_set_ceil(port, - CEIL_REQUESTOR_PD, - CHARGE_CEIL_NONE); -#endif - set_state(port, PD_STATE_SNK_SWAP_SRC_DISABLE); - timeout = 10*MSEC; - break; - case PD_STATE_SNK_SWAP_SRC_DISABLE: - /* Wait for PS_RDY */ - if (pd[port].last_state != pd[port].task_state) - set_state_timeout(port, - get_time().val + - PD_T_PS_SOURCE_OFF, - PD_STATE_HARD_RESET_SEND); - break; - case PD_STATE_SNK_SWAP_STANDBY: - if (pd[port].last_state != pd[port].task_state) { - /* Switch to Rp and enable power supply. */ - tcpm_set_cc(port, TYPEC_CC_RP); - if (pd_set_power_supply_ready(port)) { - /* Restore Rd */ - tcpm_set_cc(port, TYPEC_CC_RD); - timeout = 10*MSEC; - set_state(port, - PD_STATE_SNK_DISCONNECTED); - break; - } - /* Wait for power supply to turn on */ - set_state_timeout( - port, - get_time().val + - PD_POWER_SUPPLY_TURN_ON_DELAY, - PD_STATE_SNK_SWAP_COMPLETE); - } - break; - case PD_STATE_SNK_SWAP_COMPLETE: - /* Send PS_RDY and change to source role */ - res = send_control(port, PD_CTRL_PS_RDY); - if (res < 0) { - /* Restore Rd */ - tcpm_set_cc(port, TYPEC_CC_RD); - pd_power_supply_reset(port); - timeout = 10 * MSEC; - set_state(port, PD_STATE_SNK_DISCONNECTED); - break; - } - - /* Don't send GET_SINK_CAP on swap */ - snk_cap_count = PD_SNK_CAP_RETRIES+1; - caps_count = 0; - pd[port].msg_id = 0; - pd_set_power_role(port, PD_ROLE_SOURCE); - pd_update_roles(port); - set_state(port, PD_STATE_SRC_DISCOVERY); - timeout = 10*MSEC; - break; -#ifdef CONFIG_USBC_VCONN_SWAP - case PD_STATE_VCONN_SWAP_SEND: - if (pd[port].last_state != pd[port].task_state) { - res = send_control(port, PD_CTRL_VCONN_SWAP); - if (res < 0) { - timeout = 10*MSEC; - /* - * If failed to get goodCRC, send - * soft reset, otherwise ignore - * failure. - */ - set_state(port, res == -1 ? - PD_STATE_SOFT_RESET : - READY_RETURN_STATE(port)); - break; - } - /* Wait for accept or reject */ - set_state_timeout(port, - get_time().val + - PD_T_SENDER_RESPONSE, - READY_RETURN_STATE(port)); - } - break; - case PD_STATE_VCONN_SWAP_INIT: - if (pd[port].last_state != pd[port].task_state) { - if (!(pd[port].flags & PD_FLAGS_VCONN_ON)) { - /* Turn VCONN on and wait for it */ - set_vconn(port, 1); - set_state_timeout(port, - get_time().val + - CONFIG_USBC_VCONN_SWAP_DELAY_US, - PD_STATE_VCONN_SWAP_READY); - } else { - set_state_timeout(port, - get_time().val + - PD_T_VCONN_SOURCE_ON, - READY_RETURN_STATE(port)); - } - } - break; - case PD_STATE_VCONN_SWAP_READY: - if (pd[port].last_state != pd[port].task_state) { - if (!(pd[port].flags & PD_FLAGS_VCONN_ON)) { - /* VCONN is now on, send PS_RDY */ - pd_set_vconn_role(port, - PD_ROLE_VCONN_ON); - res = send_control(port, - PD_CTRL_PS_RDY); - if (res == -1) { - timeout = 10*MSEC; - /* - * If failed to get goodCRC, - * send soft reset - */ - set_state(port, - PD_STATE_SOFT_RESET); - break; - } - set_state(port, - READY_RETURN_STATE(port)); - } else { - /* Turn VCONN off and wait for it */ - set_vconn(port, 0); - pd_set_vconn_role(port, - PD_ROLE_VCONN_OFF); - set_state_timeout(port, - get_time().val + - CONFIG_USBC_VCONN_SWAP_DELAY_US, - READY_RETURN_STATE(port)); - } - } - break; -#endif /* CONFIG_USBC_VCONN_SWAP */ -#endif /* CONFIG_USB_PD_DUAL_ROLE */ - case PD_STATE_SOFT_RESET: - if (pd[port].last_state != pd[port].task_state) { - /* Message ID of soft reset is always 0 */ - invalidate_last_message_id(port); - pd[port].msg_id = 0; - res = send_control(port, PD_CTRL_SOFT_RESET); - - /* if soft reset failed, try hard reset. */ - if (res < 0) { - set_state(port, - PD_STATE_HARD_RESET_SEND); - timeout = 5*MSEC; - break; - } - - set_state_timeout( - port, - get_time().val + PD_T_SENDER_RESPONSE, - PD_STATE_HARD_RESET_SEND); - } - break; - case PD_STATE_HARD_RESET_SEND: - hard_reset_count++; - if (pd[port].last_state != pd[port].task_state) { - hard_reset_sent = 0; - pd[port].hard_reset_complete_timer = 0; - } -#ifdef CONFIG_CHARGE_MANAGER - if (pd[port].last_state == PD_STATE_SNK_DISCOVERY || - (pd[port].last_state == PD_STATE_SOFT_RESET && - (pd[port].flags & PD_FLAGS_VBUS_NEVER_LOW))) { - pd[port].flags &= ~PD_FLAGS_VBUS_NEVER_LOW; - /* - * If discovery timed out, assume that we - * have a dedicated charger attached. This - * may not be a correct assumption according - * to the specification, but it generally - * works in practice and the harmful - * effects of a wrong assumption here - * are minimal. - */ - charge_manager_update_dualrole(port, - CAP_DEDICATED); - } -#endif - - if (hard_reset_sent) - break; - - if (pd_transmit(port, TCPCI_MSG_TX_HARD_RESET, 0, NULL, - AMS_START) < 0) { - /* - * likely a non-idle channel - * TCPCI r2.0 v1.0 4.4.15: - * the TCPC does not retry HARD_RESET - * but we can try periodically until the timer - * expires. - */ - now = get_time(); - if (pd[port].hard_reset_complete_timer == 0) { - pd[port].hard_reset_complete_timer = - now.val + - PD_T_HARD_RESET_COMPLETE; - timeout = PD_T_HARD_RESET_RETRY; - break; - } - if (now.val < - pd[port].hard_reset_complete_timer) { - CPRINTS("C%d: Retrying hard reset", - port); - timeout = PD_T_HARD_RESET_RETRY; - break; - } - /* - * PD 2.0 spec, section 6.5.11.1 - * Pretend TX_HARD_RESET succeeded after - * timeout. - */ - } - - hard_reset_sent = 1; - /* - * If we are source, delay before cutting power - * to allow sink time to get hard reset. - */ - if (pd[port].power_role == PD_ROLE_SOURCE) { - set_state_timeout(port, - get_time().val + PD_T_PS_HARD_RESET, - PD_STATE_HARD_RESET_EXECUTE); - } else { - set_state(port, PD_STATE_HARD_RESET_EXECUTE); - timeout = 10 * MSEC; - } - break; - case PD_STATE_HARD_RESET_EXECUTE: -#ifdef CONFIG_USB_PD_DUAL_ROLE - /* - * If hard reset while in the last stages of power - * swap, then we need to restore our CC resistor. - */ - if (pd[port].last_state == PD_STATE_SNK_SWAP_STANDBY) - tcpm_set_cc(port, TYPEC_CC_RD); -#endif - - /* reset our own state machine */ - pd_execute_hard_reset(port); - timeout = 10*MSEC; - break; -#ifdef CONFIG_COMMON_RUNTIME - case PD_STATE_BIST_RX: - send_bist_cmd(port); - /* Delay at least enough for partner to finish BIST */ - timeout = PD_T_BIST_RECEIVE + 20*MSEC; - /* Set to appropriate port disconnected state */ - set_state(port, DUAL_ROLE_IF_ELSE(port, - PD_STATE_SNK_DISCONNECTED, - PD_STATE_SRC_DISCONNECTED)); - break; - case PD_STATE_BIST_TX: - pd_transmit(port, TCPCI_MSG_TX_BIST_MODE_2, 0, NULL, - AMS_START); - /* Delay at least enough to finish sending BIST */ - timeout = PD_T_BIST_TRANSMIT + 20*MSEC; - /* Set to appropriate port disconnected state */ - set_state(port, DUAL_ROLE_IF_ELSE(port, - PD_STATE_SNK_DISCONNECTED, - PD_STATE_SRC_DISCONNECTED)); - break; -#endif -#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE - case PD_STATE_DRP_AUTO_TOGGLE: - { - enum pd_drp_next_states next_state; - - assert(auto_toggle_supported); - -#ifdef CONFIG_USB_PD_TCPC_LOW_POWER - /* - * If SW decided we should be in a low power state and - * the CC lines did not change, then don't talk with the - * TCPC otherwise we might wake it up. - */ - if (pd[port].flags & PD_FLAGS_LPM_REQUESTED && - !(evt & PD_EVENT_CC)) - break; - - /* - * Debounce low power mode exit. Some TCPCs need time - * for the CC_STATUS register to be stable after exiting - * low power mode. - */ - if (pd[port].flags & PD_FLAGS_LPM_EXIT) { - uint64_t now; - - now = get_time().val; - if (now < pd[port].low_power_exit_time) - break; - - CPRINTS("TCPC p%d Exit Low Power Mode done", - port); - pd[port].flags &= ~PD_FLAGS_LPM_EXIT; - } -#endif - - /* - * Check for connection - * - * Send FALSE for supports_auto_toggle to not change - * the current return value of UNATTACHED instead of - * the auto-toggle ATTACHED_WAIT response for TCPMv1. - */ - tcpm_get_cc(port, &cc1, &cc2); - - next_state = drp_auto_toggle_next_state( - &pd[port].drp_sink_time, - pd[port].power_role, - drp_state[port], - cc1, cc2, false); - -#ifdef CONFIG_USB_PD_TCPC_LOW_POWER - /* - * The next state is not determined just by what is - * attached, but also depends on DRP_STATE. Regardless - * of next state, if nothing is attached, then always - * request low power mode. - */ - if (cc_is_open(cc1, cc2)) - pd[port].flags |= PD_FLAGS_LPM_REQUESTED; -#endif - if (next_state == DRP_TC_DEFAULT) { - if (PD_DEFAULT_STATE(port) == - PD_STATE_SNK_DISCONNECTED) - next_state = DRP_TC_UNATTACHED_SNK; - else - next_state = DRP_TC_UNATTACHED_SRC; - } - - if (next_state == DRP_TC_UNATTACHED_SNK) { - /* - * The TCPCI comes out of auto toggle with - * a prospective connection. It is expecting - * us to set the CC lines to what it is - * thinking is best or it goes direct back to - * unattached. So get the SNK polarity to - * be able to setup the CC lines to avoid this. - */ - pd[port].polarity = get_snk_polarity(cc1, cc2); - - tcpm_set_cc(port, TYPEC_CC_RD); - pd_set_power_role(port, PD_ROLE_SINK); - timeout = 2*MSEC; - set_state(port, PD_STATE_SNK_DISCONNECTED); - } else if (next_state == DRP_TC_UNATTACHED_SRC) { - /* - * The TCPCI comes out of auto toggle with - * a prospective connection. It is expecting - * us to set the CC lines to what it is - * thinking is best or it goes direct back to - * unattached. So get the SNK polarity to - * be able to setup the CC lines to avoid this. - */ - pd[port].polarity = get_src_polarity(cc1, cc2); - - tcpm_set_cc(port, TYPEC_CC_RP); - pd_set_power_role(port, PD_ROLE_SOURCE); - timeout = 2*MSEC; - set_state(port, PD_STATE_SRC_DISCONNECTED); - } else { - /* - * We are staying in PD_STATE_DRP_AUTO_TOGGLE, - * therefore enable auto-toggle. - */ - tcpm_enable_drp_toggle(port); - pd[port].flags |= PD_FLAGS_TCPC_DRP_TOGGLE; - set_state(port, PD_STATE_DRP_AUTO_TOGGLE); - } - - break; - } -#endif - case PD_STATE_ENTER_USB: - if (pd[port].last_state != pd[port].task_state) { - set_state_timeout(port, - get_time().val + PD_T_SENDER_RESPONSE, - READY_RETURN_STATE(port)); - } - break; - default: - break; - } - - pd[port].last_state = this_state; - - /* - * Check for state timeout, and if not check if need to adjust - * timeout value to wake up on the next state timeout. - */ - now = get_time(); - if (pd[port].timeout) { - if (now.val >= pd[port].timeout) { - set_state(port, pd[port].timeout_state); - /* On a state timeout, run next state soon */ - timeout = timeout < 10*MSEC ? timeout : 10*MSEC; - } else if (pd[port].timeout - now.val < timeout) { - timeout = pd[port].timeout - now.val; - } - } - -#ifdef CONFIG_USB_PD_TCPC_LOW_POWER - /* Determine if we need to put the TCPC in low power mode */ - if (pd[port].flags & PD_FLAGS_LPM_REQUESTED && - !(pd[port].flags & PD_FLAGS_LPM_ENGAGED)) { - int64_t time_left; - - /* If any task prevents LPM, wait another debounce */ - if (pd[port].tasks_preventing_lpm) { - pd[port].low_power_time = - PD_LPM_DEBOUNCE_US + now.val; - } - - time_left = pd[port].low_power_time - now.val; - if (time_left <= 0) { - pd[port].flags |= PD_FLAGS_LPM_ENGAGED; - pd[port].flags |= PD_FLAGS_LPM_TRANSITION; - tcpm_enter_low_power_mode(port); - pd[port].flags &= ~PD_FLAGS_LPM_TRANSITION; - tcpc_prints("Enter Low Power Mode", port); - timeout = -1; - } else if (timeout < 0 || timeout > time_left) { - timeout = time_left; - } - } -#endif - - /* Check for disconnection if we're connected */ - if (!pd_is_connected(port)) - continue; -#ifdef CONFIG_USB_PD_DUAL_ROLE - if (pd_is_power_swapping(port)) - continue; -#endif - if (pd[port].power_role == PD_ROLE_SOURCE) { - /* Source: detect disconnect by monitoring CC */ - tcpm_get_cc(port, &cc1, &cc2); - if (polarity_rm_dts(pd[port].polarity)) - cc1 = cc2; - if (cc1 == TYPEC_CC_VOLT_OPEN) { - set_state(port, PD_STATE_SRC_DISCONNECTED); - /* Debouncing */ - timeout = 10*MSEC; -#ifdef CONFIG_USB_PD_DUAL_ROLE - /* - * If Try.SRC is configured, then ATTACHED_SRC - * needs to transition to TryWait.SNK. Change - * power role to SNK and start state timer. - */ - if (pd_try_src_enable) { - /* Swap roles to sink */ - pd_set_power_role(port, PD_ROLE_SINK); - tcpm_set_cc(port, TYPEC_CC_RD); - /* Set timer for TryWait.SNK state */ - pd[port].try_src_marker = get_time().val - + PD_T_DEBOUNCE; - /* Advance to TryWait.SNK state */ - set_state(port, - PD_STATE_SNK_DISCONNECTED); - /* Mark state as TryWait.SNK */ - pd[port].flags |= PD_FLAGS_TRY_SRC; - } -#endif - } - } -#ifdef CONFIG_USB_PD_DUAL_ROLE - /* - * Sink disconnect if VBUS is low and - * 1) we are not waiting for VBUS to debounce after a power - * role swap. - * 2) we are not recovering from a hard reset. - */ - if (pd[port].power_role == PD_ROLE_SINK && - pd[port].vbus_debounce_time < get_time().val && - !pd_is_vbus_present(port) && - pd[port].task_state != PD_STATE_SNK_HARD_RESET_RECOVER && - pd[port].task_state != PD_STATE_HARD_RESET_EXECUTE) { - /* Sink: detect disconnect by monitoring VBUS */ - set_state(port, PD_STATE_SNK_DISCONNECTED); - /* set timeout small to reconnect fast */ - timeout = 5*MSEC; - } -#endif /* CONFIG_USB_PD_DUAL_ROLE */ - } -} - -#ifdef CONFIG_USB_PD_DUAL_ROLE -static void pd_chipset_resume(void) -{ - int i; - - for (i = 0; i < board_get_usb_pd_port_count(); i++) { -#ifdef CONFIG_CHARGE_MANAGER - if (charge_manager_get_active_charge_port() != i) -#endif - pd[i].flags |= PD_FLAGS_CHECK_PR_ROLE | - PD_FLAGS_CHECK_DR_ROLE; - pd_set_dual_role(i, PD_DRP_TOGGLE_ON); - } - - 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 < board_get_usb_pd_port_count(); i++) - pd_set_dual_role(i, PD_DRP_TOGGLE_OFF); - CPRINTS("PD:S0->S3"); -} -DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, pd_chipset_suspend, HOOK_PRIO_DEFAULT); - -static void pd_chipset_startup(void) -{ - int i; - - for (i = 0; i < board_get_usb_pd_port_count(); i++) { - pd_set_dual_role_no_wakeup(i, PD_DRP_TOGGLE_OFF); - pd[i].flags |= PD_FLAGS_CHECK_IDENTITY; - /* Reset cable attributes and flags */ - reset_pd_cable(i); - task_set_event(PD_PORT_TO_TASK_ID(i), - PD_EVENT_POWER_STATE_CHANGE | - PD_EVENT_UPDATE_DUAL_ROLE); - } - 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 < board_get_usb_pd_port_count(); i++) { - pd_set_dual_role_no_wakeup(i, PD_DRP_FORCE_SINK); - task_set_event(PD_PORT_TO_TASK_ID(i), - PD_EVENT_POWER_STATE_CHANGE | - PD_EVENT_UPDATE_DUAL_ROLE); - } - CPRINTS("PD:S3->S5"); -} -DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, pd_chipset_shutdown, HOOK_PRIO_DEFAULT); - -#endif /* CONFIG_USB_PD_DUAL_ROLE */ - -#ifdef CONFIG_COMMON_RUNTIME - -static void pd_control_resume(int port) -{ - if (pd[port].task_state != PD_STATE_SUSPENDED) - return; - - set_state(port, PD_DEFAULT_STATE(port)); - /* - * Since we did not service interrupts while we were suspended, - * see if there is a waiting interrupt to be serviced. If the - * interrupt line isn't asserted, we won't communicate with the - * TCPC. - */ - if (IS_ENABLED(HAS_TASK_PD_INT_C0)) - schedule_deferred_pd_interrupt(port); - task_wake(PD_PORT_TO_TASK_ID(port)); -} - -/* - * (suspend=1) request pd_task transition to the suspended state. hang - * around for a while until we observe the state change. this can - * take a while (like 300ms) on startup when pd_task is sleeping in - * tcpci_tcpm_init. - * - * (suspend=0) force pd_task out of the suspended state and into the - * port's default state. - */ - -void pd_set_suspend(int port, int suspend) -{ - int tries = 300; - - if (suspend) { - pd[port].req_suspend_state = 1; - do { - task_wake(PD_PORT_TO_TASK_ID(port)); - if (pd[port].task_state == PD_STATE_SUSPENDED) - break; - msleep(1); - } while (--tries != 0); - if (!tries) - tcpc_prints("set_suspend failed!", port); - } else { - pd_control_resume(port); - } -} - -int pd_is_port_enabled(int port) -{ - switch (pd[port].task_state) { - case PD_STATE_DISABLED: - case PD_STATE_SUSPENDED: - return 0; - default: - return 1; - } -} - -#if defined(CONFIG_USB_PD_ALT_MODE) && !defined(CONFIG_USB_PD_ALT_MODE_DFP) -void pd_send_hpd(int port, enum hpd_event hpd) -{ - uint32_t data[1]; - int opos = pd_alt_mode(port, TCPCI_MSG_SOP, USB_SID_DISPLAYPORT); - if (!opos) - return; - - data[0] = VDO_DP_STATUS((hpd == hpd_irq), /* IRQ_HPD */ - (hpd != hpd_low), /* HPD_HI|LOW */ - 0, /* request exit DP */ - 0, /* request exit USB */ - 0, /* MF pref */ - 1, /* enabled */ - 0, /* power low */ - 0x2); - pd_send_vdm(port, USB_SID_DISPLAYPORT, - VDO_OPOS(opos) | CMD_ATTENTION, data, 1); - /* Wait until VDM is done. */ - while (pd[0].vdm_state > 0) - task_wait_event(USB_PD_RX_TMOUT_US * - (CONFIG_PD_RETRY_COUNT + 1)); -} -#endif - -int pd_fetch_acc_log_entry(int port) -{ - timestamp_t timeout; - - /* Cannot send a VDM now, the host should retry */ - if (pd[port].vdm_state > 0) - return pd[port].vdm_state == VDM_STATE_BUSY ? - EC_RES_BUSY : EC_RES_UNAVAILABLE; - - pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_GET_LOG, NULL, 0); - timeout.val = get_time().val + 75*MSEC; - - /* Wait until VDM is done */ - while ((pd[port].vdm_state > 0) && - (get_time().val < timeout.val)) - task_wait_event(10*MSEC); - - if (pd[port].vdm_state > 0) - return EC_RES_TIMEOUT; - else if (pd[port].vdm_state < 0) - return EC_RES_ERROR; - - return EC_RES_SUCCESS; -} - -#ifdef CONFIG_USB_PD_DUAL_ROLE -void pd_request_source_voltage(int port, int mv) -{ - pd_set_max_voltage(mv); - - if (pd[port].task_state == PD_STATE_SNK_READY || - pd[port].task_state == PD_STATE_SNK_TRANSITION) { - /* Set flag to send new power request in pd_task */ - pd[port].new_power_request = 1; - } else { - pd_set_power_role(port, PD_ROLE_SINK); - tcpm_set_cc(port, TYPEC_CC_RD); - set_state(port, PD_STATE_SNK_DISCONNECTED); - } - - task_wake(PD_PORT_TO_TASK_ID(port)); -} - -void pd_set_external_voltage_limit(int port, int mv) -{ - pd_set_max_voltage(mv); - - if (pd[port].task_state == PD_STATE_SNK_READY || - pd[port].task_state == PD_STATE_SNK_TRANSITION) { - /* Set flag to send new power request in pd_task */ - pd[port].new_power_request = 1; - task_wake(PD_PORT_TO_TASK_ID(port)); - } -} - -void pd_update_contract(int port) -{ - if ((pd[port].task_state >= PD_STATE_SRC_NEGOCIATE) && - (pd[port].task_state <= PD_STATE_SRC_GET_SINK_CAP)) { - pd[port].flags |= PD_FLAGS_UPDATE_SRC_CAPS; - task_wake(PD_PORT_TO_TASK_ID(port)); - } -} - -#endif /* CONFIG_USB_PD_DUAL_ROLE */ - -static int command_pd(int argc, char **argv) -{ - int port; - char *e; - - if (argc < 2) - return EC_ERROR_PARAM_COUNT; - - if (!strcasecmp(argv[1], "dump")) { - if (argc >= 3) { -#ifdef CONFIG_USB_PD_DEBUG_LEVEL - return EC_ERROR_PARAM2; -#else - int level = strtoi(argv[2], &e, 10); - if (*e) - return EC_ERROR_PARAM2; - debug_level = level; -#endif - } - ccprintf("debug=%d\n", debug_level); - - return EC_SUCCESS; - } - -#ifdef CONFIG_CMD_PD -#ifdef CONFIG_CMD_PD_DEV_DUMP_INFO - else if (!strncasecmp(argv[1], "rwhashtable", 3)) { - int i; - struct ec_params_usb_pd_rw_hash_entry *p; - for (i = 0; i < RW_HASH_ENTRIES; i++) { - p = &rw_hash_table[i]; - pd_dev_dump_info(p->dev_id, p->dev_rw_hash); - } - return EC_SUCCESS; - } -#endif /* CONFIG_CMD_PD_DEV_DUMP_INFO */ -#ifdef CONFIG_USB_PD_TRY_SRC - else if (!strncasecmp(argv[1], "trysrc", 6)) { - int enable; - - if (argc >= 3) { - enable = strtoi(argv[2], &e, 10); - if (*e) - return EC_ERROR_PARAM3; - pd_try_src_enable = enable ? 1 : 0; - } - - ccprintf("Try.SRC %s\n", pd_try_src_enable ? "on" : "off"); - return EC_SUCCESS; - } -#endif -#endif - else if (!strcasecmp(argv[1], "version")) { - ccprintf("%d\n", PD_STACK_VERSION); - return EC_SUCCESS; - } - - /* command: pd <port> <subcmd> [args] */ - port = strtoi(argv[1], &e, 10); - if (argc < 3) - return EC_ERROR_PARAM_COUNT; - if (*e || port >= board_get_usb_pd_port_count()) - return EC_ERROR_PARAM2; -#if defined(CONFIG_CMD_PD) && defined(CONFIG_USB_PD_DUAL_ROLE) - - if (!strcasecmp(argv[2], "tx")) { - set_state(port, PD_STATE_SNK_DISCOVERY); - task_wake(PD_PORT_TO_TASK_ID(port)); - } else if (!strcasecmp(argv[2], "bist_rx")) { - set_state(port, PD_STATE_BIST_RX); - task_wake(PD_PORT_TO_TASK_ID(port)); - } else if (!strcasecmp(argv[2], "bist_tx")) { - if (*e) - return EC_ERROR_PARAM3; - set_state(port, PD_STATE_BIST_TX); - task_wake(PD_PORT_TO_TASK_ID(port)); - } else if (!strcasecmp(argv[2], "charger")) { - pd_set_power_role(port, PD_ROLE_SOURCE); - tcpm_set_cc(port, TYPEC_CC_RP); - set_state(port, PD_STATE_SRC_DISCONNECTED); - task_wake(PD_PORT_TO_TASK_ID(port)); - } else if (!strncasecmp(argv[2], "dev", 3)) { - int max_volt; - if (argc >= 4) - max_volt = strtoi(argv[3], &e, 10) * 1000; - else - max_volt = pd_get_max_voltage(); - - pd_request_source_voltage(port, max_volt); - ccprintf("max req: %dmV\n", max_volt); - } else if (!strcasecmp(argv[2], "disable")) { - pd_comm_enable(port, 0); - ccprintf("Port C%d disable\n", port); - return EC_SUCCESS; - } else if (!strcasecmp(argv[2], "enable")) { - pd_comm_enable(port, 1); - ccprintf("Port C%d enabled\n", port); - return EC_SUCCESS; - } else if (!strncasecmp(argv[2], "hard", 4)) { - set_state(port, PD_STATE_HARD_RESET_SEND); - task_wake(PD_PORT_TO_TASK_ID(port)); - } else if (!strncasecmp(argv[2], "info", 4)) { - int i; - ccprintf("Hash "); - for (i = 0; i < PD_RW_HASH_SIZE / 4; i++) - ccprintf("%08x ", pd[port].dev_rw_hash[i]); - ccprintf("\nImage %s\n", - ec_image_to_string(pd[port].current_image)); - } else if (!strncasecmp(argv[2], "soft", 4)) { - set_state(port, PD_STATE_SOFT_RESET); - task_wake(PD_PORT_TO_TASK_ID(port)); - } else if (!strncasecmp(argv[2], "swap", 4)) { - if (argc < 4) - return EC_ERROR_PARAM_COUNT; - - if (!strncasecmp(argv[3], "power", 5)) - pd_request_power_swap(port); - else if (!strncasecmp(argv[3], "data", 4)) - pd_request_data_swap(port); -#ifdef CONFIG_USBC_VCONN_SWAP - else if (!strncasecmp(argv[3], "vconn", 5)) - pd_request_vconn_swap(port); -#endif - else - return EC_ERROR_PARAM3; - } else if (!strncasecmp(argv[2], "srccaps", 7)) { - pd_srccaps_dump(port); - } else if (!strncasecmp(argv[2], "ping", 4)) { - int enable; - - if (argc > 3) { - enable = strtoi(argv[3], &e, 10); - if (*e) - return EC_ERROR_PARAM3; - pd_ping_enable(port, enable); - } - - ccprintf("Pings %s\n", - (pd[port].flags & PD_FLAGS_PING_ENABLED) ? - "on" : "off"); - } else if (!strncasecmp(argv[2], "vdm", 3)) { - if (argc < 4) - return EC_ERROR_PARAM_COUNT; - - if (!strncasecmp(argv[3], "ping", 4)) { - uint32_t enable; - if (argc < 5) - return EC_ERROR_PARAM_COUNT; - enable = strtoi(argv[4], &e, 10); - if (*e) - return EC_ERROR_PARAM4; - pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_PING_ENABLE, - &enable, 1); - } else if (!strncasecmp(argv[3], "curr", 4)) { - pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_CURRENT, - NULL, 0); - } else if (!strncasecmp(argv[3], "vers", 4)) { - pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_VERSION, - NULL, 0); - } else { - return EC_ERROR_PARAM_COUNT; - } -#if defined(CONFIG_CMD_PD) && defined(CONFIG_CMD_PD_FLASH) - } else if (!strncasecmp(argv[2], "flash", 4)) { - return remote_flashing(argc, argv); -#endif -#if defined(CONFIG_CMD_PD) && defined(CONFIG_USB_PD_DUAL_ROLE) - } else if (!strcasecmp(argv[2], "dualrole")) { - if (argc < 4) { - ccprintf("dual-role toggling: "); - switch (drp_state[port]) { - case PD_DRP_TOGGLE_ON: - ccprintf("on\n"); - break; - case PD_DRP_TOGGLE_OFF: - ccprintf("off\n"); - break; - case PD_DRP_FREEZE: - ccprintf("freeze\n"); - break; - case PD_DRP_FORCE_SINK: - ccprintf("force sink\n"); - break; - case PD_DRP_FORCE_SOURCE: - ccprintf("force source\n"); - break; - } - } else { - if (!strcasecmp(argv[3], "on")) - pd_set_dual_role(port, PD_DRP_TOGGLE_ON); - else if (!strcasecmp(argv[3], "off")) - pd_set_dual_role(port, PD_DRP_TOGGLE_OFF); - else if (!strcasecmp(argv[3], "freeze")) - pd_set_dual_role(port, PD_DRP_FREEZE); - else if (!strcasecmp(argv[3], "sink")) - pd_set_dual_role(port, PD_DRP_FORCE_SINK); - else if (!strcasecmp(argv[3], "source")) - pd_set_dual_role(port, - PD_DRP_FORCE_SOURCE); - else - return EC_ERROR_PARAM4; - } - return EC_SUCCESS; -#endif - } else -#endif - if (!strncasecmp(argv[2], "state", 5)) { - ccprintf("Port C%d CC%d, %s - Role: %s-%s%s " - "State: %d(%s), Flags: 0x%04x\n", - port, pd[port].polarity + 1, - pd_comm_is_enabled(port) ? "Ena" : "Dis", - pd[port].power_role == PD_ROLE_SOURCE ? "SRC" : "SNK", - pd[port].data_role == PD_ROLE_DFP ? "DFP" : "UFP", - (pd[port].flags & PD_FLAGS_VCONN_ON) ? "-VC" : "", - pd[port].task_state, - debug_level > 0 ? pd_get_task_state_name(port) : "", - pd[port].flags); - } else { - return EC_ERROR_PARAM1; - } - - return EC_SUCCESS; -} -DECLARE_CONSOLE_COMMAND(pd, command_pd, - "version" - "|dump" -#ifdef CONFIG_USB_PD_TRY_SRC - "|trysrc" -#endif - " [0|1|2]" -#ifdef CONFIG_CMD_PD_DEV_DUMP_INFO - "|rwhashtable" -#endif - "\n\t<port> state" -#ifdef CONFIG_USB_PD_DUAL_ROLE - "|tx|bist_rx|bist_tx|charger|dev" - "\n\t<port> disable|enable|soft|info|hard|ping" - "\n\t<port> dualrole [on|off|freeze|sink|source]" - "\n\t<port> swap [power|data|vconn]" - "\n\t<port> vdm [ping|curr|vers]" -#ifdef CONFIG_CMD_PD_FLASH - "\n\t<port> flash [erase|reboot|signature|info|version]" -#endif /* CONFIG_CMD_PD_FLASH */ -#endif /* CONFIG_USB_PD_DUAL_ROLE */ - "\n\t<port> srccaps", - "USB PD"); - -#ifdef HAS_TASK_HOSTCMD - -#ifdef CONFIG_HOSTCMD_FLASHPD -static enum ec_status hc_remote_flash(struct host_cmd_handler_args *args) -{ - const struct ec_params_usb_pd_fw_update *p = args->params; - int port = p->port; - const uint32_t *data = &(p->size) + 1; - int i, size, rv = EC_RES_SUCCESS; - timestamp_t timeout; - - if (port >= board_get_usb_pd_port_count()) - return EC_RES_INVALID_PARAM; - - if (p->size + sizeof(*p) > args->params_size) - return EC_RES_INVALID_PARAM; - -#if defined(CONFIG_BATTERY) && \ - (defined(CONFIG_BATTERY_PRESENT_CUSTOM) || \ - defined(CONFIG_BATTERY_PRESENT_GPIO)) - /* - * Do not allow PD firmware update if no battery and this port - * is sinking power, because we will lose power. - */ - if (battery_is_present() != BP_YES && - charge_manager_get_active_charge_port() == port) - return EC_RES_UNAVAILABLE; -#endif - - /* - * Busy still with a VDM that host likely generated. 1 deep VDM queue - * so just return for retry logic on host side to deal with. - */ - if (pd[port].vdm_state > 0) - return EC_RES_BUSY; - - switch (p->cmd) { - case USB_PD_FW_REBOOT: - pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_REBOOT, NULL, 0); - - /* - * Return immediately to free pending i2c bus. Host needs to - * manage this delay. - */ - return EC_RES_SUCCESS; - - case USB_PD_FW_FLASH_ERASE: - pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_FLASH_ERASE, NULL, 0); - - /* - * Return immediately. Host needs to manage delays here which - * can be as long as 1.2 seconds on 64KB RW flash. - */ - return EC_RES_SUCCESS; - - case USB_PD_FW_ERASE_SIG: - pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_ERASE_SIG, NULL, 0); - timeout.val = get_time().val + 500*MSEC; - break; - - case USB_PD_FW_FLASH_WRITE: - /* Data size must be a multiple of 4 */ - if (!p->size || p->size % 4) - return EC_RES_INVALID_PARAM; - - size = p->size / 4; - for (i = 0; i < size; i += VDO_MAX_SIZE - 1) { - pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_FLASH_WRITE, - data + i, MIN(size - i, VDO_MAX_SIZE - 1)); - timeout.val = get_time().val + 500*MSEC; - - /* Wait until VDM is done */ - while ((pd[port].vdm_state > 0) && - (get_time().val < timeout.val)) - task_wait_event(10*MSEC); - - if (pd[port].vdm_state > 0) - return EC_RES_TIMEOUT; - } - return EC_RES_SUCCESS; - - default: - return EC_RES_INVALID_PARAM; - break; - } - - /* Wait until VDM is done or timeout */ - while ((pd[port].vdm_state > 0) && (get_time().val < timeout.val)) - task_wait_event(50*MSEC); - - if ((pd[port].vdm_state > 0) || - (pd[port].vdm_state == VDM_STATE_ERR_TMOUT)) - rv = EC_RES_TIMEOUT; - else if (pd[port].vdm_state < 0) - rv = EC_RES_ERROR; - - return rv; -} -DECLARE_HOST_COMMAND(EC_CMD_USB_PD_FW_UPDATE, - hc_remote_flash, - EC_VER_MASK(0)); -#endif /* CONFIG_HOSTCMD_FLASHPD */ - -#endif /* HAS_TASK_HOSTCMD */ - - -#endif /* CONFIG_COMMON_RUNTIME */ |