summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaisuke Nojiri <dnojiri@chromium.org>2022-12-12 17:27:50 -0800
committerChromeos LUCI <chromeos-scoped@luci-project-accounts.iam.gserviceaccount.com>2023-03-10 00:19:04 +0000
commitb44e58aadac1c4ceeea3bfe0fd988fa1599156c9 (patch)
treec4a4974a4b2bbbc64c456af90bbbf2b8a2a83d2f
parenta362f8c342528a372d2acaa9fbb1883b8b5a0ec5 (diff)
downloadchrome-ec-b44e58aadac1c4ceeea3bfe0fd988fa1599156c9.tar.gz
USB-PD: Add EPR sink capability to TCPMv2
This patch adds EPR capability for a sink port in TCPMv2. Done: - PE_SNK_EPR_Mode_Entry - PE_SNK_EPR_Mode_Wait_For_Response - PE_SNK_EPR_Mode_Exit - PE_SNK_EPR_Mode_Exit_Received - PE_SNK_EPR_Keep_Alive - EPR_Source_Capabilities message (6.5.15.2) - EPR_Request message (6.4.9) - EPR exit initiated by Sink (6.4.10.3) - EPR exit initiated by Source (2.8 step 6.a) The following features are not included: - EPR_Get_Sink_Cap message (6.5.14.2) & EPR_Sink_Capabilities message (6.5.15.3) - EPR_Get_Source_Cap message (6.5.14.1) - EPR adjustable voltage supply in APDO (6.4.1.2.5.2) - Stop SinkEPRKeepAliveTimer whenever - GoodCRC is transmitted in response to any message from source. - GoodCRC is received in response to any message sent to source. - Make EC_CMD_TYPEC_STATUS host 11 PDOs. - Implicit Exit (6.4.10.3.2) - Exits due to errors (6.4.10.3.3) References: 6.4.10 EPR_Mode Message (ERPMDO) 8.3.2.2.2.1.1 Entering EPR Mode (Success) 8.3.2.2.2.5.1 Exiting EPR Mode (Sink Initiated) 8.3.3 Policy Engine Sink Port State Diagram 8.3.3.27.2 Sink EPR Mode Entry State Diagram 8.3.3.27.4 Sink EPR Mode Exit State Diagram BRANCH=None BUG=b:257320026 TEST=Agah. Run 'pd 1 epr enter/exit' with Apple and Anker charger. Signed-off-by: Daisuke Nojiri <dnojiri@chromium.org> Change-Id: I54c711865a51cc18460feace21d04144c7420b06 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/4290370 Reviewed-by: Diana Z <dzigterman@chromium.org>
-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