// // Copyright 2024 Wenting Zhang // Copyright 2017 Jason Cerundolo // /* 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 #include #include #include "pico/stdlib.h" #include "usb_pd.h" #include "usb_pd_tcpm.h" #include "usb_mux.h" #include "tcpm.h" #include "usb_pd_driver.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); /* * 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...) printf(format, ## args) #define CPRINTS(format, args...) printf(format, ## args) static const int debug_level = 1; #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 #ifdef CONFIG_USB_PD_DUAL_ROLE /* Port dual-role state */ enum pd_dual_role_states drp_state = 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 // variables that used to be pd_task, but had to be promoted // so both pd_init and pd_run_state_machine can see them static int head; static int port = TASK_ID_TO_PD_PORT(task_get_current()); static uint32_t payload[7]; static int timeout = 10*MSEC_US; static int cc1, cc2; static int res, incoming_packet = 0; static int hard_reset_count = 0; #ifdef CONFIG_USB_PD_DUAL_ROLE static uint64_t next_role_swap = PD_T_DRP_SNK; #ifndef CONFIG_USB_PD_VBUS_DETECT_NONE static int snk_hard_reset_vbus_off = 0; #endif #ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE static const int auto_toggle_supported = tcpm_auto_toggle_supported(port); #endif #if defined(CONFIG_CHARGE_MANAGER) static typec_current_t typec_curr = 0, typec_curr_change = 0; #endif /* CONFIG_CHARGE_MANAGER */ #endif /* CONFIG_USB_PD_DUAL_ROLE */ static enum pd_states this_state; static enum pd_cc_states new_cc_state; static timestamp_t now; static int caps_count = 0, hard_reset_sent = 0; static int snk_cap_count = 0; static int evt; 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, }; 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; #ifdef CONFIG_USB_PD_CHROMEOS /* 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; #endif #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; } #ifdef CONFIG_USB_PD_DUAL_ROLE void pd_vbus_low(int port) { pd[port].flags &= ~PD_FLAGS_VBUS_NEVER_LOW; } static inline 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 } #endif 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 tcpm_set_vconn(port, 0); #endif #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) { /* * Rp is restored by pd_power_supply_reset if * CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT is defined. */ pd_power_supply_reset(port); #if !defined(CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT) && \ defined(CONFIG_USB_PD_REV30) /* Restore Rp */ tcpm_select_rp_value(port, CONFIG_USB_PD_PULLUP); tcpm_set_cc(port, TYPEC_CC_RP); #endif } #ifdef CONFIG_USB_PD_REV30 /* Adjust rev to highest level*/ pd[port].rev = PD_REV30; #endif #ifdef CONFIG_USB_PD_CHROMEOS pd[port].dev_id = 0; pd[port].flags &= ~PD_FLAGS_RESET_ON_DISCONNECT_MASK; #endif #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 #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[i].flags & PD_FLAGS_PREVIOUS_PD_CONN) break; } if (i == CONFIG_USB_PD_PORT_COUNT) enable_sleep(SLEEP_MASK_USB_PD); else disable_sleep(SLEEP_MASK_USB_PD); #endif CPRINTF("C%d st%d\n", port, next_state); } /* increment message ID counter */ static void inc_id(int port) { pd[port].msg_id = (pd[port].msg_id + 1) & PD_MESSAGE_ID_COUNT; } #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); pd_task_set_event(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 */ // Would wait, except that we're making tcpm_transmit blocking //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 // removing task-based stuff from the library //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("CTRL[%d]>%d\n", 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("srcCAP>%d\n", 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("batCap>%d\n", 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("batStat>%d\n", 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("snkCAP>%d\n", 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("REQ%d>\n", bit_len); return bit_len; } #ifdef CONFIG_BBRAM static int pd_get_saved_active(int port) { uint8_t val; if (system_get_bbram(port ? SYSTEM_BBRAM_IDX_PD1 : SYSTEM_BBRAM_IDX_PD0, &val)) { CPRINTS("PD NVRAM FAIL"); return 0; } return !!val; } static void pd_set_saved_active(int port, int val) { if (system_set_bbram(port ? SYSTEM_BBRAM_IDX_PD1 : SYSTEM_BBRAM_IDX_PD0, val)) CPRINTS("PD NVRAM FAIL"); } #endif // CONFIG_BBRAM #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("BIST>%d\n", 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; CPRINTF("VDM request\n"); 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("Unhandled VDM VID %04x CMD %04x\n", PD_VDO_VID(payload[0]), payload[0] & 0xFFFF); } 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); } 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); // getting rid of task stuff //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); const int charging = 1; #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("Req C%d [%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); // We call the callback after we send the request // because the timing on Request seems to be sensitive // User code can take the time until PS_RDY to do stuff pd_process_source_cap_callback(port, cnt, payload); } 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_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 #ifdef CONFIG_USB_PD_DUAL_ROLE #ifdef CONFIG_BBRAM pd_set_saved_active(port, 1); #endif #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("Unhandled data message type %d\n", 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); // getting rid of task stuff //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); // getting rid of task stuff //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); // getting rid of task stuff //task_wake(PD_PORT_TO_TASK_ID(port)); } static void pd_set_data_role(int port, int role) { pd[port].data_role = 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); } 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[port].power_role = 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) 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; 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; 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; #ifdef CONFIG_BBRAM pd_set_saved_active(port, 1); #endif 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("Unhandled ctrl message type %d\n", 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 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("RECV %04x/%d ", 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. * Ignore it if we get one. */ if (!pd_is_connected(port)) 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("VDM over max size\n"); 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); // getting rid of task stuff //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_US; 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; /*static int old_vdm_state = 0; if (pd[port].vdm_state != old_vdm_state) { CPRINTF("VDM state: %d\n", pd[port].vdm_state); old_vdm_state = pd[port].vdm_state; }*/ 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 #ifdef CONFIG_USB_PD_CHROMEOS pd[port].dev_id = dev_id; memcpy(pd[port].dev_rw_hash, rw_hash, PD_RW_HASH_SIZE); #endif #ifdef CONFIG_CMD_PD_DEV_DUMP_INFO if (debug_level >= 2) pd_dev_dump_info(dev_id, (uint8_t *)rw_hash); #endif #ifdef CONFIG_USB_PD_CHROMEOS pd[port].current_image = current_image; #endif #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(void) { return drp_state; } #ifdef CONFIG_USB_PD_TRY_SRC static void pd_update_try_source(void) { int i; #ifndef CONFIG_CHARGER int batt_soc = board_get_battery_soc(); #else int batt_soc = charge_get_percent(); #endif /* * Enable try source when dual-role toggling AND battery is present * and at some minimum percentage. */ pd_try_src_enable = drp_state == PD_DRP_TOGGLE_ON && 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(enum pd_dual_role_states state) { int i; drp_state = 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++) // getting rid of task stuff //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 == PD_DRP_FORCE_SINK && !pd_ts_dts_plugged(port)) || (drp_state == PD_DRP_TOGGLE_OFF && pd[port].task_state == PD_STATE_SRC_DISCONNECTED))) { pd[port].power_role = 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 == PD_DRP_FORCE_SOURCE) { pd[port].power_role = 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 */ tcpm_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; #ifdef CONFIG_BBRAM /* * Check our battery-backed previous port state. If PD comms were * active, and we didn't just lose power, make sure we * don't boot into RO with a pre-existing power contract. */ if (!pd_get_saved_active(port) || system_get_image_copy() != SYSTEM_IMAGE_RO || system_get_reset_flags() & (RESET_FLAG_BROWNOUT | RESET_FLAG_POWER_ON)) return; #endif // CONFIG_BBRAM /* Provide Rp for 100 msec. or until we no longer have VBUS. */ tcpm_set_cc(port, TYPEC_CC_RP); timeout = get_time().val + 100 * MSEC_US; while (get_time().val < timeout && pd_is_vbus_present(port)) msleep(10); #ifdef CONFIG_BBRAM pd_set_saved_active(port, 0); #endif } #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; // getting rid of task stuff //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)) drp_state = PD_DRP_FORCE_SINK; else if (chipset_in_state(CHIPSET_STATE_SUSPEND)) drp_state = PD_DRP_TOGGLE_OFF; else /* CHIPSET_STATE_ON */ drp_state = 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; #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 void pd_init(int port) { #ifdef CONFIG_COMMON_RUNTIME pd_init_tasks(); #endif /* Ensure the power supply is in the default state */ pd_power_supply_reset(port); #ifdef CONFIG_USB_PD_TCPC_BOARD_INIT /* Board specific TCPC init */ board_tcpc_init(); #endif /* Initialize TCPM driver and wait for TCPC to be ready */ res = tcpm_init(port); #ifdef CONFIG_USB_PD_DUAL_ROLE //pd_partner_port_reset(port); #endif CPRINTS("TCPC p%d init %s", port, res ? "failed" : "ready"); this_state = res ? PD_STATE_SUSPENDED : PD_DEFAULT_STATE(port); #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; pd[port].flags = 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 /* Initialize PD protocol state variables for each port. */ pd[port].power_role = PD_ROLE_DEFAULT(port); pd[port].vdm_state = VDM_STATE_DONE; set_state(port, this_state); #ifdef CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT ASSERT(PD_ROLE_DEFAULT(port) == PD_ROLE_SINK); tcpm_select_rp_value(port, CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT); #else tcpm_select_rp_value(port, CONFIG_USB_PD_PULLUP); #endif tcpm_set_cc(port, PD_ROLE_DEFAULT(port) == PD_ROLE_SOURCE ? TYPEC_CC_RP : TYPEC_CC_RD); #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 } void pd_run_state_machine(int port) { #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 */ // getting rid of task stuff //evt = task_wait_event(timeout); #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) { CPRINTS("TCPC p%d reset!", port); if (tcpm_init(port) != EC_SUCCESS) CPRINTS("TCPC p%d init failed", 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 /* 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) || #endif (PD_ROLE_DEFAULT(port) == PD_ROLE_SOURCE && pd[port].task_state == PD_STATE_SRC_READY))) { tcpm_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[port].power_role = 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_US; switch (this_state) { case PD_STATE_DISABLED: /* Nothing to do */ break; case PD_STATE_SRC_DISCONNECTED: timeout = 10*MSEC_US; tcpm_get_cc(port, &cc1, &cc2); #ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE /* * Attempt TCPC auto DRP toggle if it is * not already auto toggling and not try.src */ if (auto_toggle_supported && !(pd[port].flags & PD_FLAGS_TCPC_DRP_TOGGLE) && !(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_US; 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 != PD_DRP_FORCE_SOURCE && drp_state != PD_DRP_FREEZE && get_time().val >= next_role_swap)) { pd[port].power_role = 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_US; } #endif break; case PD_STATE_SRC_DISCONNECTED_DEBOUNCE: timeout = 20*MSEC_US; 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_US; 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); tcpm_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; #ifndef CONFIG_USBC_BACKWARDS_COMPATIBLE_DFP /* Enable VBUS */ if (pd_set_power_supply_ready(port)) { #ifdef CONFIG_USBC_SS_MUX usb_mux_set(port, TYPEC_MUX_NONE, USB_SWITCH_DISCONNECT, pd[port].polarity); #endif break; } #endif /* If PD comm is enabled, enable TCPC RX */ if (pd_comm_is_enabled(port)) tcpm_set_rx_enable(port, 1); #ifdef CONFIG_USBC_VCONN tcpm_set_vconn(port, 1); pd[port].flags |= PD_FLAGS_VCONN_ON; #endif pd[port].flags |= PD_FLAGS_CHECK_PR_ROLE | PD_FLAGS_CHECK_DR_ROLE; hard_reset_count = 0; timeout = 5*MSEC_US; 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_US; break; } /* Enable VBUS */ timeout = 10*MSEC_US; 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[port].flags & PD_FLAGS_PREVIOUS_PD_CONN) 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_US; 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_US; /* 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("ERR SNK_CAP\n"); } } /* 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_US; 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_US; /* * 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_US; /* * 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_US; set_state(port, PD_STATE_SRC_DISCONNECTED); break; } /* Switch to Rd and swap roles to sink */ tcpm_set_cc(port, TYPEC_CC_RD); pd[port].power_role = 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 */ // getting rid of task stuff //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 != PD_DRP_TOGGLE_ON ? SECOND_US : 10*MSEC_US; #else timeout = 10*MSEC_US; #endif tcpm_get_cc(port, &cc1, &cc2); #ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE /* * Attempt TCPC auto DRP toggle if it is * not already auto toggling and not try.src */ if (auto_toggle_supported && !(pd[port].flags & PD_FLAGS_TCPC_DRP_TOGGLE) && !(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_US; 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_US; 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 == PD_DRP_TOGGLE_ON && get_time().val >= next_role_swap) { /* Swap roles to source */ pd[port].power_role = 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_US; } 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_US; break; } timeout = 20*MSEC_US; /* 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[port].power_role = PD_ROLE_SOURCE; tcpm_set_cc(port, TYPEC_CC_RP); timeout = 2*MSEC_US; 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); tcpm_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; send_control(port, PD_CTRL_GET_SOURCE_CAP); set_state(port, PD_STATE_SNK_DISCOVERY); timeout = 10*MSEC_US; //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_US; } /* * 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[port].flags & PD_FLAGS_PREVIOUS_PD_CONN) /* 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_US; /* * 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_US; 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_US; /* * 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_US; 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_US; 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_US; 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[port].power_role = PD_ROLE_SOURCE; pd_update_roles(port); set_state(port, PD_STATE_SRC_DISCOVERY); timeout = 10*MSEC_US; 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_US; /* * 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 */ tcpm_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_US; /* * 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 */ tcpm_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_US; 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_US; 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_US; } } 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_US; 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_US; /* 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_US; /* 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); /* 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 != 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 != PD_DRP_TOGGLE_OFF && drp_state != 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) { tcpm_set_drp_toggle(port, 0); #ifdef CONFIG_USB_PD_TCPC_LOW_POWER CPRINTS("TCPC p%d Exit Low Power Mode", port); #endif } if (next_state == PD_STATE_SNK_DISCONNECTED) { tcpm_set_cc(port, TYPEC_CC_RD); pd[port].power_role = PD_ROLE_SINK; timeout = 2*MSEC_US; } else if (next_state == PD_STATE_SRC_DISCONNECTED) { tcpm_set_cc(port, TYPEC_CC_RP); pd[port].power_role = PD_ROLE_SOURCE; timeout = 2*MSEC_US; } else { tcpm_set_drp_toggle(port, 1); pd[port].flags |= PD_FLAGS_TCPC_DRP_TOGGLE; timeout = -1; #ifdef CONFIG_USB_PD_TCPC_LOW_POWER CPRINTS("TCPC p%d Low Power Mode", port); #endif } 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_US ? timeout : 10*MSEC_US; } else if (pd[port].timeout - now.val < timeout) { timeout = pd[port].timeout - now.val; } } /* Check for disconnection if we're connected */ if (!pd_is_connected(port)) return; #ifdef CONFIG_USB_PD_DUAL_ROLE if (pd_is_power_swapping(port)) return; #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_US; #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[port].power_role = 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_US; } #endif /* CONFIG_USB_PD_DUAL_ROLE */ } #ifdef CONFIG_USB_PD_DUAL_ROLE static void dual_role_on(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[i].flags |= PD_FLAGS_CHECK_IDENTITY; } pd_set_dual_role(PD_DRP_TOGGLE_ON); CPRINTS("chipset -> S0"); } DECLARE_HOOK(HOOK_CHIPSET_RESUME, dual_role_on, HOOK_PRIO_DEFAULT); static void dual_role_off(void) { pd_set_dual_role(PD_DRP_TOGGLE_OFF); CPRINTS("chipset -> S3"); } DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, dual_role_off, HOOK_PRIO_DEFAULT); DECLARE_HOOK(HOOK_CHIPSET_STARTUP, dual_role_off, HOOK_PRIO_DEFAULT); static void dual_role_force_sink(void) { pd_set_dual_role(PD_DRP_FORCE_SINK); CPRINTS("chipset -> S5"); } DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, dual_role_force_sink, 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 { // getting rid of task stuff //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)); // getting rid of task stuff //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_US); ccprintf("DONE %d\n", pd[port].vdm_state); return EC_SUCCESS; } #endif /* defined(CONFIG_CMD_PD) && defined(CONFIG_CMD_PD_FLASH) */ 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_US; /* Wait until VDM is done */ while ((pd[port].vdm_state > 0) && (get_time().val < timeout.val)) task_wait_event(10*MSEC_US); 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[port].power_role = PD_ROLE_SINK; tcpm_set_cc(port, TYPEC_CC_RD); set_state(port, PD_STATE_SNK_DISCONNECTED); } // getting rid of task stuff //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; // getting rid of task stuff //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; // getting rid of task stuff //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 defined(CONFIG_CMD_PD) && defined(CONFIG_USB_PD_DUAL_ROLE) /* command: pd */ if (!strcasecmp(argv[1], "dualrole")) { if (argc < 3) { ccprintf("dual-role toggling: "); switch (drp_state) { 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[2], "on")) pd_set_dual_role(PD_DRP_TOGGLE_ON); else if (!strcasecmp(argv[2], "off")) pd_set_dual_role(PD_DRP_TOGGLE_OFF); else if (!strcasecmp(argv[2], "freeze")) pd_set_dual_role(PD_DRP_FREEZE); else if (!strcasecmp(argv[2], "sink")) pd_set_dual_role(PD_DRP_FORCE_SINK); else if (!strcasecmp(argv[2], "source")) pd_set_dual_role(PD_DRP_FORCE_SOURCE); else return EC_ERROR_PARAM3; } return EC_SUCCESS; } else #endif 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 [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); // getting rid of task stuff //task_wake(PD_PORT_TO_TASK_ID(port)); } else if (!strcasecmp(argv[2], "bist_rx")) { set_state(port, PD_STATE_BIST_RX); // getting rid of task stuff //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); // getting rid of task stuff //task_wake(PD_PORT_TO_TASK_ID(port)); } else if (!strcasecmp(argv[2], "charger")) { pd[port].power_role = PD_ROLE_SOURCE; tcpm_set_cc(port, TYPEC_CC_RP); set_state(port, PD_STATE_SRC_DISCONNECTED); // getting rid of task stuff //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); // getting rid of task stuff //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( pd[port].current_image)); } else if (!strncasecmp(argv[2], "soft", 4)) { set_state(port, PD_STATE_SOFT_RESET); // getting rid of task stuff //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 } 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 " "[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(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_US; 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_US; /* Wait until VDM is done */ while ((pd[port].vdm_state > 0) && (get_time().val < timeout.val)) task_wait_event(10*MSEC_US); 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_US); 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) { 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 */ #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)); } bool pd_is_vdm_busy(int port) { return ((pd[port].vdm_state != 0) && (pd[port].vdm_state != -1)); } #endif