| /* Copyright (c) 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 "ec_commands.h" |
| #include "gpio.h" |
| #include "hooks.h" |
| #include "host_command.h" |
| #include "registers.h" |
| #include "system.h" |
| #include "task.h" |
| #include "timer.h" |
| #include "util.h" |
| #include "usb_charge.h" |
| #include "usb_mux.h" |
| #include "usb_pd.h" |
| #include "usb_pd_tcpm.h" |
| #include "usbc_ppc.h" |
| #include "tcpm.h" |
| #include "version.h" |
| #include "vboot.h" |
| |
| #ifdef CONFIG_COMMON_RUNTIME |
| #define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) |
| #define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) |
| |
| BUILD_ASSERT(CONFIG_USB_PD_PORT_COUNT <= EC_USB_PD_MAX_PORTS); |
| |
| /* |
| * If we are trying to upgrade the TCPC port that is supplying power, then we |
| * need to ensure that the battery has enough charge for the upgrade. 100mAh |
| * is about 5% of most batteries, and it should be enough charge to get us |
| * through the EC jump to RW and PD upgrade. |
| */ |
| #define MIN_BATTERY_FOR_TCPC_UPGRADE_MAH 100 /* mAH */ |
| |
| /* |
| * 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_COUNT]; |
| #else /* CONFIG_COMMON_RUNTIME */ |
| #define CPRINTF(format, args...) |
| #define CPRINTS(format, args...) |
| 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 |
| |
| 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_COUNT] = { |
| [0 ... (CONFIG_USB_PD_PORT_COUNT - 1)] = |
| CONFIG_USB_PD_INITIAL_DRP_STATE}; |
| |
| /* Enable variable for Try.SRC states */ |
| static uint8_t pd_try_src_enable; |
| #endif |
| |
| #ifdef CONFIG_USB_PD_REV30 |
| /* |
| * The spec. revision is used to index into this array. |
| * 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 |
| */ |
| static const uint8_t refuse[] = { |
| PD_CTRL_REJECT, PD_CTRL_REJECT, PD_CTRL_NOT_SUPPORTED}; |
| #define REFUSE(r) refuse[r] |
| #else |
| #define REFUSE(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) */ |
| uint8_t power_role; |
| /* current port data role (DFP or UFP) */ |
| uint8_t data_role; |
| /* 3-bit rolling message ID counter */ |
| uint8_t msg_id; |
| /* Port polarity : 0 => CC1 is CC line, 1 => CC2 is CC line */ |
| uint8_t 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 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; |
| #endif |
| |
| /* 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]; |
| 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_current_image current_image; |
| #ifdef CONFIG_USB_PD_REV30 |
| /* PD Collision avoidance buffer */ |
| uint16_t ca_buffered; |
| uint16_t ca_header; |
| uint32_t ca_buffer[PDO_MAX_OBJECTS]; |
| enum tcpm_transmit_type ca_type; |
| /* protocol revision */ |
| uint8_t rev; |
| #endif |
| } pd[CONFIG_USB_PD_PORT_COUNT]; |
| |
| #ifdef CONFIG_COMMON_RUNTIME |
| static const char * const pd_state_names[] = { |
| "DISABLED", "SUSPENDED", |
| #ifdef CONFIG_USB_PD_DUAL_ROLE |
| "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", |
| #endif /* CONFIG_USB_PD_DUAL_ROLE */ |
| "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", |
| #ifdef CONFIG_USB_PD_DUAL_ROLE |
| "SRC_SWAP_INIT", "SRC_SWAP_SNK_DISABLE", "SRC_SWAP_SRC_DISABLE", |
| "SRC_SWAP_STANDBY", |
| #ifdef CONFIG_USBC_VCONN_SWAP |
| "VCONN_SWAP_SEND", "VCONN_SWAP_INIT", "VCONN_SWAP_READY", |
| #endif /* CONFIG_USBC_VCONN_SWAP */ |
| #endif /* CONFIG_USB_PD_DUAL_ROLE */ |
| "SOFT_RESET", "HARD_RESET_SEND", "HARD_RESET_EXECUTE", "BIST_RX", |
| "BIST_TX", |
| #ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE |
| "DRP_AUTO_TOGGLE", |
| #endif |
| }; |
| BUILD_ASSERT(ARRAY_SIZE(pd_state_names) == PD_STATE_COUNT); |
| #endif |
| |
| /* |
| * 4 entry rw_hash table of type-C devices that AP has firmware updates for. |
| */ |
| #ifdef CONFIG_COMMON_RUNTIME |
| #define RW_HASH_ENTRIES 4 |
| static struct ec_params_usb_pd_rw_hash_entry rw_hash_table[RW_HASH_ENTRIES]; |
| #endif |
| |
| int pd_comm_is_enabled(int port) |
| { |
| #ifdef CONFIG_COMMON_RUNTIME |
| return pd_comm_enabled[port]; |
| #else |
| return 1; |
| #endif |
| } |
| |
| 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; |
| } |
| |
| #ifdef CONFIG_USB_PD_REV30 |
| int pd_get_rev(int port) |
| { |
| return pd[port].rev; |
| } |
| |
| int pd_get_vdo_ver(int port) |
| { |
| return vdo_ver[pd[port].rev]; |
| } |
| #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 a DTS or TS capable of entering debug |
| * mode (eg. is presenting Rp/Rp or Rd/Rd). |
| */ |
| int pd_ts_dts_plugged(int port) |
| { |
| return pd[port].flags & PD_FLAGS_TS_DTS_PARTNER; |
| } |
| |
| /* Return true if partner port is known to be PD capable. */ |
| int pd_capable(int port) |
| { |
| return pd[port].flags & PD_FLAGS_PREVIOUS_PD_CONN; |
| } |
| |
| #ifdef CONFIG_USB_PD_DUAL_ROLE |
| void pd_vbus_low(int port) |
| { |
| pd[port].flags &= ~PD_FLAGS_VBUS_NEVER_LOW; |
| } |
| #endif |
| |
| int pd_is_vbus_present(int port) |
| { |
| #ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC |
| return tcpm_get_vbus_level(port); |
| #else |
| return pd_snk_is_vbus_provided(port); |
| #endif |
| } |
| |
| static void set_polarity(int port, int polarity) |
| { |
| tcpm_set_polarity(port, polarity); |
| #ifdef CONFIG_USBC_PPC_POLARITY |
| ppc_set_polarity(port, polarity); |
| #endif /* defined(CONFIG_USBC_PPC_POLARITY) */ |
| } |
| |
| #ifdef CONFIG_USBC_VCONN |
| static void set_vconn(int port, int enable) |
| { |
| /* |
| * We always need to tell the TCPC to enable Vconn first, otherwise some |
| * TCPCs get confused and think the CC line is in over voltage mode and |
| * immediately disconnects. If there is a PPC, both devices will |
| * potentially source Vconn, but that should be okay since Vconn has |
| * "make before break" electrical requirements when swapping anyway. |
| */ |
| tcpm_set_vconn(port, enable); |
| #ifdef CONFIG_USBC_PPC_VCONN |
| ppc_set_vconn(port, enable); |
| #endif |
| } |
| #endif /* defined(CONFIG_USBC_VCONN) */ |
| |
| #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) |
| static timestamp_t lpm_debounce_deadlines[CONFIG_USB_PD_PORT_COUNT]; |
| static int tasks_waiting_on_reset[CONFIG_USB_PD_PORT_COUNT]; |
| |
| /* 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())); |
| |
| lpm_debounce_deadlines[port].val = get_time().val + PD_LPM_DEBOUNCE_US; |
| if (pd[port].flags & PD_FLAGS_LPM_ENGAGED) { |
| CPRINTS("TCPC p%d Exited Low Power Mode via bus access", port); |
| pd[port].flags &= ~PD_FLAGS_LPM_ENGAGED; |
| } |
| } |
| |
| /* This can be called from any task. */ |
| void pd_device_accessed(int port) |
| { |
| const int current_task = task_get_current(); |
| |
| /* If not in the PD TASK that owns data, marshal to that task */ |
| if (current_task == PD_PORT_TO_TASK_ID(port)) { |
| /* Ignore any access to device while it is waking up */ |
| if (tasks_waiting_on_reset[port] & (1 << current_task)) |
| return; |
| |
| handle_device_access(port); |
| } |
| else |
| task_set_event(PD_PORT_TO_TASK_ID(port), |
| PD_EVENT_DEVICE_ACCESSED, 0); |
| } |
| |
| int pd_device_in_low_power(int port) |
| { |
| const int current_task = task_get_current(); |
| |
| /* |
| * 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(current_task) && |
| tasks_waiting_on_reset[port] & (1 << current_task)) |
| return 0; |
| |
| return pd[port].flags & PD_FLAGS_LPM_ENGAGED; |
| } |
| |
| /* This is only called from the PD tasks that owns the port. */ |
| static void request_low_power_mode(int port, int enable) |
| { |
| /* This should only be called from the PD task */ |
| assert(port == TASK_ID_TO_PD_PORT(task_get_current())); |
| |
| if (enable) |
| pd[port].flags |= PD_FLAGS_LPM_REQUESTED; |
| else |
| pd[port].flags &= ~PD_FLAGS_LPM_REQUESTED; |
| } |
| |
| static int reset_device_and_notify(int port) |
| { |
| int rv; |
| int task, waiting_tasks; |
| const int current_task_mask = 1 << task_get_current(); |
| |
| /* This should only be called from the PD task */ |
| assert(port == TASK_ID_TO_PD_PORT(task_get_current())); |
| |
| /* |
| * Signal that this task is actively waiting for a wake up, which we |
| * use to skip recursive wake calls within the tcpc_init method and |
| * prevent pd_access from changing the HW status until we are done. |
| */ |
| atomic_or(&tasks_waiting_on_reset[port], current_task_mask); |
| |
| rv = tcpm_init(port); |
| if (rv == EC_SUCCESS) |
| CPRINTS("TCPC p%d init ready", port); |
| else |
| CPRINTS("TCPC p%d init failed!", port); |
| |
| waiting_tasks = atomic_read_clear(&tasks_waiting_on_reset[port]); |
| |
| /* |
| * 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 */ |
| request_low_power_mode(port, 0); |
| |
| /* Wake up all waiting tasks (except this task). */ |
| waiting_tasks &= ~current_task_mask; |
| while (waiting_tasks) { |
| task = __fls(waiting_tasks); |
| waiting_tasks &= ~(1 << task); |
| task_set_event(task, TASK_EVENT_PD_AWAKE, 0); |
| } |
| |
| return rv; |
| } |
| |
| void pd_wait_for_wakeup(int port) |
| { |
| if (task_get_current() == PD_PORT_TO_TASK_ID(port)) { |
| /* 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(&tasks_waiting_on_reset[port], |
| 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, 0); |
| task_wait_event_mask(TASK_EVENT_PD_AWAKE, -1); |
| } |
| } |
| #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) |
| CPRINTS("TCPC p%d init ready", port); |
| else |
| CPRINTS("TCPC p%d init failed!", port); |
| |
| return rv; |
| } |
| |
| #endif /* CONFIG_USB_PD_TCPC_LOW_POWER */ |
| |
| /* Local convenience method for two method currently always called together. */ |
| #ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE |
| static void pd_set_drp_toggle(int port, int enable) |
| { |
| tcpm_set_drp_toggle(port, enable); |
| #ifdef CONFIG_USB_PD_TCPC_LOW_POWER |
| request_low_power_mode(port, enable); |
| #endif |
| } |
| #endif /* CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE */ |
| |
| #ifdef CONFIG_USB_PD_DUAL_ROLE |
| static int get_bbram_idx(int port) |
| { |
| switch (port) { |
| case 2: |
| return SYSTEM_BBRAM_IDX_PD2; |
| |
| case 1: |
| return SYSTEM_BBRAM_IDX_PD1; |
| |
| case 0: |
| return SYSTEM_BBRAM_IDX_PD0; |
| |
| default: |
| return -1; |
| } |
| } |
| |
| static int pd_get_saved_port_flags(int port, uint8_t *flags) |
| { |
| if (system_get_bbram(get_bbram_idx(port), flags) != EC_SUCCESS) { |
| CPRINTS("PD NVRAM FAIL"); |
| return EC_ERROR_UNKNOWN; |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| static void pd_set_saved_port_flags(int port, uint8_t flags) |
| { |
| if (system_set_bbram(get_bbram_idx(port), flags) != EC_SUCCESS) |
| CPRINTS("PD NVRAM FAIL"); |
| } |
| |
| static void pd_update_saved_port_flags(int port, uint8_t flag, uint8_t val) |
| { |
| uint8_t saved_flags; |
| |
| if (pd_get_saved_port_flags(port, &saved_flags) != EC_SUCCESS) |
| return; |
| |
| if (val) |
| saved_flags |= flag; |
| else |
| saved_flags &= ~flag; |
| |
| pd_set_saved_port_flags(port, saved_flags); |
| } |
| #endif /* defined(CONFIG_USB_PD_DUAL_ROLE) */ |
| |
| static inline void set_state(int port, enum pd_states next_state) |
| { |
| enum pd_states last_state = pd[port].task_state; |
| #ifdef CONFIG_LOW_POWER_IDLE |
| int i; |
| #endif |
| |
| set_state_timeout(port, 0, 0); |
| pd[port].task_state = next_state; |
| |
| if (last_state == next_state) |
| return; |
| |
| #ifdef CONFIG_USB_PD_DUAL_ROLE |
| #ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE |
| /* Clear flag to allow DRP auto toggle when possible */ |
| if (last_state != PD_STATE_DRP_AUTO_TOGGLE) |
| pd[port].flags &= ~PD_FLAGS_TCPC_DRP_TOGGLE; |
| #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) { |
| /* 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_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) { |
| #endif |
| /* |
| * If we are source, make sure VBUS is off and |
| * if PD REV3.0, restore RP. |
| */ |
| if (pd[port].power_role == PD_ROLE_SOURCE) { |
| /* |
| * If CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT is |
| * defined, Rp is reset as follows. |
| * If all ports are open, pd_power_supply_reset will |
| * change Rp to 3A for all ports. If the other port is |
| * occupied, it'll be given 3A and this port will be |
| * given 1.5A. |
| * In either case, this port is immediately reset to |
| * 1.5A by tcpm_select_rp_value. |
| */ |
| pd_power_supply_reset(port); |
| tcpm_select_rp_value(port, CONFIG_USB_PD_PULLUP); |
| 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; |
| pd[port].flags &= ~PD_FLAGS_RESET_ON_DISCONNECT_MASK; |
| #ifdef CONFIG_CHARGE_MANAGER |
| charge_manager_update_dualrole(port, CAP_UNKNOWN); |
| #endif |
| #ifdef CONFIG_USB_PD_ALT_MODE_DFP |
| pd_dfp_exit_mode(port, 0, 0); |
| #endif |
| /* |
| * Indicate that the port is disconnected so the board |
| * can restore state from any previous data swap. |
| */ |
| pd_execute_data_swap(port, PD_ROLE_DISCONNECTED); |
| #ifdef CONFIG_USBC_SS_MUX |
| usb_mux_set(port, TYPEC_MUX_NONE, USB_SWITCH_DISCONNECT, |
| pd[port].polarity); |
| #endif |
| /* Disable TCPC RX */ |
| tcpm_set_rx_enable(port, 0); |
| } |
| |
| #ifdef CONFIG_LOW_POWER_IDLE |
| /* If a PD device is attached then disable deep sleep */ |
| for (i = 0; i < CONFIG_USB_PD_PORT_COUNT; i++) { |
| if (pd_capable(i)) |
| break; |
| } |
| if (i == CONFIG_USB_PD_PORT_COUNT) |
| enable_sleep(SLEEP_MASK_USB_PD); |
| else |
| disable_sleep(SLEEP_MASK_USB_PD); |
| #endif |
| |
| if (debug_level >= 1) |
| CPRINTF("C%d st%d %s\n", port, next_state, |
| pd_state_names[next_state]); |
| else |
| 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; |
| } |
| |
| #ifdef CONFIG_USB_PD_REV30 |
| static void sink_can_xmit(int port, int rp) |
| { |
| tcpm_select_rp_value(port, rp); |
| tcpm_set_cc(port, TYPEC_CC_RP); |
| } |
| |
| static inline void pd_ca_reset(int port) |
| { |
| pd[port].ca_buffered = 0; |
| } |
| #endif |
| |
| 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, 0); |
| } |
| |
| static int pd_transmit(int port, enum tcpm_transmit_type type, |
| uint16_t header, const uint32_t *data) |
| { |
| int evt; |
| |
| /* If comms are disabled, do not transmit, return error */ |
| if (!pd_comm_is_enabled(port)) |
| return -1; |
| #ifdef CONFIG_USB_PD_REV30 |
| /* Source-coordinated collision avoidance */ |
| /* |
| * In order to avoid message collisions due to asynchronous Messaging |
| * sent from the Sink, the Source sets Rp to SinkTxOk 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. 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 |
| * PD R3 V1.1 Section 2.5.2. |
| * |
| * Note: a Sink can still send Hard Reset signaling at any time. |
| */ |
| if ((pd[port].rev == PD_REV30) && |
| (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 |
| * transmition is in progress and a collsion 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); |
| } else if (type != TCPC_TX_HARD_RESET) { |
| int cc1; |
| int cc2; |
| |
| tcpm_get_cc(port, &cc1, &cc2); |
| if (cc1 == TYPEC_CC_VOLT_SNK_1_5 || |
| cc2 == TYPEC_CC_VOLT_SNK_1_5) { |
| /* Sink can't transmit now. */ |
| /* Check if message is already buffered. */ |
| if (pd[port].ca_buffered) |
| return -1; |
| |
| /* Buffer message and send later. */ |
| pd[port].ca_type = type; |
| pd[port].ca_header = header; |
| memcpy(pd[port].ca_buffer, |
| data, sizeof(uint32_t) * |
| PD_HEADER_CNT(header)); |
| pd[port].ca_buffered = 1; |
| 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); |
| |
| #ifdef CONFIG_USB_PD_REV30 |
| /* |
| * If the source just completed a transmit, tell |
| * the sink it can transmit if it wants to. |
| */ |
| 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 (evt & TASK_EVENT_TIMER) |
| return -1; |
| |
| /* TODO: give different error condition for failed vs discarded */ |
| return pd[port].tx_status == TCPC_TX_COMPLETE_SUCCESS ? 1 : -1; |
| } |
| |
| #ifdef CONFIG_USB_PD_REV30 |
| static void pd_ca_send_pending(int port) |
| { |
| int cc1; |
| int cc2; |
| |
| /* Check if a message has been buffered. */ |
| if (!pd[port].ca_buffered) |
| return; |
| |
| tcpm_get_cc(port, &cc1, &cc2); |
| if ((cc1 != TYPEC_CC_VOLT_SNK_1_5) && |
| (cc2 != TYPEC_CC_VOLT_SNK_1_5)) |
| if (pd_transmit(port, pd[port].ca_type, |
| pd[port].ca_header, |
| pd[port].ca_buffer) < 0) |
| return; |
| |
| /* Message was sent, so free up the buffer. */ |
| pd[port].ca_buffered = 0; |
| } |
| #endif |
| |
| 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), 0); |
| |
| bit_len = pd_transmit(port, TCPC_TX_SOP, header, NULL); |
| if (debug_level >= 2) |
| CPRINTF("C%d CTRL[%d]>%d\n", port, type, bit_len); |
| |
| return bit_len; |
| } |
| |
| static int send_source_cap(int port) |
| { |
| 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), 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), 0); |
| |
| bit_len = pd_transmit(port, TCPC_TX_SOP, header, src_pdo); |
| 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, TCPC_TX_SOP, header, (uint32_t *)msg); |
| 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, TCPC_TX_SOP, header, &msg); |
| 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), 0); |
| |
| bit_len = pd_transmit(port, TCPC_TX_SOP, header, pd_snk_pdo); |
| 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), 0); |
| |
| bit_len = pd_transmit(port, TCPC_TX_SOP, header, &rdo); |
| 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), 0); |
| |
| bit_len = pd_transmit(port, TCPC_TX_SOP, header, &bdo); |
| 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) |
| { |
| pd[port].vdo_count = data_cnt + 1; |
| pd[port].vdo_data[0] = header[0]; |
| 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) |
| { |
| int rlen = 0; |
| uint32_t *rdata; |
| |
| 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; |
| } |
| } |
| |
| if (PD_VDO_SVDM(payload[0])) |
| rlen = pd_svdm(port, cnt, payload, &rdata); |
| else |
| rlen = pd_custom_vdm(port, cnt, payload, &rdata); |
| |
| if (rlen > 0) { |
| queue_vdm(port, rdata, &rdata[1], rlen - 1); |
| return; |
| } |
| if (debug_level >= 2) |
| CPRINTF("C%d Unhandled VDM VID %04x CMD %04x\n", |
| port, PD_VDO_VID(payload[0]), payload[0] & 0xFFFF); |
| } |
| |
| static void pd_set_data_role(int port, int 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); |
| |
| #ifdef CONFIG_USBC_SS_MUX |
| #ifdef CONFIG_USBC_SS_MUX_DFP_ONLY |
| /* |
| * Need to connect SS mux for if new data role is DFP. |
| * If new data role is UFP, then disconnect the SS mux. |
| */ |
| if (role == PD_ROLE_DFP) |
| usb_mux_set(port, TYPEC_MUX_USB, USB_SWITCH_CONNECT, |
| pd[port].polarity); |
| else |
| usb_mux_set(port, TYPEC_MUX_NONE, USB_SWITCH_DISCONNECT, |
| pd[port].polarity); |
| #else |
| usb_mux_set(port, TYPEC_MUX_USB, USB_SWITCH_CONNECT, |
| pd[port].polarity); |
| #endif |
| #endif |
| pd_update_roles(port); |
| } |
| |
| void pd_execute_hard_reset(int port) |
| { |
| if (pd[port].last_state == PD_STATE_HARD_RESET_SEND) |
| CPRINTF("C%d HARD RST TX\n", port); |
| else |
| CPRINTF("C%d HARD RST RX\n", port); |
| |
| pd[port].msg_id = 0; |
| #ifdef CONFIG_USB_PD_ALT_MODE_DFP |
| pd_dfp_exit_mode(port, 0, 0); |
| #endif |
| |
| #ifdef CONFIG_USB_PD_REV30 |
| pd[port].rev = PD_REV30; |
| pd_ca_reset(port); |
| #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); |
| } |
| |
| /* Set initial data role (matching power role) */ |
| pd_set_data_role(port, pd[port].power_role); |
| if (pd[port].power_role == PD_ROLE_SINK) { |
| /* 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 */ |
| |
| set_state(port, PD_STATE_SNK_HARD_RESET_RECOVER); |
| return; |
| } |
| #endif /* CONFIG_USB_PD_DUAL_ROLE */ |
| |
| /* We are a source, cut power */ |
| pd_power_supply_reset(port); |
| pd[port].src_recover = get_time().val + PD_T_SRC_RECOVER; |
| set_state(port, PD_STATE_SRC_HARD_RESET_RECOVER); |
| } |
| |
| static void execute_soft_reset(int port) |
| { |
| pd[port].msg_id = 0; |
| 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 < CONFIG_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; |
| |
| #ifdef CONFIG_CHARGE_MANAGER |
| int charging = (charge_manager_get_active_charge_port() == port); |
| #else |
| const int charging = 1; |
| #endif |
| |
| #ifdef CONFIG_USB_PD_CHECK_MAX_REQUEST_ALLOWED |
| int max_request_allowed = pd_is_max_request_allowed(); |
| #else |
| const int max_request_allowed = 1; |
| #endif |
| |
| /* Clear new power request */ |
| pd[port].new_power_request = 0; |
| |
| /* Build and send request RDO */ |
| /* |
| * If this port is not actively charging or we are not allowed to |
| * request the max voltage, then select vSafe5V |
| */ |
| res = pd_build_request(port, &rdo, &curr_limit, &supply_voltage, |
| charging && max_request_allowed ? |
| PD_REQUEST_MAX : PD_REQUEST_VSAFE5V); |
| |
| if (res != EC_SUCCESS) |
| /* |
| * If fail to choose voltage, do nothing, let source re-send |
| * source cap |
| */ |
| return -1; |
| |
| 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, uint32_t pdo) |
| { |
| #ifdef CONFIG_CHARGE_MANAGER |
| #ifdef CONFIG_USB_PD_ALT_MODE_DFP |
| int charge_whitelisted = |
| (pd[port].power_role == PD_ROLE_SINK && |
| pd_charge_from_device(pd_get_identity_vid(port), |
| pd_get_identity_pid(port))); |
| #else |
| const int charge_whitelisted = 0; |
| #endif |
| #endif |
| |
| /* can only parse PDO flags if type is fixed */ |
| if ((pdo & PDO_TYPE_MASK) != PDO_TYPE_FIXED) |
| return; |
| |
| #ifdef CONFIG_USB_PD_DUAL_ROLE |
| if (pdo & PDO_FIXED_DUAL_ROLE) |
| pd[port].flags |= PD_FLAGS_PARTNER_DR_POWER; |
| else |
| pd[port].flags &= ~PD_FLAGS_PARTNER_DR_POWER; |
| |
| if (pdo & PDO_FIXED_EXTERNAL) |
| pd[port].flags |= PD_FLAGS_PARTNER_EXTPOWER; |
| else |
| pd[port].flags &= ~PD_FLAGS_PARTNER_EXTPOWER; |
| |
| if (pdo & PDO_FIXED_COMM_CAP) |
| pd[port].flags |= PD_FLAGS_PARTNER_USB_COMM; |
| else |
| pd[port].flags &= ~PD_FLAGS_PARTNER_USB_COMM; |
| #endif |
| |
| if (pdo & PDO_FIXED_DATA_SWAP) |
| pd[port].flags |= PD_FLAGS_PARTNER_DR_DATA; |
| else |
| pd[port].flags &= ~PD_FLAGS_PARTNER_DR_DATA; |
| |
| #ifdef CONFIG_CHARGE_MANAGER |
| /* |
| * Treat device as a dedicated charger (meaning we should charge |
| * from it) if it does not support power swap, or if it is externally |
| * powered, or if we are a sink and the device identity matches a |
| * charging white-list. |
| */ |
| if (!(pd[port].flags & PD_FLAGS_PARTNER_DR_POWER) || |
| (pd[port].flags & PD_FLAGS_PARTNER_EXTPOWER) || |
| charge_whitelisted) |
| charge_manager_update_dualrole(port, CAP_DEDICATED); |
| else |
| charge_manager_update_dualrole(port, CAP_DUALROLE); |
| #endif |
| } |
| |
| static void handle_data_request(int port, uint16_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) |
| #ifdef CONFIG_USB_PD_VBUS_DETECT_NONE |
| || (pd[port].task_state == |
| PD_STATE_SNK_HARD_RESET_RECOVER) |
| #endif |
| || (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, payload[0]); |
| |
| 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 */ |
| #ifdef CONFIG_USB_PD_REV30 |
| /* |
| * Start Source-coordinated collision |
| * avoidance |
| */ |
| if (pd[port].rev == PD_REV30 && |
| pd[port].power_role == PD_ROLE_SOURCE) |
| sink_can_xmit(port, SINK_TX_OK); |
| #endif |
| 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, TCPC_TX_BIST_MODE_2, 0, |
| NULL); |
| /* 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, payload[0]); |
| 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; |
| #endif |
| case PD_DATA_VENDOR_DEF: |
| handle_vdm_request(port, cnt, payload); |
| 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 |
| static 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, int 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, uint16_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: |
| res = send_source_cap(port); |
| 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, REFUSE(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; |
| pd_set_power_role(port, PD_ROLE_SINK); |
| pd_update_roles(port); |
| 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) { |
| 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: |
| 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) { |
| /* |
| * Explicit Contract in place |
| * |
| * On reception of a WAIT message, transition to |
| * PD_STATE_SNK_READY after PD_T_SINK_REQUEST ms to |
| * send another reqest. |
| * |
| * On reception of a REJECT messag, transition to |
| * PD_STATE_SNK_READY but don't resend the request. |
| * |
| * NO Explicit Contract in place |
| * |
| * On reception of a WAIT or REJECT message, |
| * transition to PD_STATE_SNK_DISCOVERY |
| */ |
| if (pd[port].flags & PD_FLAGS_EXPLICIT_CONTRACT) { |
| /* We have an explicit contract */ |
| 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 */ |
| set_state(port, PD_STATE_SNK_READY); |
| } |
| } else { |
| /* No explicit contract */ |
| set_state(port, PD_STATE_SNK_DISCOVERY); |
| } |
| } |
| #endif |
| break; |
| case PD_CTRL_ACCEPT: |
| 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); |
| /* 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, REFUSE(pd[port].rev)); |
| } |
| #else |
| send_control(port, REFUSE(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, REFUSE(pd[port].rev)); |
| |
| } |
| 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, REFUSE(pd[port].rev)); |
| } |
| } |
| #else |
| send_control(port, REFUSE(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, uint16_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); |
| |
| 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 pd_vdm_send_state_machine(int port) |
| { |
| int res; |
| uint16_t header; |
| |
| 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; |
| |
| /* Prepare 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), 0); |
| res = pd_transmit(port, TCPC_TX_SOP, header, |
| pd[port].vdo_data); |
| 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; |
| 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; |
| } |
| |
| #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; |
| int try_src = 0; |
| |
| #ifndef CONFIG_CHARGER |
| int batt_soc = board_get_battery_soc(); |
| #else |
| int batt_soc = charge_get_percent(); |
| #endif |
| |
| try_src = 0; |
| for (i = 0; i < CONFIG_USB_PD_PORT_COUNT; i++) |
| try_src |= drp_state[i] == PD_DRP_TOGGLE_ON; |
| |
| /* |
| * Enable try source when dual-role toggling AND battery is present |
| * and at some minimum percentage. |
| */ |
| pd_try_src_enable = try_src && |
| batt_soc >= CONFIG_USB_PD_TRY_SRC_MIN_BATT_SOC; |
| #if defined(CONFIG_BATTERY_PRESENT_CUSTOM) || \ |
| defined(CONFIG_BATTERY_PRESENT_GPIO) |
| /* |
| * When battery is cutoff in ship mode it may not be reliable to |
| * check if battery is present with its state of charge. |
| * Also check if battery is initialized and ready to provide power. |
| */ |
| pd_try_src_enable &= (battery_is_present() == BP_YES); |
| #endif |
| |
| /* |
| * 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 < CONFIG_USB_PD_PORT_COUNT; i++) |
| pd[i].flags &= ~PD_FLAGS_TRY_SRC; |
| |
| } |
| DECLARE_HOOK(HOOK_BATTERY_SOC_CHANGE, pd_update_try_source, HOOK_PRIO_DEFAULT); |
| #endif |
| |
| void pd_set_dual_role(int port, enum pd_dual_role_states state) |
| { |
| int i; |
| |
| drp_state[port] = state; |
| |
| #ifdef CONFIG_USB_PD_TRY_SRC |
| pd_update_try_source(); |
| #endif |
| |
| /* Inform PD tasks of dual role change. */ |
| for (i = 0; i < CONFIG_USB_PD_PORT_COUNT; i++) |
| task_set_event(PD_PORT_TO_TASK_ID(i), |
| PD_EVENT_UPDATE_DUAL_ROLE, 0); |
| } |
| |
| 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 either toggle off |
| * or debug accessory toggle only and we are in the source |
| * disconnected state). |
| */ |
| if (pd[port].power_role == PD_ROLE_SOURCE && |
| ((drp_state[port] == PD_DRP_FORCE_SINK && !pd_ts_dts_plugged(port)) |
| || (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 (pd[port].power_role == PD_ROLE_SINK && |
| drp_state[port] == PD_DRP_FORCE_SOURCE) { |
| pd_set_power_role(port, PD_ROLE_SOURCE); |
| set_state(port, PD_STATE_SRC_DISCONNECTED); |
| tcpm_set_cc(port, TYPEC_CC_RP); |
| } |
| |
| #if defined(CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE) && \ |
| defined(CONFIG_USB_PD_TCPC_LOW_POWER) |
| /* When switching drp mode, make sure tcpc is out of standby mode */ |
| pd_set_drp_toggle(port, 0); |
| #endif |
| } |
| |
| int pd_get_role(int port) |
| { |
| return pd[port].power_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; |
| } |
| |
| /* |
| * 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; |
| int explicit_contract_in_place; |
| uint8_t flags; |
| |
| pd_get_saved_port_flags(port, &flags); |
| explicit_contract_in_place = (flags & PD_BBRMFLG_EXPLICIT_CONTRACT); |
| |
| /* |
| * If an explicit contract is in place and 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 (explicit_contract_in_place && pd_comm_is_enabled(port)) |
| return; |
| |
| /* If we just lost power, don't apply Rp. */ |
| if (!explicit_contract_in_place || |
| system_get_reset_flags() & |
| (RESET_FLAG_BROWNOUT | 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"); |
| 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 */ |
| |
| int pd_get_polarity(int port) |
| { |
| return pd[port].polarity; |
| } |
| |
| int 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_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; |
| } |
| |
| /** |
| * Returns whether the sink has detected a Rp resistor on the other side. |
| */ |
| static inline int cc_is_rp(int cc) |
| { |
| return (cc == TYPEC_CC_VOLT_SNK_DEF) || (cc == TYPEC_CC_VOLT_SNK_1_5) || |
| (cc == TYPEC_CC_VOLT_SNK_3_0); |
| } |
| |
| /* |
| * CC values for regular sources and Debug sources (aka DTS) |
| * |
| * Source type Mode of Operation CC1 CC2 |
| * --------------------------------------------- |
| * Regular Default USB Power RpUSB Open |
| * Regular USB-C @ 1.5 A Rp1A5 Open |
| * Regular USB-C @ 3 A Rp3A0 Open |
| * DTS Default USB Power Rp3A0 Rp1A5 |
| * DTS USB-C @ 1.5 A Rp1A5 RpUSB |
| * DTS USB-C @ 3 A Rp3A0 RpUSB |
| */ |
| |
| /** |
| * Returns the polarity of a Sink. |
| */ |
| static inline int get_snk_polarity(int cc1, int cc2) |
| { |
| /* the following assumes: |
| * TYPEC_CC_VOLT_SNK_3_0 > TYPEC_CC_VOLT_SNK_1_5 |
| * TYPEC_CC_VOLT_SNK_1_5 > TYPEC_CC_VOLT_SNK_DEF |
| * TYPEC_CC_VOLT_SNK_DEF > TYPEC_CC_VOLT_OPEN |
| */ |
| return (cc2 > cc1); |
| } |
| |
| #if defined(CONFIG_CHARGE_MANAGER) |
| /** |
| * Returns type C current limit (mA) based upon cc_voltage (mV). |
| */ |
| static typec_current_t get_typec_current_limit(int polarity, int cc1, int cc2) |
| { |
| typec_current_t charge; |
| int cc = polarity ? cc2 : cc1; |
| int cc_alt = polarity ? cc1 : cc2; |
| |
| if (cc == TYPEC_CC_VOLT_SNK_3_0 && cc_alt != TYPEC_CC_VOLT_SNK_1_5) |
| charge = 3000; |
| else if (cc == TYPEC_CC_VOLT_SNK_1_5) |
| charge = 1500; |
| else |
| charge = 0; |
| |
| if (cc_is_rp(cc_alt)) |
| charge |= TYPEC_CURRENT_DTS_MASK; |
| |
| return charge; |
| } |
| |
| /** |
| * 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 < CONFIG_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 < CONFIG_USB_PD_PORT_COUNT; i++) |
| drp_state[i] = PD_DRP_TOGGLE_OFF; |
| else /* CHIPSET_STATE_ON */ |
| for (i = 0; i < CONFIG_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 at init if we're in RO and locked. */ |
| if (!system_is_in_rw() && system_is_locked()) |
| enable = 0; |
| #ifdef CONFIG_VBOOT_EFS |
| if (vboot_need_pd_comm()) |
| enable = 1; |
| #endif |
| #endif |
| for (i = 0; i < CONFIG_USB_PD_PORT_COUNT; i++) |
| pd_comm_enabled[i] = enable; |
| CPRINTS("PD comm %sabled", enable ? "en" : "dis"); |
| |
| initialized = 1; |
| } |
| #endif /* CONFIG_COMMON_RUNTIME */ |
| |
| #ifndef CONFIG_USB_PD_TCPC |
| 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 |
| |
| #ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE |
| #define DRP_AUTO_TOGGLE_ALLOWED_DELAY_US (5 * SECOND) |
| static uint8_t auto_toggle_allowed; |
| static void allow_drp_auto_toggle(void) |
| { |
| auto_toggle_allowed = 1; |
| } |
| DECLARE_DEFERRED(allow_drp_auto_toggle); |
| #endif /* CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE */ |
| |
| void pd_task(void *u) |
| { |
| int head; |
| int port = TASK_ID_TO_PD_PORT(task_get_current()); |
| uint32_t payload[7]; |
| int timeout = 10*MSEC; |
| int 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; |
| int caps_count = 0, hard_reset_sent = 0; |
| int snk_cap_count = 0; |
| int evt; |
| |
| #ifdef CONFIG_COMMON_RUNTIME |
| pd_init_tasks(); |
| #endif |
| |
| /* Ensure the power supply is in the default state */ |
| pd_power_supply_reset(port); |
| |
| /* Initialize TCPM driver and wait for TCPC to be ready */ |
| res = reset_device_and_notify(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 *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%lx", |
| 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; |
| pd_ca_reset(port); |
| #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)) { |
| if (saved_flgs & PD_BBRMFLG_EXPLICIT_CONTRACT) { |
| 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); |
| |
| /* |
| * 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; |
| |
| /* |
| * 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. |
| */ |
| task_set_event(task_get_current(), |
| PD_EVENT_TCPC_RESET, |
| 0); |
| } |
| } |
| #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_pe_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 |
| #ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE |
| /* |
| * Delay entering PD_STATE_DRP_AUTO_TOGGLE by |
| * DRP_AUTO_TOGGLE_ALLOWED_DELAY_US, such that we don't dismiss a |
| * charger from being detected as a source. Otherwise, shortly after |
| * init, we may think that nothing is connected and remain in the auto |
| * toggle state and never charge our battery. |
| */ |
| hook_call_deferred(&allow_drp_auto_toggle_data, |
| DRP_AUTO_TOGGLE_ALLOWED_DELAY_US); |
| #endif /* CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE */ |
| |
| while (1) { |
| #ifdef CONFIG_USB_PD_REV30 |
| /* send any pending messages */ |
| pd_ca_send_pending(port); |
| #endif |
| /* 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, TCPC_TX_HARD_RESET, 0, NULL); |
| } |
| |
| /* wait for next event/packet or timeout expiration */ |
| evt = task_wait_event(timeout); |
| |
| #ifdef CONFIG_USB_PD_TCPC_LOW_POWER |
| if (evt & PD_EVENT_DEVICE_ACCESSED) |
| handle_device_access(port); |
| #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) { |
| int 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 { |
| pd[port].polarity = |
| (cc1 != TYPEC_CC_VOLT_RD); |
| } |
| } 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))) { |
| 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)); |
| } |
| } |
| #endif |
| |
| /* process any potential incoming message */ |
| incoming_packet = evt & PD_EVENT_RX; |
| if (incoming_packet) { |
| if (!tcpm_get_message(port, payload, &head)) |
| handle_request(port, head, payload); |
| } |
| |
| 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; |
| tcpm_get_cc(port, &cc1, &cc2); |
| #ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE |
| /* |
| * Attempt TCPC auto DRP toggle if it is |
| * allowed, not already auto toggling, and not try.src |
| */ |
| if (auto_toggle_supported && |
| auto_toggle_allowed && |
| !(pd[port].flags & PD_FLAGS_TCPC_DRP_TOGGLE) && |
| !(pd[port].flags & PD_FLAGS_TRY_SRC) && |
| (cc1 == TYPEC_CC_VOLT_OPEN && |
| cc2 == TYPEC_CC_VOLT_OPEN)) { |
| set_state(port, PD_STATE_DRP_AUTO_TOGGLE); |
| timeout = 2*MSEC; |
| break; |
| } |
| #endif |
| |
| /* Vnc monitoring */ |
| if ((cc1 == TYPEC_CC_VOLT_RD || |
| cc2 == TYPEC_CC_VOLT_RD) || |
| (cc1 == TYPEC_CC_VOLT_RA && |
| cc2 == TYPEC_CC_VOLT_RA)) { |
| #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); |
| } |
| #if defined(CONFIG_USB_PD_DUAL_ROLE) |
| /* |
| * Try.SRC state is embedded here. Wait for SNK |
| * detect, or if timer expires, transition to |
| * SNK_DISCONNETED. |
| * |
| * If Try.SRC state is not active, then this block |
| * handles the normal DRP toggle from SRC->SNK |
| */ |
| else if ((pd[port].flags & PD_FLAGS_TRY_SRC && |
| get_time().val >= pd[port].try_src_marker) || |
| (!(pd[port].flags & PD_FLAGS_TRY_SRC) && |
| drp_state[port] != PD_DRP_FORCE_SOURCE && |
| drp_state[port] != PD_DRP_FREEZE && |
| get_time().val >= next_role_swap)) { |
| pd_set_power_role(port, PD_ROLE_SINK); |
| set_state(port, PD_STATE_SNK_DISCONNECTED); |
| tcpm_set_cc(port, TYPEC_CC_RD); |
| next_role_swap = get_time().val + PD_T_DRP_SNK; |
| pd[port].try_src_marker = get_time().val |
| + PD_T_TRY_WAIT; |
| |
| /* Swap states quickly */ |
| timeout = 2*MSEC; |
| } |
| #endif |
| break; |
| case PD_STATE_SRC_DISCONNECTED_DEBOUNCE: |
| timeout = 20*MSEC; |
| tcpm_get_cc(port, &cc1, &cc2); |
| |
| if (cc1 == TYPEC_CC_VOLT_RD && |
| cc2 == TYPEC_CC_VOLT_RD) { |
| /* Debug accessory */ |
| new_cc_state = PD_CC_DEBUG_ACC; |
| } else if (cc1 == TYPEC_CC_VOLT_RD || |
| cc2 == TYPEC_CC_VOLT_RD) { |
| /* UFP attached */ |
| new_cc_state = PD_CC_UFP_ATTACHED; |
| } else if (cc1 == TYPEC_CC_VOLT_RA && |
| cc2 == TYPEC_CC_VOLT_RA) { |
| /* Audio accessory */ |
| new_cc_state = PD_CC_AUDIO_ACC; |
| } else { |
| /* No UFP */ |
| set_state(port, PD_STATE_SRC_DISCONNECTED); |
| timeout = 5*MSEC; |
| break; |
| } |
| /* If in Try.SRC state, then don't need to debounce */ |
| if (!(pd[port].flags & PD_FLAGS_TRY_SRC)) { |
| /* 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; |
| } else if (get_time().val < |
| pd[port].cc_debounce) { |
| break; |
| } |
| } |
| |
| /* Debounce complete */ |
| /* UFP is attached */ |
| if (new_cc_state == PD_CC_UFP_ATTACHED || |
| new_cc_state == PD_CC_DEBUG_ACC) { |
| pd[port].polarity = (cc1 != TYPEC_CC_VOLT_RD); |
| set_polarity(port, pd[port].polarity); |
| |
| /* initial data role for source is DFP */ |
| pd_set_data_role(port, PD_ROLE_DFP); |
| |
| if (new_cc_state == PD_CC_DEBUG_ACC) |
| pd[port].flags |= |
| PD_FLAGS_TS_DTS_PARTNER; |
| |
| #ifdef CONFIG_USBC_VCONN |
| /* |
| * Start sourcing Vconn before Vbus to ensure |
| * we are within USB Type-C Spec 1.3 tVconnON |
| */ |
| set_vconn(port, 1); |
| pd[port].flags |= PD_FLAGS_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[port].flags &= ~PD_FLAGS_VCONN_ON; |
| #endif /* CONFIG_USBC_VCONN */ |
| #ifdef CONFIG_USBC_SS_MUX |
| usb_mux_set(port, TYPEC_MUX_NONE, |
| USB_SWITCH_DISCONNECT, |
| pd[port].polarity); |
| #endif /* CONFIG_USBC_SS_MUX */ |
| break; |
| } |
| #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; |
| } |
| |
| /* Enable VBUS */ |
| timeout = 10*MSEC; |
| if (pd_set_power_supply_ready(port)) { |
| set_state(port, PD_STATE_SRC_DISCONNECTED); |
| break; |
| } |
| #ifdef CONFIG_USB_PD_TCPM_TCPCI |
| /* |
| * 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 */ |
| |
| set_state(port, PD_STATE_SRC_STARTUP); |
| break; |
| case PD_STATE_SRC_STARTUP: |
| /* 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: |
| if (pd[port].last_state != pd[port].task_state) { |
| /* |
| * 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) { |
| /* Query capabilities of the other side */ |
| res = send_source_cap(port); |
| /* 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; |
| caps_count++; |
| } |
| } |
| 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_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; |
| /* it'a 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 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); |
| 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; |
| 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; |
| } |
| |
| /* 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; |
| } |
| |
| 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: |
| /* Turn power off */ |
| if (pd[port].last_state != pd[port].task_state) { |
| pd_power_supply_reset(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; |
| } |
| /* Switch to Rd and swap roles to sink */ |
| tcpm_set_cc(port, TYPEC_CC_RD); |
| pd_set_power_role(port, PD_ROLE_SINK); |
| /* 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 |
| CPRINTS("TCPC p%d 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 |
| rstatus = tcpm_release(port); |
| if (rstatus != 0 && rstatus != EC_ERROR_UNIMPLEMENTED) |
| CPRINTS("TCPC p%d release failed!", port); |
| #endif |
| /* Wait for resume */ |
| while (pd[port].task_state == PD_STATE_SUSPENDED) |
| task_wait_event(-1); |
| #ifdef CONFIG_USB_PD_TCPC |
| pd_hw_init(port, PD_ROLE_DEFAULT(port)); |
| CPRINTS("TCPC p%d resumed!", port); |
| #else |
| if (rstatus != EC_ERROR_UNIMPLEMENTED && |
| pd_restart_tcpc(port) != 0) { |
| /* stay in PD_STATE_SUSPENDED */ |
| CPRINTS("TCPC p%d restart failed!", port); |
| break; |
| } |
| set_state(port, PD_DEFAULT_STATE(port)); |
| CPRINTS("TCPC p%d 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 |
| tcpm_get_cc(port, &cc1, &cc2); |
| |
| #ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE |
| /* |
| * Attempt TCPC auto DRP toggle if it is |
| * allowed, not already auto toggling, and not try.src |
| */ |
| if (auto_toggle_supported && |
| auto_toggle_allowed && |
| !(pd[port].flags & PD_FLAGS_TCPC_DRP_TOGGLE) && |
| !(pd[port].flags & PD_FLAGS_TRY_SRC) && |
| (cc1 == TYPEC_CC_VOLT_OPEN && |
| cc2 == TYPEC_CC_VOLT_OPEN)) { |
| set_state(port, PD_STATE_DRP_AUTO_TOGGLE); |
| timeout = 2*MSEC; |
| break; |
| } |
| #endif |
| |
| /* Source connection monitoring */ |
| if (cc1 != TYPEC_CC_VOLT_OPEN || |
| cc2 != TYPEC_CC_VOLT_OPEN) { |
| 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; |
| |
| /* Swap states quickly */ |
| timeout = 2*MSEC; |
| } |
| 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_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_TRY_SRC; |
| /* 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 */ |
| pd[port].polarity = get_snk_polarity(cc1, cc2); |
| 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); |
| #if defined(CONFIG_CHARGE_MANAGER) |
| typec_curr = get_typec_current_limit(pd[port].polarity, |
| cc1, cc2); |
| typec_set_input_current_limit( |
| port, typec_curr, TYPE_C_VOLTAGE); |
| #endif |
| /* 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_DEBUG_ACC) { |
| pd[port].flags |= PD_FLAGS_CHECK_PR_ROLE | |
| PD_FLAGS_CHECK_DR_ROLE | |
| PD_FLAGS_CHECK_IDENTITY; |
| if (new_cc_state == PD_CC_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; |
| #ifdef CONFIG_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 |
| /* 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) { |
| #ifdef CONFIG_USB_PD_TCPM_TCPCI |
| /* |
| * After transmitting hard reset, TCPM writes |
| * to RECEIVE_MESSAGE register to enable |
| * PD message passing. |
| */ |
| if (pd_comm_is_enabled(port)) |
| tcpm_set_rx_enable(port, 1); |
| #endif /* CONFIG_USB_PD_TCPM_TCPCI */ |
| |
| /* 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 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. |
| */ |
| 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 != 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 = 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) { |
| 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 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; |
| } |
| |
| /* 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; |
| } |
| |
| /* 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 + PD_VCONN_SWAP_DELAY, |
| 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[port].flags |= PD_FLAGS_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[port].flags &= ~PD_FLAGS_VCONN_ON; |
| set_state_timeout(port, |
| get_time().val + PD_VCONN_SWAP_DELAY, |
| 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 */ |
| 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; |
| #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 |
| |
| /* try sending hard reset until it succeeds */ |
| if (!hard_reset_sent) { |
| if (pd_transmit(port, TCPC_TX_HARD_RESET, |
| 0, NULL) < 0) { |
| timeout = 10*MSEC; |
| break; |
| } |
| |
| /* successfully sent hard reset */ |
| 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, TCPC_TX_BIST_MODE_2, 0, NULL); |
| /* 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_states next_state; |
| |
| assert(auto_toggle_supported); |
| |
| /* |
| * 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; |
| |
| /* Check for connection */ |
| tcpm_get_cc(port, &cc1, &cc2); |
| |
| /* Set to appropriate port state */ |
| if (cc1 == TYPEC_CC_VOLT_OPEN && |
| cc2 == TYPEC_CC_VOLT_OPEN) |
| /* nothing connected, keep toggling*/ |
| next_state = PD_STATE_DRP_AUTO_TOGGLE; |
| else if ((cc_is_rp(cc1) || cc_is_rp(cc2)) && |
| drp_state[port] != PD_DRP_FORCE_SOURCE) |
| /* SNK allowed unless ForceSRC */ |
| next_state = PD_STATE_SNK_DISCONNECTED; |
| else if (((cc1 == TYPEC_CC_VOLT_RD || |
| cc2 == TYPEC_CC_VOLT_RD) || |
| (cc1 == TYPEC_CC_VOLT_RA && |
| cc2 == TYPEC_CC_VOLT_RA)) && |
| (drp_state[port] != PD_DRP_TOGGLE_OFF && |
| drp_state[port] != PD_DRP_FORCE_SINK)) |
| /* SRC allowed unless ForceSNK or Toggle Off */ |
| next_state = PD_STATE_SRC_DISCONNECTED; |
| else |
| /* Anything else, keep toggling */ |
| next_state = PD_STATE_DRP_AUTO_TOGGLE; |
| |
| if (next_state != PD_STATE_DRP_AUTO_TOGGLE) { |
| pd_set_drp_toggle(port, 0); |
| } |
| |
| if (next_state == PD_STATE_SNK_DISCONNECTED) { |
| tcpm_set_cc(port, TYPEC_CC_RD); |
| pd_set_power_role(port, PD_ROLE_SINK); |
| timeout = 2*MSEC; |
| } else if (next_state == PD_STATE_SRC_DISCONNECTED) { |
| tcpm_set_cc(port, TYPEC_CC_RP); |
| pd_set_power_role(port, PD_ROLE_SOURCE); |
| timeout = 2*MSEC; |
| } else { |
| /* |
| * Staying in PD_STATE_DRP_AUTO_TOGGLE, |
| * always enter low power mode, and auto-toggle |
| * while in low power mode if drp_state allows |
| * us to be dual role. |
| */ |
| if (drp_state[port] == PD_DRP_TOGGLE_ON) |
| tcpm_set_drp_toggle(port, 1); |
| request_low_power_mode(port, 1); |
| pd[port].flags |= PD_FLAGS_TCPC_DRP_TOGGLE; |
| timeout = -1; |
| } |
| set_state(port, next_state); |
| |
| break; |
| } |
| #endif |
| 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)) { |
| const int64_t time_left = |
| lpm_debounce_deadlines[port].val - now.val; |
| if (time_left <= 0) { |
| pd[port].flags |= PD_FLAGS_LPM_ENGAGED; |
| tcpm_enter_low_power_mode(port); |
| CPRINTS("TCPC p%d 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 (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_TRY_WAIT; |
| /* 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 we are not recovering |
| * a hard reset. |
| */ |
| if (pd[port].power_role == PD_ROLE_SINK && |
| !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 < CONFIG_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 < CONFIG_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 < CONFIG_USB_PD_PORT_COUNT; i++) { |
| pd_set_dual_role(i, PD_DRP_TOGGLE_OFF); |
| pd[i].flags |= PD_FLAGS_CHECK_IDENTITY; |
| } |
| CPRINTS("PD:S5->S3"); |
| } |
| DECLARE_HOOK(HOOK_CHIPSET_STARTUP, pd_chipset_startup, HOOK_PRIO_DEFAULT); |
| |
| static void pd_chipset_shutdown(void) |
| { |
| int i; |
| |
| for (i = 0; i < CONFIG_USB_PD_PORT_COUNT; i++) |
| pd_set_dual_role(i, PD_DRP_FORCE_SINK); |
| 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 |
| |
| /* |
| * (enable=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. |
| * |
| * (enable=0) force pd_task out of the suspended state and into the |
| * port's default state. |
| */ |
| |
| void pd_set_suspend(int port, int enable) |
| { |
| int tries = 300; |
| |
| if (enable) { |
| 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) |
| CPRINTS("TCPC p%d set_suspend failed!", port); |
| } else { |
| if (pd[port].task_state != PD_STATE_SUSPENDED) |
| CPRINTS("TCPC p%d suspend disable request " |
| "while not suspended!", port); |
| set_state(port, PD_DEFAULT_STATE(port)); |
| task_wake(PD_PORT_TO_TASK_ID(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_CMD_PD) && defined(CONFIG_CMD_PD_FLASH) |
| static int hex8tou32(char *str, uint32_t *val) |
| { |
| char *ptr = str; |
| uint32_t tmp = 0; |
| |
| while (*ptr) { |
| char c = *ptr++; |
| if (c >= '0' && c <= '9') |
| tmp = (tmp << 4) + (c - '0'); |
| else if (c >= 'A' && c <= 'F') |
| tmp = (tmp << 4) + (c - 'A' + 10); |
| else if (c >= 'a' && c <= 'f') |
| tmp = (tmp << 4) + (c - 'a' + 10); |
| else |
| return EC_ERROR_INVAL; |
| } |
| if (ptr != str + 8) |
| return EC_ERROR_INVAL; |
| *val = tmp; |
| return EC_SUCCESS; |
| } |
| |
| static int remote_flashing(int argc, char **argv) |
| { |
| int port, cnt, cmd; |
| uint32_t data[VDO_MAX_SIZE-1]; |
| char *e; |
| static int flash_offset[CONFIG_USB_PD_PORT_COUNT]; |
| |
| if (argc < 4 || argc > (VDO_MAX_SIZE + 4 - 1)) |
| return EC_ERROR_PARAM_COUNT; |
| |
| port = strtoi(argv[1], &e, 10); |
| if (*e || port >= CONFIG_USB_PD_PORT_COUNT) |
| return EC_ERROR_PARAM2; |
| |
| cnt = 0; |
| if (!strcasecmp(argv[3], "erase")) { |
| cmd = VDO_CMD_FLASH_ERASE; |
| flash_offset[port] = 0; |
| ccprintf("ERASE ..."); |
| } else if (!strcasecmp(argv[3], "reboot")) { |
| cmd = VDO_CMD_REBOOT; |
| ccprintf("REBOOT ..."); |
| } else if (!strcasecmp(argv[3], "signature")) { |
| cmd = VDO_CMD_ERASE_SIG; |
| ccprintf("ERASE SIG ..."); |
| } else if (!strcasecmp(argv[3], "info")) { |
| cmd = VDO_CMD_READ_INFO; |
| ccprintf("INFO..."); |
| } else if (!strcasecmp(argv[3], "version")) { |
| cmd = VDO_CMD_VERSION; |
| ccprintf("VERSION..."); |
| } else { |
| int i; |
| argc -= 3; |
| for (i = 0; i < argc; i++) |
| if (hex8tou32(argv[i+3], data + i)) |
| return EC_ERROR_INVAL; |
| cmd = VDO_CMD_FLASH_WRITE; |
| cnt = argc; |
| ccprintf("WRITE %d @%04x ...", argc * 4, |
| flash_offset[port]); |
| flash_offset[port] += argc * 4; |
| } |
| |
| pd_send_vdm(port, USB_VID_GOOGLE, cmd, data, cnt); |
| |
| /* Wait until VDM is done */ |
| while (pd[port].vdm_state > 0) |
| task_wait_event(100*MSEC); |
| |
| ccprintf("DONE %d\n", pd[port].vdm_state); |
| return EC_SUCCESS; |
| } |
| #endif /* defined(CONFIG_CMD_PD) && defined(CONFIG_CMD_PD_FLASH) */ |
| |
| #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, 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 * (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")) { |
| #ifndef CONFIG_USB_PD_DEBUG_LEVEL |
| int level; |
| |
| if (argc >= 3) { |
| level = strtoi(argv[2], &e, 10); |
| if (*e) |
| return EC_ERROR_PARAM2; |
| debug_level = level; |
| } else |
| #endif |
| ccprintf("dump level: %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 < 2) { |
| return EC_ERROR_PARAM_COUNT; |
| } else 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 |
| /* command: pd <port> <subcmd> [args] */ |
| port = strtoi(argv[1], &e, 10); |
| if (argc < 3) |
| return EC_ERROR_PARAM_COUNT; |
| if (*e || port >= CONFIG_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", system_image_copy_t_to_string( |
| (enum system_image_copy_t)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], "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: %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_state_names[pd[port].task_state], |
| pd[port].flags); |
| } else { |
| return EC_ERROR_PARAM1; |
| } |
| |
| return EC_SUCCESS; |
| } |
| DECLARE_CONSOLE_COMMAND(pd, command_pd, |
| "dualrole|dump|rwhashtable" |
| "|trysrc [0|1]\n\t<port> " |
| "[tx|bist_rx|bist_tx|charger|clock|dev|disable|enable" |
| "|soft|hash|hard|ping|state|swap [power|data]|" |
| "vdm [ping | curr | vers]]", |
| "USB PD"); |
| |
| #ifdef HAS_TASK_HOSTCMD |
| |
| static int hc_pd_ports(struct host_cmd_handler_args *args) |
| { |
| struct ec_response_usb_pd_ports *r = args->response; |
| r->num_ports = CONFIG_USB_PD_PORT_COUNT; |
| |
| args->response_size = sizeof(*r); |
| return EC_RES_SUCCESS; |
| } |
| DECLARE_HOST_COMMAND(EC_CMD_USB_PD_PORTS, |
| hc_pd_ports, |
| EC_VER_MASK(0)); |
| |
| static const enum pd_dual_role_states dual_role_map[USB_PD_CTRL_ROLE_COUNT] = { |
| [USB_PD_CTRL_ROLE_TOGGLE_ON] = PD_DRP_TOGGLE_ON, |
| [USB_PD_CTRL_ROLE_TOGGLE_OFF] = PD_DRP_TOGGLE_OFF, |
| [USB_PD_CTRL_ROLE_FORCE_SINK] = PD_DRP_FORCE_SINK, |
| [USB_PD_CTRL_ROLE_FORCE_SOURCE] = PD_DRP_FORCE_SOURCE, |
| [USB_PD_CTRL_ROLE_FREEZE] = PD_DRP_FREEZE, |
| }; |
| |
| #ifdef CONFIG_USBC_SS_MUX |
| static const enum typec_mux typec_mux_map[USB_PD_CTRL_MUX_COUNT] = { |
| [USB_PD_CTRL_MUX_NONE] = TYPEC_MUX_NONE, |
| [USB_PD_CTRL_MUX_USB] = TYPEC_MUX_USB, |
| [USB_PD_CTRL_MUX_AUTO] = TYPEC_MUX_DP, |
| [USB_PD_CTRL_MUX_DP] = TYPEC_MUX_DP, |
| [USB_PD_CTRL_MUX_DOCK] = TYPEC_MUX_DOCK, |
| }; |
| #endif |
| |
| static int hc_usb_pd_control(struct host_cmd_handler_args *args) |
| { |
| const struct ec_params_usb_pd_control *p = args->params; |
| struct ec_response_usb_pd_control_v1 *r_v1 = args->response; |
| struct ec_response_usb_pd_control *r = args->response; |
| |
| if (p->port >= CONFIG_USB_PD_PORT_COUNT) |
| return EC_RES_INVALID_PARAM; |
| |
| if (p->role >= USB_PD_CTRL_ROLE_COUNT || |
| p->mux >= USB_PD_CTRL_MUX_COUNT) |
| return EC_RES_INVALID_PARAM; |
| |
| if (p->role != USB_PD_CTRL_ROLE_NO_CHANGE) |
| pd_set_dual_role(p->port, dual_role_map[p->role]); |
| |
| #ifdef CONFIG_USBC_SS_MUX |
| if (p->mux != USB_PD_CTRL_MUX_NO_CHANGE) |
| usb_mux_set(p->port, typec_mux_map[p->mux], |
| typec_mux_map[p->mux] == TYPEC_MUX_NONE ? |
| USB_SWITCH_DISCONNECT : |
| USB_SWITCH_CONNECT, |
| pd_get_polarity(p->port)); |
| #endif /* CONFIG_USBC_SS_MUX */ |
| |
| if (p->swap == USB_PD_CTRL_SWAP_DATA) |
| pd_request_data_swap(p->port); |
| #ifdef CONFIG_USB_PD_DUAL_ROLE |
| else if (p->swap == USB_PD_CTRL_SWAP_POWER) |
| pd_request_power_swap(p->port); |
| #ifdef CONFIG_USBC_VCONN_SWAP |
| else if (p->swap == USB_PD_CTRL_SWAP_VCONN) |
| pd_request_vconn_swap(p->port); |
| #endif |
| #endif |
| |
| if (args->version == 0) { |
| r->enabled = pd_comm_is_enabled(p->port); |
| r->role = pd[p->port].power_role; |
| r->polarity = pd[p->port].polarity; |
| r->state = pd[p->port].task_state; |
| args->response_size = sizeof(*r); |
| } else { |
| r_v1->enabled = |
| (pd_comm_is_enabled(p->port) ? |
| PD_CTRL_RESP_ENABLED_COMMS : 0) | |
| (pd_is_connected(p->port) ? |
| PD_CTRL_RESP_ENABLED_CONNECTED : 0) | |
| ((pd[p->port].flags & PD_FLAGS_PREVIOUS_PD_CONN) ? |
| PD_CTRL_RESP_ENABLED_PD_CAPABLE : 0); |
| r_v1->role = |
| (pd[p->port].power_role ? PD_CTRL_RESP_ROLE_POWER : 0) | |
| (pd[p->port].data_role ? PD_CTRL_RESP_ROLE_DATA : 0) | |
| ((pd[p->port].flags & PD_FLAGS_VCONN_ON) ? |
| PD_CTRL_RESP_ROLE_VCONN : 0) | |
| ((pd[p->port].flags & PD_FLAGS_PARTNER_DR_POWER) ? |
| PD_CTRL_RESP_ROLE_DR_POWER : 0) | |
| ((pd[p->port].flags & PD_FLAGS_PARTNER_DR_DATA) ? |
| PD_CTRL_RESP_ROLE_DR_DATA : 0) | |
| ((pd[p->port].flags & PD_FLAGS_PARTNER_USB_COMM) ? |
| PD_CTRL_RESP_ROLE_USB_COMM : 0) | |
| ((pd[p->port].flags & PD_FLAGS_PARTNER_EXTPOWER) ? |
| PD_CTRL_RESP_ROLE_EXT_POWERED : 0); |
| r_v1->polarity = pd[p->port].polarity; |
| strzcpy(r_v1->state, |
| pd_state_names[pd[p->port].task_state], |
| sizeof(r_v1->state)); |
| args->response_size = sizeof(*r_v1); |
| } |
| return EC_RES_SUCCESS; |
| } |
| DECLARE_HOST_COMMAND(EC_CMD_USB_PD_CONTROL, |
| hc_usb_pd_control, |
| EC_VER_MASK(0) | EC_VER_MASK(1)); |
| |
| static int 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 >= CONFIG_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_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)); |
| |
| static int hc_remote_rw_hash_entry(struct host_cmd_handler_args *args) |
| { |
| int i, idx = 0, found = 0; |
| const struct ec_params_usb_pd_rw_hash_entry *p = args->params; |
| static int rw_hash_next_idx; |
| |
| if (!p->dev_id) |
| return EC_RES_INVALID_PARAM; |
| |
| for (i = 0; i < RW_HASH_ENTRIES; i++) { |
| if (p->dev_id == rw_hash_table[i].dev_id) { |
| idx = i; |
| found = 1; |
| break; |
| } |
| } |
| if (!found) { |
| idx = rw_hash_next_idx; |
| rw_hash_next_idx = rw_hash_next_idx + 1; |
| if (rw_hash_next_idx == RW_HASH_ENTRIES) |
| rw_hash_next_idx = 0; |
| } |
| memcpy(&rw_hash_table[idx], p, sizeof(*p)); |
| |
| return EC_RES_SUCCESS; |
| } |
| DECLARE_HOST_COMMAND(EC_CMD_USB_PD_RW_HASH_ENTRY, |
| hc_remote_rw_hash_entry, |
| EC_VER_MASK(0)); |
| |
| static int hc_remote_pd_dev_info(struct host_cmd_handler_args *args) |
| { |
| const uint8_t *port = args->params; |
| struct ec_params_usb_pd_rw_hash_entry *r = args->response; |
| |
| if (*port >= CONFIG_USB_PD_PORT_COUNT) |
| return EC_RES_INVALID_PARAM; |
| |
| r->dev_id = pd[*port].dev_id; |
| |
| if (r->dev_id) { |
| memcpy(r->dev_rw_hash, pd[*port].dev_rw_hash, |
| PD_RW_HASH_SIZE); |
| } |
| |
| r->current_image = pd[*port].current_image; |
| |
| args->response_size = sizeof(*r); |
| return EC_RES_SUCCESS; |
| } |
| |
| DECLARE_HOST_COMMAND(EC_CMD_USB_PD_DEV_INFO, |
| hc_remote_pd_dev_info, |
| EC_VER_MASK(0)); |
| |
| #ifndef CONFIG_USB_PD_TCPC |
| #ifdef CONFIG_EC_CMD_PD_CHIP_INFO |
| static int hc_remote_pd_chip_info(struct host_cmd_handler_args *args) |
| { |
| const struct ec_params_pd_chip_info *p = args->params; |
| struct ec_response_pd_chip_info *r = args->response, *info; |
| |
| if (p->port >= CONFIG_USB_PD_PORT_COUNT) |
| return EC_RES_INVALID_PARAM; |
| |
| if (tcpm_get_chip_info(p->port, p->renew, &info)) |
| return EC_RES_ERROR; |
| |
| memcpy(r, info, sizeof(*r)); |
| args->response_size = sizeof(*r); |
| |
| return EC_RES_SUCCESS; |
| } |
| DECLARE_HOST_COMMAND(EC_CMD_PD_CHIP_INFO, |
| hc_remote_pd_chip_info, |
| EC_VER_MASK(0)); |
| #endif |
| #endif |
| |
| #ifdef CONFIG_USB_PD_ALT_MODE_DFP |
| static int hc_remote_pd_set_amode(struct host_cmd_handler_args *args) |
| { |
| const struct ec_params_usb_pd_set_mode_request *p = args->params; |
| |
| if ((p->port >= CONFIG_USB_PD_PORT_COUNT) || (!p->svid) || (!p->opos)) |
| return EC_RES_INVALID_PARAM; |
| |
| switch (p->cmd) { |
| case PD_EXIT_MODE: |
| if (pd_dfp_exit_mode(p->port, p->svid, p->opos)) |
| pd_send_vdm(p->port, p->svid, |
| CMD_EXIT_MODE | VDO_OPOS(p->opos), NULL, 0); |
| else { |
| CPRINTF("Failed exit mode\n"); |
| return EC_RES_ERROR; |
| } |
| break; |
| case PD_ENTER_MODE: |
| if (pd_dfp_enter_mode(p->port, p->svid, p->opos)) |
| pd_send_vdm(p->port, p->svid, CMD_ENTER_MODE | |
| VDO_OPOS(p->opos), NULL, 0); |
| break; |
| default: |
| return EC_RES_INVALID_PARAM; |
| } |
| return EC_RES_SUCCESS; |
| } |
| DECLARE_HOST_COMMAND(EC_CMD_USB_PD_SET_AMODE, |
| hc_remote_pd_set_amode, |
| EC_VER_MASK(0)); |
| #endif /* CONFIG_USB_PD_ALT_MODE_DFP */ |
| |
| #endif /* HAS_TASK_HOSTCMD */ |
| |
| #ifdef CONFIG_CMD_PD_CONTROL |
| |
| static int pd_control(struct host_cmd_handler_args *args) |
| { |
| static int pd_control_disabled[CONFIG_USB_PD_PORT_COUNT]; |
| const struct ec_params_pd_control *cmd = args->params; |
| int enable = 0; |
| |
| if (cmd->chip >= CONFIG_USB_PD_PORT_COUNT) |
| return EC_RES_INVALID_PARAM; |
| |
| /* Always allow disable command */ |
| if (cmd->subcmd == PD_CONTROL_DISABLE) { |
| pd_control_disabled[cmd->chip] = 1; |
| return EC_RES_SUCCESS; |
| } |
| |
| if (pd_control_disabled[cmd->chip]) |
| return EC_RES_ACCESS_DENIED; |
| |
| if (cmd->subcmd == PD_SUSPEND) { |
| /* |
| * The AP is requesting to suspend PD traffic on the EC so it |
| * can perform a firmware upgrade. If Vbus is present on the |
| * connector (it is either a source or sink), then we will |
| * prevent the upgrade if there is not enough battery to finish |
| * the upgrade. We cannot rely on the EC's active charger data |
| * as the EC just rebooted into RW and has not necessarily |
| * picked the active charger yet. |
| */ |
| #ifdef HAS_TASK_CHARGER |
| if (pd_is_vbus_present(cmd->chip)) { |
| struct batt_params batt = { 0 }; |
| /* |
| * The charger task has not re-initialized, so we need |
| * to ask the battery directly. |
| */ |
| battery_get_params(&batt); |
| if (batt.remaining_capacity < |
| MIN_BATTERY_FOR_TCPC_UPGRADE_MAH || |
| batt.flags & BATT_FLAG_BAD_REMAINING_CAPACITY) { |
| CPRINTS("C%d: Cannot suspend for upgrade, not " |
| "enough battery (%dmAh)!", |
| cmd->chip, batt.remaining_capacity); |
| return EC_RES_BUSY; |
| } |
| } |
| #else |
| if (pd_is_vbus_present(cmd->chip)) { |
| CPRINTS("C%d: Cannot suspend for upgrade, Vbus " |
| "present!", |
| cmd->chip); |
| return EC_RES_BUSY; |
| } |
| #endif |
| enable = 0; |
| } else if (cmd->subcmd == PD_RESUME) { |
| enable = 1; |
| } else if (cmd->subcmd == PD_RESET) { |
| #ifdef HAS_TASK_PDCMD |
| board_reset_pd_mcu(); |
| #else |
| return EC_RES_INVALID_COMMAND; |
| #endif |
| } else if (cmd->subcmd == PD_CHIP_ON && board_set_tcpc_power_mode) { |
| board_set_tcpc_power_mode(cmd->chip, 1); |
| return EC_RES_SUCCESS; |
| } else { |
| return EC_RES_INVALID_COMMAND; |
| } |
| |
| pd_comm_enable(cmd->chip, enable); |
| pd_set_suspend(cmd->chip, !enable); |
| |
| return EC_RES_SUCCESS; |
| } |
| |
| DECLARE_HOST_COMMAND(EC_CMD_PD_CONTROL, pd_control, EC_VER_MASK(0)); |
| #endif /* CONFIG_CMD_PD_CONTROL */ |
| |
| #endif /* CONFIG_COMMON_RUNTIME */ |