diff options
Diffstat (limited to 'common/usbc/usb_pe_drp_sm.c')
-rw-r--r-- | common/usbc/usb_pe_drp_sm.c | 476 |
1 files changed, 470 insertions, 6 deletions
diff --git a/common/usbc/usb_pe_drp_sm.c b/common/usbc/usb_pe_drp_sm.c index 3a94287094..b3e51c6966 100644 --- a/common/usbc/usb_pe_drp_sm.c +++ b/common/usbc/usb_pe_drp_sm.c @@ -287,6 +287,13 @@ enum usb_pe_state { PE_SNK_CHUNK_RECEIVED, /* pe-st76 */ PE_VCS_FORCE_VCONN, /* pe-st77 */ PE_GET_REVISION, /* pe-st78 */ + + /* EPR states */ + PE_SNK_SEND_EPR_MODE_ENTRY, + PE_SNK_EPR_MODE_ENTRY_WAIT_FOR_RESPONSE, + PE_SNK_EPR_KEEP_ALIVE, + PE_SNK_SEND_EPR_MODE_EXIT, + PE_SNK_EPR_MODE_EXIT_RECEIVED, }; /* @@ -426,6 +433,14 @@ __maybe_unused static __const_data const char *const pe_state_names[] = { [PE_DDR_WAIT_FOR_VCONN_OFF] = "PE_DDR_Wait_For_VCONN_Off", [PE_DDR_PERFORM_DATA_RESET] = "PE_DDR_Perform_Data_Reset", #endif /* CONFIG_USB_PD_DATA_RESET_MSG */ +#ifdef CONFIG_USB_PD_EPR + [PE_SNK_SEND_EPR_MODE_ENTRY] = "PE_SNK_Send_EPR_Mode_Entry", + [PE_SNK_EPR_MODE_ENTRY_WAIT_FOR_RESPONSE] = + "PE_SNK_EPR_Mode_Entry_Wait_For_Response", + [PE_SNK_EPR_KEEP_ALIVE] = "PE_SNK_EPR_Keep_Alive", + [PE_SNK_SEND_EPR_MODE_EXIT] = "PE_SNK_Send_EPR_Mode_Exit", + [PE_SNK_EPR_MODE_EXIT_RECEIVED] = "PE_SNK_EPR_Mode_Exit_Received", +#endif #endif /* CONFIG_USB_PD_REV30 */ }; @@ -1056,6 +1071,18 @@ uint32_t pd_get_requested_current(int port) return pe[port].curr_limit; } +#ifdef CONFIG_USB_PD_EPR +/** + * Return true if we're in an SPR contract. Note that before exiting EPR mode, + * port partners are required first to drop to SPR. Thus, we can be still in + * EPR mode (with a SPR contract). + */ +static int pe_in_spr_contract(int port) +{ + return pd_get_requested_voltage(port) <= PD_MAX_SPR_VOLTAGE; +} +#endif /* CONFIG_USB_PD_EPR */ + /* * Determine if this port may communicate with the cable plug. * @@ -1122,6 +1149,41 @@ static bool pe_can_send_sop_vdm(int port, int vdm_cmd) return false; } +static const uint32_t pd_get_fixed_pdo(int port) +{ + return pe[port].src_caps[0]; +} + +bool pe_snk_in_epr_mode(int port) +{ + return PE_CHK_FLAG(port, PE_FLAGS_IN_EPR); +} + +bool pe_snk_can_enter_epr_mode(int port) +{ + /* + * 6.4.10.1 of USB PD R3.1 V1.6 + * + * 1. A Sink Shall Not be Connected to the Source through a CT-VPD. + * 2. The Source and Sink Shall be in an SPR Explicit Contract. + * 3. The EPR Mode capable bit Shall have been set in the 5V fixed PDO + * in the last Source_Capabilities Message the Sink received. + * 4. The EPR Mode capable bit Shall have been set in the RDO in the + * last Request Message the Source received. + */ + if (is_vpd_ct_supported(port)) { + return false; + } + + if (!pe_is_explicit_contract(port)) + return false; + + if (!(pd_get_fixed_pdo(port) & PDO_FIXED_EPR_MODE_CAPABLE)) + return false; + + return true; +} + static void pe_send_soft_reset(const int port, enum tcpci_msg_type type) { pe[port].soft_reset_sop = type; @@ -1304,7 +1366,8 @@ __overridable bool pd_can_charge_from_device(int port, const int pdo_cnt, * Get max power that the partner offers (not necessarily what * this board will request) */ - pd_find_pdo_index(pdo_cnt, pdos, PD_REV3_MAX_VOLTAGE, &max_pdo); + pd_find_pdo_index(pdo_cnt, pdos, pd_get_max_voltage(), + &max_pdo); pd_extract_pdo_power(max_pdo, &max_ma, &max_mv, &unused); max_mw = max_ma * max_mv / 1000; @@ -1765,6 +1828,60 @@ static bool sink_dpm_requests(int port) PE_CLR_DPM_REQUEST(port, DPM_REQUEST_FRS_DET_DISABLE); } else if (common_src_snk_dpm_requests(port)) { return true; +#ifdef CONFIG_USB_PD_EPR + } else if (PE_CHK_DPM_REQUEST(port, + DPM_REQUEST_EPR_MODE_ENTRY)) { + if (pe_snk_in_epr_mode(port)) { + PE_CLR_DPM_REQUEST(port, + DPM_REQUEST_EPR_MODE_ENTRY); + CPRINTS("C%d: Already in EPR mode", port); + return false; + } + + if (!pe_snk_can_enter_epr_mode(port)) { + PE_CLR_DPM_REQUEST(port, + DPM_REQUEST_EPR_MODE_ENTRY); + CPRINTS("C%d: Not allowed to enter EPR", port); + return false; + } + + pe_set_dpm_curr_request(port, + DPM_REQUEST_EPR_MODE_ENTRY); + pd_set_max_voltage(PD_MAX_VOLTAGE_MV); + set_state_pe(port, PE_SNK_SEND_EPR_MODE_ENTRY); + return true; + } else if (PE_CHK_DPM_REQUEST(port, + DPM_REQUEST_EPR_MODE_EXIT)) { + if (!pe_snk_in_epr_mode(port)) { + PE_CLR_DPM_REQUEST(port, + DPM_REQUEST_EPR_MODE_EXIT); + CPRINTS("C%d: Not in EPR mode", port); + return false; + } + + /* + * If we're already in an SPR contract, send an exit + * message. Figure 8-217. + */ + if (pe_in_spr_contract(port)) { + pe_set_dpm_curr_request( + port, DPM_REQUEST_EPR_MODE_EXIT); + set_state_pe(port, PE_SNK_SEND_EPR_MODE_EXIT); + return true; + } + + /* + * Can't exit yet because we're still in EPR contract. + * Send an SPR RDO to negotiate an SPR contract. + * Keep DPM_REQUEST_EPR_MODE_EXIT so that we can retry. + */ + CPRINTS("C%d: Request SPR before EPR exit", port); + pd_set_max_voltage(PD_MAX_SPR_VOLTAGE); + pe_set_dpm_curr_request(port, + DPM_REQUEST_NEW_POWER_LEVEL); + set_state_pe(port, PE_SNK_SELECT_CAPABILITY); + return true; +#endif /* CONFIG_USB_PD_EPR */ } else { CPRINTF("Unhandled DPM Request %x received\n", dpm_request); @@ -1776,7 +1893,7 @@ static bool sink_dpm_requests(int port) return false; } -/* Get the previous TypeC state. */ +/* Get the previous PE state. */ static enum usb_pe_state get_last_state_pe(const int port) { return pe[port].ctx.previous - &pe_states[0]; @@ -1821,6 +1938,7 @@ static void pe_send_request_msg(int port) uint32_t rdo; uint32_t curr_limit; uint32_t supply_voltage; + enum pd_data_msg_type msg; /* * If we are charging through a VPD, the requested voltage and current @@ -1848,10 +1966,22 @@ static void pe_send_request_msg(int port) pe[port].curr_limit = curr_limit; pe[port].supply_voltage = supply_voltage; - tx_emsg[port].len = 4; + if (IS_ENABLED(CONFIG_USB_PD_EPR) && pe_snk_in_epr_mode(port)) { + const uint32_t *src_caps = pd_get_src_caps(port); - memcpy(tx_emsg[port].buf, (uint8_t *)&rdo, tx_emsg[port].len); - send_data_msg(port, TCPCI_MSG_SOP, PD_DATA_REQUEST); + tx_emsg[port].len = 8; + memcpy(tx_emsg[port].buf, (uint8_t *)&rdo, sizeof(rdo)); + memcpy(tx_emsg[port].buf + sizeof(rdo), + (uint8_t *)&src_caps[RDO_POS(rdo) - 1], + sizeof(src_caps[0])); + msg = PD_DATA_EPR_REQUEST; + } else { + tx_emsg[port].len = 4; + memcpy(tx_emsg[port].buf, (uint8_t *)&rdo, tx_emsg[port].len); + msg = PD_DATA_REQUEST; + } + + send_data_msg(port, TCPCI_MSG_SOP, msg); } static void pe_update_src_pdo_flags(int port, int pdo_cnt, uint32_t *pdos) @@ -3223,6 +3353,7 @@ static void pe_snk_wait_for_capabilities_run(int port) uint8_t type; uint8_t cnt; uint8_t ext; + uint32_t *payload; /* * Transition to the PE_SNK_Evaluate_Capability state when: @@ -3234,10 +3365,24 @@ static void pe_snk_wait_for_capabilities_run(int port) type = PD_HEADER_TYPE(rx_emsg[port].header); cnt = PD_HEADER_CNT(rx_emsg[port].header); ext = PD_HEADER_EXT(rx_emsg[port].header); + payload = (uint32_t *)rx_emsg[port].buf; if ((ext == 0) && (cnt > 0) && (type == PD_DATA_SOURCE_CAP)) { set_state_pe(port, PE_SNK_EVALUATE_CAPABILITY); return; + } else if (ext > 0) { + switch (type) { +#ifdef CONFIG_USB_PD_EPR + case PD_EXT_EPR_SOURCE_CAP: + if (!pe_snk_in_epr_mode(port)) + break; + set_state_pe(port, PE_SNK_EVALUATE_CAPABILITY); + break; +#endif /* CONFIG_USB_PD_EPR */ + default: + extended_message_not_supported(port, payload); + } + return; } } @@ -3562,7 +3707,9 @@ static void pe_snk_transition_sink_exit(int port) */ static void pe_snk_ready_entry(int port) { - print_current_state(port); + if (get_last_state_pe(port) != PE_SNK_EPR_KEEP_ALIVE) { + print_current_state(port); + } /* Ensure any message send flags are cleaned up */ PE_CLR_MASK(port, PE_MASK_READY_CLR); @@ -3589,6 +3736,11 @@ static void pe_snk_ready_entry(int port) * have been sent since enter this state. */ pe_update_wait_and_add_jitter_timer(port); + + if (IS_ENABLED(CONFIG_USB_PD_EPR) && pe_snk_in_epr_mode(port)) { + pd_timer_enable(port, PE_TIMER_SINK_EPR_KEEP_ALIVE, + PD_T_SINK_EPR_KEEP_ALIVE); + } } static void pe_snk_ready_run(int port) @@ -3615,6 +3767,13 @@ static void pe_snk_ready_run(int port) case PD_EXT_GET_BATTERY_STATUS: set_state_pe(port, PE_GIVE_BATTERY_STATUS); break; +#ifdef CONFIG_USB_PD_EPR + case PD_EXT_EPR_SOURCE_CAP: + if (!pe_snk_in_epr_mode(port)) + break; + set_state_pe(port, PE_SNK_EVALUATE_CAPABILITY); + break; +#endif /* CONFIG_USB_PD_EPR */ #endif /* CONFIG_USB_PD_EXTENDED_MESSAGES && CONFIG_BATTERY */ default: extended_message_not_supported(port, payload); @@ -3646,6 +3805,17 @@ static void pe_snk_ready_run(int port) case PD_DATA_ALERT: set_state_pe(port, PE_ALERT_RECEIVED); return; +#ifdef CONFIG_USB_PD_EPR + case PD_DATA_EPR_MODE: + struct eprmdo *mdo = (void *)payload; + + if (mdo->action == PD_EPRMDO_ACTION_EXIT) { + set_state_pe( + port, + PE_SNK_EPR_MODE_EXIT_RECEIVED); + } + return; +#endif /* CONFIG_USB_PD_EPR */ #endif /* CONFIG_USB_PD_REV30 */ default: set_state_pe(port, PE_SEND_NOT_SUPPORTED); @@ -3772,6 +3942,11 @@ static void pe_snk_ready_run(int port) /* Inform DPM state machine that PE is set for messages */ dpm_set_pe_ready(port, true); + + if (pd_timer_is_expired(port, PE_TIMER_SINK_EPR_KEEP_ALIVE)) { + set_state_pe(port, PE_SNK_EPR_KEEP_ALIVE); + return; + } } } @@ -3779,6 +3954,10 @@ static void pe_snk_ready_exit(int port) { /* Inform DPM state machine that PE is in ready state */ dpm_set_pe_ready(port, false); + + if (IS_ENABLED(CONFIG_USB_PD_EPR) && pe_snk_in_epr_mode(port)) { + pd_timer_disable(port, PE_TIMER_SINK_EPR_KEEP_ALIVE); + } } /** @@ -7710,6 +7889,268 @@ static void pe_ddr_perform_data_reset_exit(int port) } #endif /* CONFIG_USB_PD_DATA_RESET_MSG */ +#ifdef CONFIG_USB_PD_EPR +static void pe_enter_epr_mode(int port) +{ + PE_SET_FLAG(port, PE_FLAGS_IN_EPR); + CPRINTS("C%d: Entered EPR", port); +} + +static void pe_exit_epr_mode(int port) +{ + PE_CLR_FLAG(port, PE_FLAGS_IN_EPR); + PE_CLR_DPM_REQUEST(port, DPM_REQUEST_EPR_MODE_EXIT); + CPRINTS("C%d: Exited EPR", port); +} + +/* + * PE_SNK_EPR_KEEP_ALIVE + */ +static void pe_snk_epr_keep_alive_entry(int port) +{ + struct pd_ecdb *ecdb = (void *)tx_emsg[port].buf; + + if (pe_debug_level >= DEBUG_LEVEL_2) { + print_current_state(port); + } + + ecdb->type = PD_EXT_CTRL_EPR_KEEPALIVE; + ecdb->data = 0; + tx_emsg[port].len = sizeof(*ecdb); + + send_ext_data_msg(port, TCPCI_MSG_SOP, PD_EXT_CONTROL); + pe_sender_response_msg_entry(port); +} + +static void pe_snk_epr_keep_alive_run(int port) +{ + enum pe_msg_check msg_check = pe_sender_response_msg_run(port); + + if (msg_check & PE_MSG_DISCARDED) { + /* + * An EPR_KeepAlive was discarded due to an incoming message + * from the source. Both ends know the partnership is alive. We + * go back to SNK_Ready and restart the KeepAlive timer. + */ + set_state_pe(port, PE_SNK_READY); + return; + } + + if (msg_check & PE_MSG_SENT && + PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { + int type = PD_HEADER_TYPE(rx_emsg[port].header); + int cnt = PD_HEADER_CNT(rx_emsg[port].header); + int ext = PD_HEADER_EXT(rx_emsg[port].header); + struct pd_ecdb *ecdb = (void *)rx_emsg[port].buf; + + PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); + + if (cnt == 0 || ext == 0 || type != PD_EXT_CONTROL) { + CPRINTS("C%d: Protocol Error: 0x%04x", port, + rx_emsg[port].header); + pe_send_soft_reset(port, TCPCI_MSG_SOP); + } else if (ecdb->type == PD_EXT_CTRL_EPR_KEEPALIVE_ACK) { + pe_sender_response_msg_exit(port); + set_state_pe(port, PE_SNK_READY); + } + + return; + } + + if (pd_timer_is_expired(port, PE_TIMER_SENDER_RESPONSE)) + pe_set_hard_reset(port); +} + +/* + * PE_SNK_SEND_EPR_MODE_ENTRY + */ +static void pe_snk_send_epr_mode_entry_entry(int port) +{ + struct eprmdo *eprmdo = (void *)tx_emsg[port].buf; + + print_current_state(port); + + /* Send EPR mode entry message */ + eprmdo->action = PD_EPRMDO_ACTION_ENTER; + eprmdo->data = 0; /* EPR Sink Operational PDP */ + eprmdo->reserved = 0; + tx_emsg[port].len = sizeof(*eprmdo); + + send_data_msg(port, TCPCI_MSG_SOP, PD_DATA_EPR_MODE); + pe_sender_response_msg_entry(port); + + pd_timer_enable(port, PE_TIMER_SINK_EPR_ENTER, PD_T_ENTER_EPR); +} + +static void pe_snk_send_epr_mode_entry_run(int port) +{ + enum pe_msg_check msg_check; + + /* Check the state of the message sent */ + msg_check = pe_sender_response_msg_run(port); + + if (msg_check & PE_MSG_DISCARDED) { + set_state_pe(port, PE_SNK_READY); + return; + } + + if ((msg_check & PE_MSG_SENT) && + PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { + uint8_t type = PD_HEADER_TYPE(rx_emsg[port].header); + uint8_t cnt = PD_HEADER_CNT(rx_emsg[port].header); + uint8_t ext = PD_HEADER_EXT(rx_emsg[port].header); + + PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); + if ((ext == 0) && (cnt > 0) && (type == PD_DATA_EPR_MODE)) { + struct eprmdo *eprmdo = (void *)rx_emsg[port].buf; + + if (eprmdo->action == PD_EPRMDO_ACTION_ENTER_ACK) { + /* EPR Enter Mode Acknowledge received */ + set_state_pe( + port, + PE_SNK_EPR_MODE_ENTRY_WAIT_FOR_RESPONSE); + return; + } + /* + * Other actions should result in soft reset but not + * clear from the spec. So, we just let it time out. + */ + } + } + + /* When the SinkEPREnterTimer times out, send a soft reset. */ + if (pd_timer_is_expired(port, PE_TIMER_SINK_EPR_ENTER)) { + pe_send_soft_reset(port, TCPCI_MSG_SOP); + } else if (pd_timer_is_expired(port, PE_TIMER_SENDER_RESPONSE)) { + pe_send_soft_reset(port, TCPCI_MSG_SOP); + } +} + +static void pe_snk_send_epr_mode_entry_exit(int port) +{ + pe_sender_response_msg_exit(port); +} + +/* + * PE_SNK_EPR_MODE_ENTRY_WAIT_FOR_RESPONSE + */ +static void pe_snk_epr_mode_entry_wait_for_response_entry(int port) +{ + print_current_state(port); + /* Wait for EPR Enter Mode response */ +} + +static void pe_snk_epr_mode_entry_wait_for_response_run(int port) +{ + if (PE_CHK_FLAG(port, PE_FLAGS_MSG_RECEIVED)) { + uint8_t type = PD_HEADER_TYPE(rx_emsg[port].header); + uint8_t cnt = PD_HEADER_CNT(rx_emsg[port].header); + uint8_t ext = PD_HEADER_EXT(rx_emsg[port].header); + + PE_CLR_FLAG(port, PE_FLAGS_MSG_RECEIVED); + if ((ext == 0) && (cnt > 0) && (type == PD_DATA_EPR_MODE)) { + struct eprmdo *eprmdo = (void *)rx_emsg[port].buf; + + if (eprmdo->action == PD_EPRMDO_ACTION_ENTER_SUCCESS) { + pe_enter_epr_mode(port); + set_state_pe(port, + PE_SNK_WAIT_FOR_CAPABILITIES); + return; + } else if (eprmdo->action == + PD_EPRMDO_ACTION_ENTER_FAILED) { + /* Table 6-50 EPR Mode Data Object */ + CPRINTS("C%d: Failed to enter EPR for 0x%x", + port, eprmdo->data); + } + /* Fall through to soft reset. */ + } + /* + * 6.4.10.1 Process to enter EPR Mode + * "3. If the Sink receives any Message, other than an + * EPR_ModeMessage with ENTER_SUCCESS, the Sink Shall initiate a + * Soft Reset." + */ + pe_send_soft_reset(port, TCPCI_MSG_SOP); + return; + } + + /* When the SinkEPREnterTimer times out, send a soft reset. */ + if (pd_timer_is_expired(port, PE_TIMER_SINK_EPR_ENTER)) { + PE_SET_FLAG(port, PE_FLAGS_SNK_WAIT_CAP_TIMEOUT); + pe_send_soft_reset(port, TCPCI_MSG_SOP); + } +} + +static void pe_snk_epr_mode_entry_wait_for_response_exit(int port) +{ + pd_timer_disable(port, PE_TIMER_SINK_EPR_ENTER); + /* + * Figure 8-215 indicates a sink shall enter EPR Mode on exit but Figure + * 6-34 indicates we enter EPR mode only on success (and soft reset + * otherwise). Since the later makes sense, we don't enter EPR here. + */ +} + +/* + * PE_SNK_SEND_EPR_MODE_EXIT + */ +static void pe_snk_send_epr_mode_exit_entry(int port) +{ + struct eprmdo *eprmdo = (void *)tx_emsg[port].buf; + + print_current_state(port); + + /* Send EPR mode entry message */ + eprmdo->action = PD_EPRMDO_ACTION_EXIT; + eprmdo->data = 0; + eprmdo->reserved = 0; + tx_emsg[port].len = sizeof(*eprmdo); + + send_data_msg(port, TCPCI_MSG_SOP, PD_DATA_EPR_MODE); + pe_sender_response_msg_entry(port); +} + +static void pe_snk_send_epr_mode_exit_run(int port) +{ + enum pe_msg_check msg_check = pe_sender_response_msg_run(port); + + if (msg_check & PE_MSG_DISCARDED) { + set_state_pe(port, PE_SNK_READY); + return; + } + + if (msg_check & PE_MSG_SENT) { + pe_sender_response_msg_exit(port); + pe_exit_epr_mode(port); + set_state_pe(port, PE_SNK_WAIT_FOR_CAPABILITIES); + } +} + +/* + * PE_SNK_EPR_MODE_EXIT_RECEIVED + */ +static void pe_snk_epr_mode_exit_received_entry(int port) +{ + print_current_state(port); + + /* + * Table 8-22 Steps for Exiting EPR Mode (Source Initiated) states 'The + * Port Partners are in an Explicit Contract using an SPR PDO.' Thus, + * it's expected Source already has sent new SPR PDOs (and we switched + * to a SPR contract) before it sent EPR mode exit. Violation of this + * results in a hard reset (6.4.10.3.3 Exits due to errors). + */ + if (!pe_in_spr_contract(port)) { + CPRINTS("C%d: Received EPR exit while in EPR contract", port); + pe_set_hard_reset(port); + return; + } + + pe_exit_epr_mode(port); + set_state_pe(port, PE_SNK_WAIT_FOR_CAPABILITIES); +} +#endif /* CONFIG_USB_PD_EPR */ + const uint32_t *const pd_get_src_caps(int port) { return pe[port].src_caps; @@ -8221,6 +8662,29 @@ static __const_data const struct usb_state pe_states[] = { .exit = pe_ddr_perform_data_reset_exit, }, #endif /* CONFIG_USB_PD_DATA_RESET_MSG */ +#ifdef CONFIG_USB_PD_EPR + [PE_SNK_EPR_KEEP_ALIVE] = { + .entry = pe_snk_epr_keep_alive_entry, + .run = pe_snk_epr_keep_alive_run, + }, + [PE_SNK_SEND_EPR_MODE_ENTRY] = { + .entry = pe_snk_send_epr_mode_entry_entry, + .run = pe_snk_send_epr_mode_entry_run, + .exit = pe_snk_send_epr_mode_entry_exit, + }, + [PE_SNK_EPR_MODE_ENTRY_WAIT_FOR_RESPONSE] = { + .entry = pe_snk_epr_mode_entry_wait_for_response_entry, + .run = pe_snk_epr_mode_entry_wait_for_response_run, + .exit = pe_snk_epr_mode_entry_wait_for_response_exit, + }, + [PE_SNK_SEND_EPR_MODE_EXIT] = { + .entry = pe_snk_send_epr_mode_exit_entry, + .run = pe_snk_send_epr_mode_exit_run, + }, + [PE_SNK_EPR_MODE_EXIT_RECEIVED] = { + .entry = pe_snk_epr_mode_exit_received_entry, + }, +#endif /* CONFIG_USB_PD_EPR */ #endif /* CONFIG_USB_PD_REV30 */ }; |