diff options
-rw-r--r-- | common/usb_pd_dual_role.c | 10 | ||||
-rw-r--r-- | common/usbc/usb_pe_drp_sm.c | 476 | ||||
-rw-r--r-- | common/usbc/usb_pe_private.h | 2 | ||||
-rw-r--r-- | include/config.h | 5 | ||||
-rw-r--r-- | include/usb_pd.h | 69 | ||||
-rw-r--r-- | include/usb_pd_timer.h | 8 | ||||
-rw-r--r-- | include/usb_pe_sm.h | 16 | ||||
-rw-r--r-- | zephyr/Kconfig.pd | 7 |
8 files changed, 586 insertions, 7 deletions
diff --git a/common/usb_pd_dual_role.c b/common/usb_pd_dual_role.c index a17c8b926e..44cd9c0853 100644 --- a/common/usb_pd_dual_role.c +++ b/common/usb_pd_dual_role.c @@ -99,6 +99,12 @@ int pd_find_pdo_index(uint32_t src_cap_cnt, const uint32_t *const src_caps, /* Skip invalid voltage */ if (!mv) continue; + /* + * It's illegal to have EPR PDO in 1...7. + * TODO: This is supposed to be a hard reset (8.3.3.3.8) + */ + if (i < 7 && mv > PD_MAX_SPR_VOLTAGE) + continue; /* Skip any voltage not supported by this board */ if (!pd_is_valid_input_voltage(mv)) continue; @@ -317,6 +323,10 @@ void pd_build_request(int32_t vpd_vdo, uint32_t *rdo, uint32_t *ma, if (uw < (1000 * PD_OPERATING_POWER_MW)) flags |= RDO_CAP_MISMATCH; + /* b:271612382S has more details. */ + if (IS_ENABLED(CONFIG_USB_PD_EPR)) + flags |= RDO_EPR_MODE_CAPABLE; + #ifdef CONFIG_USB_PD_GIVE_BACK /* Tell source we are give back capable. */ flags |= RDO_GIVE_BACK; 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 */ }; diff --git a/common/usbc/usb_pe_private.h b/common/usbc/usb_pe_private.h index 6bbf947bea..d1a6a3c9fa 100644 --- a/common/usbc/usb_pe_private.h +++ b/common/usbc/usb_pe_private.h @@ -84,6 +84,8 @@ enum { PE_FLAGS_DATA_RESET_COMPLETE_FN, /* Waiting for SRC to SNK settle time */ PE_FLAGS_SRC_SNK_SETTLE_FN, + /* In EPR mode */ + PE_FLAGS_IN_EPR_FN, /* Last element */ PE_FLAGS_COUNT }; diff --git a/include/config.h b/include/config.h index dc88b31d7d..d08214c17a 100644 --- a/include/config.h +++ b/include/config.h @@ -5130,6 +5130,11 @@ #undef CONFIG_USB_PD_FRS /* + * Enable USB-PD extended power range. + */ +#undef CONFIG_USB_PD_EPR + +/* * USB Product ID. Each platform (e.g. baseboard set) should have a single * VID/PID combination. If there is a big enough change within a platform, * then we can differentiate USB topologies by varying the HW version field diff --git a/include/usb_pd.h b/include/usb_pd.h index 75808581ca..0b30ff11a0 100644 --- a/include/usb_pd.h +++ b/include/usb_pd.h @@ -76,7 +76,11 @@ enum pd_rx_errors { PD_EVENT_POWER_STATE_CHANGE | PD_EVENT_TCPC_RESET) /* --- PD data message helpers --- */ +#ifdef CONFIG_USB_PD_EPR +#define PDO_MAX_OBJECTS 11 +#else #define PDO_MAX_OBJECTS 7 +#endif /* PDO : Power Data Object */ /* @@ -99,6 +103,7 @@ enum pd_rx_errors { #define PDO_FIXED_FRS_CURR_DFLT_USB_POWER (1 << 23) #define PDO_FIXED_FRS_CURR_1A5_AT_5V (2 << 23) #define PDO_FIXED_FRS_CURR_3A0_AT_5V (3 << 23) +#define PDO_FIXED_EPR_MODE_CAPABLE BIT(23) #define PDO_FIXED_PEAK_CURR () /* [21..20] Peak current */ #define PDO_FIXED_VOLT(mv) (((mv) / 50) << 10) /* Voltage in 50mV units */ #define PDO_FIXED_CURR(ma) (((ma) / 10) << 0) /* Max current in 10mA units */ @@ -139,6 +144,7 @@ enum pd_rx_errors { #define RDO_CAP_MISMATCH BIT(26) #define RDO_COMM_CAP BIT(25) #define RDO_NO_SUSPEND BIT(24) +#define RDO_EPR_MODE_CAPABLE BIT(22) #define RDO_FIXED_VAR_OP_CURR(ma) ((((ma) / 10) & 0x3FF) << 10) #define RDO_FIXED_VAR_MAX_CURR(ma) ((((ma) / 10) & 0x3FF) << 0) @@ -199,6 +205,7 @@ enum pd_rx_errors { #define PD_T_SINK_WAIT_CAP (575 * MSEC) /* between 310ms and 620ms */ #define PD_T_SINK_TRANSITION (35 * MSEC) /* between 20ms and 35ms */ #define PD_T_SOURCE_ACTIVITY (45 * MSEC) /* between 40ms and 50ms */ +#define PD_T_ENTER_EPR (500 * MSEC) /* between 450ms and 550ms */ /* * Adjusting for TCPMv2 PD2 Compliance. In tests like TD.PD.SRC.E5 this * value is the duration before the Hard Reset can be sent. Setting the @@ -261,6 +268,7 @@ enum pd_rx_errors { #define PD_T_DATA_RESET_FAIL (300 * MSEC) /* 300ms */ #define PD_T_VCONN_REAPPLIED (10 * MSEC) /* between 10ms and 20ms */ #define PD_T_VCONN_DISCHARGE (240 * MSEC) /* between 160ms and 240ms */ +#define PD_T_SINK_EPR_KEEP_ALIVE (375 * MSEC) /* between 250ms and 500ms */ /* * Non-spec timer to prevent going Unattached if Vbus drops before a partner FRS @@ -298,9 +306,15 @@ enum pd_rx_errors { #define PD_V_SINK_DISCONNECT_MAX 3670 /* TODO(b/149530538): Add equation for vSinkDisconnectPD */ -/* Maximum voltage in mV offered by PD 3.0 Version 2.0 Spec */ +/* Maximum SPR voltage in mV offered by PD 3.0 Version 2.0 Spec */ #define PD_REV3_MAX_VOLTAGE 20000 +/* Maximum SPR voltage in mV */ +#define PD_MAX_SPR_VOLTAGE 20000 + +/* Maximum EPR voltage in mV */ +#define PD_MAX_EPR_VOLTAGE 48000 + /* Power in mW at which we will automatically charge from a DRP partner */ #define PD_DRP_CHARGE_POWER_MIN 27000 @@ -599,6 +613,57 @@ struct partner_active_modes { /* Max Attention length is header + 1 VDO */ #define PD_ATTENTION_MAX_VDO 2 +/* + * 6.4.10 EPR_Mode Message (PD Rev 3.1) + */ + +enum pd_eprmdo_action { + /* 0x00: Reserved */ + PD_EPRMDO_ACTION_ENTER = 0x01, + PD_EPRMDO_ACTION_ENTER_ACK = 0x02, + PD_EPRMDO_ACTION_ENTER_SUCCESS = 0x03, + PD_EPRMDO_ACTION_ENTER_FAILED = 0x04, + PD_EPRMDO_ACTION_EXIT = 0x05, + /* 0x06 ... 0xFF: Reserved */ +} __packed; +BUILD_ASSERT(sizeof(enum pd_eprmdo_action) == 1); + +enum pd_eprmdo_enter_failed_data { + PD_EPRMDO_ENTER_FAILED_DATA_UNKNOWN = 0x00, + PD_EPRMDO_ENTER_FAILED_DATA_CABLE = 0x01, + PD_EPRMDO_ENTER_FAILED_DATA_VCONN = 0x02, + PD_EPRMDO_ENTER_FAILED_DATA_RDO = 0x03, + PD_EPRMDO_ENTER_FAILED_DATA_UNABLE = 0x04, + PD_EPRMDO_ENTER_FAILED_DATA_PDO = 0x05, +} __packed; +BUILD_ASSERT(sizeof(enum pd_eprmdo_enter_failed_data) == 1); + +struct eprmdo { + uint16_t reserved; + enum pd_eprmdo_enter_failed_data data; + enum pd_eprmdo_action action; +}; +BUILD_ASSERT(sizeof(struct eprmdo) == 4); + +/* + * 6.5.14 Extended Control Message + */ +enum pd_ext_ctrl_msg_type { + /* 0: Reserved */ + PD_EXT_CTRL_EPR_GET_SOURCE_CAP = 1, + PD_EXT_CTRL_EPR_GET_SINK_CAP = 2, + PD_EXT_CTRL_EPR_KEEPALIVE = 3, + PD_EXT_CTRL_EPR_KEEPALIVE_ACK = 4, + /* 5-255: Reserved */ +} __packed; +BUILD_ASSERT(sizeof(enum pd_ext_ctrl_msg_type) == 1); + +/* Extended Control Data Block (ECDB) */ +struct pd_ecdb { + uint8_t type; + uint8_t data; +} __packed; + /* PD Rev 3.1 Revision Message Data Object (RMDO) */ struct rmdo { uint32_t reserved : 16; @@ -1044,6 +1109,8 @@ enum pd_dpm_request { DPM_REQUEST_FRS_DET_DISABLE = BIT(22), DPM_REQUEST_DATA_RESET = BIT(23), DPM_REQUEST_GET_REVISION = BIT(24), + DPM_REQUEST_EPR_MODE_ENTRY = BIT(25), + DPM_REQUEST_EPR_MODE_EXIT = BIT(26), }; /** diff --git a/include/usb_pd_timer.h b/include/usb_pd_timer.h index 687c980243..60ea3b1479 100644 --- a/include/usb_pd_timer.h +++ b/include/usb_pd_timer.h @@ -113,6 +113,14 @@ enum pd_task_timer { PE_TIMER_SENDER_RESPONSE, /* + * 6.6.21 EPR Timers of PD R3.1 V1.6 + * This timer is used to ensure the EPR Mode entry process completes + * within PD_T_ENTER_EPR. + */ + PE_TIMER_SINK_EPR_ENTER, + PE_TIMER_SINK_EPR_KEEP_ALIVE, + + /* * This timer is used to ensure that the time before the next Sink * Request Message, after a Wait Message has been received from the * Source in response to a Sink Request Message. diff --git a/include/usb_pe_sm.h b/include/usb_pe_sm.h index f8d20a0394..9672c6b26b 100644 --- a/include/usb_pe_sm.h +++ b/include/usb_pe_sm.h @@ -201,4 +201,20 @@ void pe_clear_ado(int port); void pe_clear_port_data(int port); #endif /* TEST_BUILD */ +/** + * Check whether the port is in EPR mode or not. + * + * @param port USB-C port number + * @return true if the port is in EPR mode or false. + */ +bool pe_snk_in_epr_mode(int port); + +/** + * Checks whether the port is ready for EPR entry. + * + * @param port USB-C port number + * @return true if the port can enter EPR mode or false. + */ +bool pe_snk_can_enter_epr_mode(int port); + #endif /* __CROS_EC_USB_PE_H */ diff --git a/zephyr/Kconfig.pd b/zephyr/Kconfig.pd index 68ecfe78e6..6dc7df3049 100644 --- a/zephyr/Kconfig.pd +++ b/zephyr/Kconfig.pd @@ -470,6 +470,13 @@ config PLATFORM_EC_USB_PD_LONG_PRESS_MAX_MS partner in ms. Any press longer than this will not be considered a valid USB PD button press. +config PLATFORM_EC_USB_PD_EPR + bool "USB PD Extended Power Range support" + depends on PLATFORM_EC_USB_PD_REV30 + default n + help + This enables support for EPR (Extended Power Range). + rsource "Kconfig.svdm_rsp" endif # PLATFORM_EC_USB_POWER_DELIVERY |