summaryrefslogtreecommitdiff
path: root/common/usb_pd_protocol.c
diff options
context:
space:
mode:
authorJack Rosenthal <jrosenth@chromium.org>2021-11-04 12:11:58 -0600
committerCommit Bot <commit-bot@chromium.org>2021-11-05 04:22:34 +0000
commit252457d4b21f46889eebad61d4c0a65331919cec (patch)
tree01856c4d31d710b20e85a74c8d7b5836e35c3b98 /common/usb_pd_protocol.c
parent08f5a1e6fc2c9467230444ac9b582dcf4d9f0068 (diff)
downloadchrome-ec-stabilize-14695.107.B-ish.tar.gz
In the interest of making long-term branch maintenance incur as little technical debt on us as possible, we should not maintain any files on the branch we are not actually using. This has the added effect of making it extremely clear when merging CLs from the main branch when changes have the possibility to affect us. The follow-on CL adds a convenience script to actually pull updates from the main branch and generate a CL for the update. BUG=b:204206272 BRANCH=ish TEST=make BOARD=arcada_ish && make BOARD=drallion_ish Signed-off-by: Jack Rosenthal <jrosenth@chromium.org> Change-Id: I17e4694c38219b5a0823e0a3e55a28d1348f4b18 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/3262038 Reviewed-by: Jett Rink <jettrink@chromium.org> Reviewed-by: Tom Hughes <tomhughes@chromium.org>
Diffstat (limited to 'common/usb_pd_protocol.c')
-rw-r--r--common/usb_pd_protocol.c5449
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 */