summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/usb_pd_dual_role.c10
-rw-r--r--common/usbc/usb_pe_drp_sm.c476
-rw-r--r--common/usbc/usb_pe_private.h2
-rw-r--r--include/config.h5
-rw-r--r--include/usb_pd.h69
-rw-r--r--include/usb_pd_timer.h8
-rw-r--r--include/usb_pe_sm.h16
-rw-r--r--zephyr/Kconfig.pd7
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