diff options
Diffstat (limited to 'common/usbc')
-rw-r--r-- | common/usbc/build.mk | 1 | ||||
-rw-r--r-- | common/usbc/dp_alt_mode.c | 103 | ||||
-rw-r--r-- | common/usbc/tbt_alt_mode.c | 104 | ||||
-rw-r--r-- | common/usbc/usb_mode.c | 5 | ||||
-rw-r--r-- | common/usbc/usb_pd_dpm.c | 65 | ||||
-rw-r--r-- | common/usbc/usb_pd_timer.c | 99 | ||||
-rw-r--r-- | common/usbc/usb_pe_drp_sm.c | 3 | ||||
-rw-r--r-- | common/usbc/usb_tc_drp_acc_trysrc_sm.c | 43 |
8 files changed, 294 insertions, 129 deletions
diff --git a/common/usbc/build.mk b/common/usbc/build.mk index 48ab5351b8..60e2347741 100644 --- a/common/usbc/build.mk +++ b/common/usbc/build.mk @@ -46,6 +46,7 @@ all-obj-$(CONFIG_USB_PD_ALT_MODE_UFP_DP)+=$(_usbc_dir)usb_pd_dp_ufp.o endif # CONFIG_USB_PD_TCPMV2 # For testing +all-obj-$(CONFIG_TEST_USB_PD_TIMER)+=$(_usbc_dir)usb_pd_timer.o all-obj-$(CONFIG_TEST_USB_PE_SM)+=$(_usbc_dir)usbc_pd_policy.o all-obj-$(CONFIG_TEST_USB_PE_SM)+=$(_usbc_dir)usb_pe_drp_sm.o all-obj-$(CONFIG_TEST_SM)+=$(_usbc_dir)usb_sm.o diff --git a/common/usbc/dp_alt_mode.c b/common/usbc/dp_alt_mode.c index 9a3493c6e1..2a532466ac 100644 --- a/common/usbc/dp_alt_mode.c +++ b/common/usbc/dp_alt_mode.c @@ -12,6 +12,7 @@ #include <stdbool.h> #include <stdint.h> #include "assert.h" +#include "atomic.h" #include "usb_common.h" #include "usb_dp_alt_mode.h" #include "usb_pd.h" @@ -31,8 +32,10 @@ enum dp_states { DP_ENTER_ACKED, DP_ENTER_NAKED, DP_STATUS_ACKED, + DP_PREPARE_CONFIG, DP_ACTIVE, DP_ENTER_RETRY, + DP_PREPARE_EXIT, DP_INACTIVE, DP_STATE_COUNT }; @@ -45,20 +48,31 @@ static enum dp_states dp_state[CONFIG_USB_PD_PORT_MAX_COUNT]; static const uint8_t state_vdm_cmd[DP_STATE_COUNT] = { [DP_START] = CMD_ENTER_MODE, [DP_ENTER_ACKED] = CMD_DP_STATUS, - [DP_STATUS_ACKED] = CMD_DP_CONFIG, - [DP_ACTIVE] = CMD_EXIT_MODE, - [DP_ENTER_NAKED] = CMD_EXIT_MODE, + [DP_PREPARE_CONFIG] = CMD_DP_CONFIG, + [DP_PREPARE_EXIT] = CMD_EXIT_MODE, [DP_ENTER_RETRY] = CMD_ENTER_MODE, }; +/* + * Track if we're retrying due to an Enter Mode NAK + */ +#define DP_FLAG_RETRY BIT(0) + +static uint32_t dpm_dp_flags[CONFIG_USB_PD_PORT_MAX_COUNT]; + +#define DP_SET_FLAG(port, flag) atomic_or(&dpm_dp_flags[port], (flag)) +#define DP_CLR_FLAG(port, flag) atomic_clear_bits(&dpm_dp_flags[port], (flag)) +#define DP_CHK_FLAG(port, flag) (dpm_dp_flags[port] & (flag)) + bool dp_is_active(int port) { - return dp_state[port] == DP_ACTIVE; + return dp_state[port] == DP_ACTIVE || dp_state[port] == DP_PREPARE_EXIT; } void dp_init(int port) { dp_state[port] = DP_START; + dpm_dp_flags[port] = 0; } bool dp_entry_is_done(int port) @@ -71,6 +85,7 @@ static void dp_entry_failed(int port) { CPRINTS("C%d: DP alt mode protocol failed!", port); dp_state[port] = DP_INACTIVE; + dpm_dp_flags[port] = 0; } static bool dp_response_valid(int port, enum tcpci_msg_type type, @@ -135,25 +150,23 @@ void dp_vdm_acked(int port, enum tcpci_msg_type type, int vdo_count, dfp_consume_attention(port, vdm); dp_state[port] = DP_STATUS_ACKED; break; - case DP_STATUS_ACKED: + case DP_PREPARE_CONFIG: if (modep && modep->opos && modep->fx->post_config) modep->fx->post_config(port); dp_state[port] = DP_ACTIVE; CPRINTS("C%d: Entered DP mode", port); break; - case DP_ACTIVE: + case DP_PREPARE_EXIT: /* * Request to exit mode successful, so put the module in an - * inactive state. + * inactive state or give entry another shot. */ - dp_exit_to_usb_mode(port); - break; - case DP_ENTER_NAKED: - /* - * The request to exit the mode was successful, - * so try to enter the mode again. - */ - dp_state[port] = DP_ENTER_RETRY; + if (DP_CHK_FLAG(port, DP_FLAG_RETRY)) { + dp_state[port] = DP_ENTER_RETRY; + DP_CLR_FLAG(port, DP_FLAG_RETRY); + } else { + dp_exit_to_usb_mode(port); + } break; case DP_INACTIVE: /* @@ -195,7 +208,7 @@ void dp_vdm_naked(int port, enum tcpci_msg_type type, uint8_t vdm_cmd) */ dp_entry_failed(port); break; - case DP_ACTIVE: + case DP_PREPARE_EXIT: /* Treat an Exit Mode NAK the same as an Exit Mode ACK. */ dp_exit_to_usb_mode(port); break; @@ -207,14 +220,15 @@ void dp_vdm_naked(int port, enum tcpci_msg_type type, uint8_t vdm_cmd) } } -int dp_setup_next_vdm(int port, int vdo_count, uint32_t *vdm) +enum dpm_msg_setup_status dp_setup_next_vdm(int port, int *vdo_count, + uint32_t *vdm) { const struct svdm_amode_data *modep = pd_get_amode_data(port, TCPCI_MSG_SOP, USB_SID_DISPLAYPORT); int vdo_count_ret; - if (vdo_count < VDO_MAX_SIZE) - return -1; + if (*vdo_count < VDO_MAX_SIZE) + return MSG_SETUP_ERROR; switch (dp_state[port]) { case DP_START: @@ -223,7 +237,7 @@ int dp_setup_next_vdm(int port, int vdo_count, uint32_t *vdm) vdm[0] = pd_dfp_enter_mode(port, TCPCI_MSG_SOP, USB_SID_DISPLAYPORT, 0); if (vdm[0] == 0) - return -1; + return MSG_SETUP_ERROR; /* CMDT_INIT is 0, so this is a no-op */ vdm[0] |= VDO_CMDT(CMDT_INIT); vdm[0] |= VDO_SVDM_VERS(pd_get_vdo_ver(port, TCPCI_MSG_SOP)); @@ -233,26 +247,48 @@ int dp_setup_next_vdm(int port, int vdo_count, uint32_t *vdm) break; case DP_ENTER_ACKED: if (!(modep && modep->opos)) - return -1; + return MSG_SETUP_ERROR; vdo_count_ret = modep->fx->status(port, vdm); if (vdo_count_ret == 0) - return -1; + return MSG_SETUP_ERROR; vdm[0] |= PD_VDO_OPOS(modep->opos); vdm[0] |= VDO_CMDT(CMDT_INIT); vdm[0] |= VDO_SVDM_VERS(pd_get_vdo_ver(port, TCPCI_MSG_SOP)); break; case DP_STATUS_ACKED: if (!(modep && modep->opos)) - return -1; + return MSG_SETUP_ERROR; + + if (!get_dp_pin_mode(port)) + return MSG_SETUP_ERROR; + dp_state[port] = DP_PREPARE_CONFIG; + + /* + * Place the USB Type-C pins that are to be re-configured to + * DisplayPort Configuration into the Safe state. For + * USB_PD_MUX_DOCK, the superspeed signals can remain + * connected. For USB_PD_MUX_DP_ENABLED, disconnect the + * superspeed signals here, before the pins are re-configured + * to DisplayPort (in svdm_dp_post_config, when we receive + * the config ack). + */ + if (svdm_dp_get_mux_mode(port) == USB_PD_MUX_DP_ENABLED) { + usb_mux_set_safe_mode(port); + return MSG_SETUP_MUX_WAIT; + } + /* Fall through if no mux set is needed */ + case DP_PREPARE_CONFIG: vdo_count_ret = modep->fx->config(port, vdm); if (vdo_count_ret == 0) - return -1; + return MSG_SETUP_ERROR; vdm[0] |= VDO_CMDT(CMDT_INIT); vdm[0] |= VDO_SVDM_VERS(pd_get_vdo_ver(port, TCPCI_MSG_SOP)); break; case DP_ENTER_NAKED: + DP_SET_FLAG(port, DP_FLAG_RETRY); + /* Fall through to send exit mode */ case DP_ACTIVE: /* * Called to exit DP alt mode, either when the mode @@ -265,10 +301,13 @@ int dp_setup_next_vdm(int port, int vdo_count, uint32_t *vdm) * TODO(b/159856063): Clean up the API to the fx functions. */ if (!(modep && modep->opos)) - return -1; + return MSG_SETUP_ERROR; usb_mux_set_safe_mode_exit(port); - + dp_state[port] = DP_PREPARE_EXIT; + return MSG_SETUP_MUX_WAIT; + case DP_PREPARE_EXIT: + /* DPM should call setup only after safe state is set */ vdm[0] = VDO(USB_SID_DISPLAYPORT, 1, /* structured */ CMD_EXIT_MODE); @@ -282,11 +321,17 @@ int dp_setup_next_vdm(int port, int vdo_count, uint32_t *vdm) /* * DP mode is inactive. */ - return -1; + return MSG_SETUP_ERROR; default: CPRINTF("%s called with invalid state %d\n", __func__, dp_state[port]); - return -1; + return MSG_SETUP_ERROR; } - return vdo_count_ret; + + if (vdo_count_ret) { + *vdo_count = vdo_count_ret; + return MSG_SETUP_SUCCESS; + } + + return MSG_SETUP_UNSUPPORTED; } diff --git a/common/usbc/tbt_alt_mode.c b/common/usbc/tbt_alt_mode.c index 73e2796345..5baf9d1a73 100644 --- a/common/usbc/tbt_alt_mode.c +++ b/common/usbc/tbt_alt_mode.c @@ -90,6 +90,8 @@ enum tbt_states { TBT_START = 0, TBT_ENTER_SOP, TBT_ACTIVE, + /* Set to force Exit mode from non-Active states */ + TBT_PREPARE_EXIT_MODE, TBT_EXIT_SOP, TBT_INACTIVE, /* Active cable only */ @@ -103,7 +105,6 @@ static enum tbt_states tbt_state[CONFIG_USB_PD_PORT_MAX_COUNT]; static const uint8_t state_vdm_cmd[TBT_STATE_COUNT] = { [TBT_ENTER_SOP] = CMD_ENTER_MODE, - [TBT_ACTIVE] = CMD_EXIT_MODE, [TBT_EXIT_SOP] = CMD_EXIT_MODE, /* Active cable only */ [TBT_ENTER_SOP_PRIME] = CMD_ENTER_MODE, @@ -299,27 +300,18 @@ void intel_vdm_acked(int port, enum tcpci_msg_type type, int vdo_count, /* Indicate to PE layer that alt mode is active */ pd_set_dfp_enter_mode_flag(port, true); break; - case TBT_ACTIVE: + case TBT_EXIT_SOP: tbt_prints("exit mode SOP", port); opos_sop = pd_alt_mode(port, TCPCI_MSG_SOP, USB_VID_INTEL); /* Clear Thunderbolt related signals */ - pd_dfp_exit_mode(port, TCPCI_MSG_SOP, USB_VID_INTEL, opos_sop); - set_usb_mux_with_current_data_role(port); + if (opos_sop > 0) + pd_dfp_exit_mode(port, TCPCI_MSG_SOP, USB_VID_INTEL, + opos_sop); if (get_usb_pd_cable_type(port) == IDH_PTYPE_ACABLE) { tbt_active_cable_exit_mode(port); } else { - /* - * Exit Mode process is complete; go to inactive state. - */ - tbt_exit_done(port); - } - break; - case TBT_EXIT_SOP: - set_usb_mux_with_current_data_role(port); - if (get_usb_pd_cable_type(port) == IDH_PTYPE_ACABLE) - tbt_active_cable_exit_mode(port); - else { + set_usb_mux_with_current_data_role(port); if (TBT_CHK_FLAG(port, TBT_FLAG_RETRY_DONE)) /* retried enter mode, still failed, give up */ tbt_exit_done(port); @@ -330,7 +322,6 @@ void intel_vdm_acked(int port, enum tcpci_msg_type type, int vdo_count, case TBT_EXIT_SOP_PRIME_PRIME: tbt_prints("exit mode SOP''", port); tbt_state[port] = TBT_EXIT_SOP_PRIME; - set_usb_mux_with_current_data_role(port); break; case TBT_EXIT_SOP_PRIME: tbt_prints("exit mode SOP'", port); @@ -382,26 +373,15 @@ void intel_vdm_naked(int port, enum tcpci_msg_type type, uint8_t vdm_cmd) * so request to exit the mode first before retrying the enter * command. This can happen if the EC is restarted */ - tbt_state[port] = TBT_EXIT_SOP; - break; - case TBT_ACTIVE: - /* Exit SOP got NAK'ed */ - set_usb_mux_with_current_data_role(port); - if (get_usb_pd_cable_type(port) == IDH_PTYPE_ACABLE) - tbt_active_cable_exit_mode(port); - else { - tbt_prints("exit mode SOP failed", port); - tbt_state[port] = TBT_INACTIVE; - TBT_CLR_FLAG(port, TBT_FLAG_RETRY_DONE); - } + tbt_state[port] = TBT_PREPARE_EXIT_MODE; break; case TBT_EXIT_SOP: /* Exit SOP got NAK'ed */ tbt_prints("exit mode SOP failed", port); - set_usb_mux_with_current_data_role(port); if (get_usb_pd_cable_type(port) == IDH_PTYPE_ACABLE) tbt_active_cable_exit_mode(port); else { + set_usb_mux_with_current_data_role(port); if (TBT_CHK_FLAG(port, TBT_FLAG_RETRY_DONE)) /* Retried enter mode, still failed, give up */ tbt_exit_done(port); @@ -410,7 +390,6 @@ void intel_vdm_naked(int port, enum tcpci_msg_type type, uint8_t vdm_cmd) } break; case TBT_EXIT_SOP_PRIME_PRIME: - set_usb_mux_with_current_data_role(port); tbt_prints("exit mode SOP'' failed", port); tbt_state[port] = TBT_EXIT_SOP_PRIME; break; @@ -458,8 +437,9 @@ static bool tbt_mode_is_supported(int port, int vdo_count) return true; } -int tbt_setup_next_vdm(int port, int vdo_count, uint32_t *vdm, - enum tcpci_msg_type *tx_type) +enum dpm_msg_setup_status tbt_setup_next_vdm(int port, int *vdo_count, + uint32_t *vdm, + enum tcpci_msg_type *tx_type) { struct svdm_amode_data *modep; int vdo_count_ret = 0; @@ -467,36 +447,39 @@ int tbt_setup_next_vdm(int port, int vdo_count, uint32_t *vdm, *tx_type = TCPCI_MSG_SOP; - if (vdo_count < VDO_MAX_SIZE) - return -1; + if (*vdo_count < VDO_MAX_SIZE) + return MSG_SETUP_ERROR; switch (tbt_state[port]) { case TBT_START: - if (!tbt_mode_is_supported(port, vdo_count)) - return 0; + if (!tbt_mode_is_supported(port, *vdo_count)) + return MSG_SETUP_UNSUPPORTED; if (!TBT_CHK_FLAG(port, TBT_FLAG_RETRY_DONE)) tbt_prints("attempt to enter mode", port); else tbt_prints("retry to enter mode", port); + /* + * Enter safe mode before sending Enter mode SOP/SOP'/SOP'' + * Ref: Tiger Lake Platform PD Controller Interface + * Requirements for Integrated USB C, section A.1.2 TBT as DFP. + */ + usb_mux_set_safe_mode(port); + cable_mode_resp.raw_value = pd_get_tbt_mode_vdo(port, TCPCI_MSG_SOP_PRIME); /* Active cable and LRD cables send Enter Mode SOP' first */ if (get_usb_pd_cable_type(port) == IDH_PTYPE_ACABLE || cable_mode_resp.tbt_active_passive == TBT_CABLE_ACTIVE) { - vdo_count_ret = enter_tbt_compat_mode(port, - TCPCI_MSG_SOP_PRIME, vdm); - *tx_type = TCPCI_MSG_SOP_PRIME; tbt_state[port] = TBT_ENTER_SOP_PRIME; } else { /* Passive cable send Enter Mode SOP */ - vdo_count_ret = - enter_tbt_compat_mode(port, TCPCI_MSG_SOP, vdm); tbt_state[port] = TBT_ENTER_SOP; } - break; + + return MSG_SETUP_MUX_WAIT; case TBT_ENTER_SOP_PRIME: vdo_count_ret = enter_tbt_compat_mode(port, TCPCI_MSG_SOP_PRIME, vdm); @@ -512,20 +495,30 @@ int tbt_setup_next_vdm(int port, int vdo_count, uint32_t *vdm, vdo_count_ret = enter_tbt_compat_mode(port, TCPCI_MSG_SOP, vdm); break; - case TBT_EXIT_SOP: case TBT_ACTIVE: /* + * Since we had successfully entered mode, consider ourselves + * done with any retires. + */ + TBT_SET_FLAG(port, TBT_FLAG_RETRY_DONE); + /* Fall through */ + case TBT_PREPARE_EXIT_MODE: + /* * Called to exit Thunderbolt alt mode, either when the mode is * active and the system is shutting down, or when an initial * request to enter the mode is NAK'ed. This can happen if EC * is restarted while Thunderbolt mode is active. */ + usb_mux_set_safe_mode_exit(port); + + tbt_state[port] = TBT_EXIT_SOP; + return MSG_SETUP_MUX_WAIT; + case TBT_EXIT_SOP: + /* DPM will only call this after safe state set is done */ modep = pd_get_amode_data(port, TCPCI_MSG_SOP, USB_VID_INTEL); if (!(modep && modep->opos)) - return -1; - - usb_mux_set_safe_mode_exit(port); + return MSG_SETUP_ERROR; vdm[0] = VDO(USB_VID_INTEL, 1, CMD_EXIT_MODE) | VDO_OPOS(modep->opos) | @@ -538,9 +531,7 @@ int tbt_setup_next_vdm(int port, int vdo_count, uint32_t *vdm, modep = pd_get_amode_data(port, TCPCI_MSG_SOP_PRIME, USB_VID_INTEL); if (!(modep && modep->opos)) - return -1; - - usb_mux_set_safe_mode_exit(port); + return MSG_SETUP_ERROR; vdm[0] = VDO(USB_VID_INTEL, 1, CMD_EXIT_MODE) | VDO_OPOS(modep->opos) | @@ -554,9 +545,7 @@ int tbt_setup_next_vdm(int port, int vdo_count, uint32_t *vdm, modep = pd_get_amode_data(port, TCPCI_MSG_SOP_PRIME, USB_VID_INTEL); if (!(modep && modep->opos)) - return -1; - - usb_mux_set_safe_mode_exit(port); + return MSG_SETUP_ERROR; vdm[0] = VDO(USB_VID_INTEL, 1, CMD_EXIT_MODE) | VDO_OPOS(modep->opos) | @@ -568,12 +557,17 @@ int tbt_setup_next_vdm(int port, int vdo_count, uint32_t *vdm, break; case TBT_INACTIVE: /* Thunderbolt mode is inactive */ - return 0; + return MSG_SETUP_UNSUPPORTED; default: CPRINTF("%s called with invalid state %d\n", __func__, tbt_state[port]); - return -1; + return MSG_SETUP_ERROR; + } + + if (vdo_count_ret) { + *vdo_count = vdo_count_ret; + return MSG_SETUP_SUCCESS; } - return vdo_count_ret; + return MSG_SETUP_UNSUPPORTED; } diff --git a/common/usbc/usb_mode.c b/common/usbc/usb_mode.c index b9dc4973bc..7a79ec7a7b 100644 --- a/common/usbc/usb_mode.c +++ b/common/usbc/usb_mode.c @@ -21,6 +21,7 @@ #include "usb_pd_dpm.h" #include "usb_pd_tcpm.h" #include "usb_pe_sm.h" +#include "usb_tbt_alt_mode.h" #include "usbc_ppc.h" #ifdef CONFIG_COMMON_RUNTIME @@ -122,7 +123,9 @@ void usb4_exit_mode_request(int port) { usb4_state[port] = USB4_START; usb_mux_set_safe_mode_exit(port); - set_usb_mux_with_current_data_role(port); + /* If TBT mode is active, leave safe state for mode exit VDMs */ + if (!tbt_is_active(port)) + set_usb_mux_with_current_data_role(port); } void enter_usb_init(int port) diff --git a/common/usbc/usb_pd_dpm.c b/common/usbc/usb_pd_dpm.c index eb2dfc52c0..36cfdf0f75 100644 --- a/common/usbc/usb_pd_dpm.c +++ b/common/usbc/usb_pd_dpm.c @@ -18,9 +18,11 @@ #include "tcpm/tcpm.h" #include "usb_dp_alt_mode.h" #include "usb_mode.h" +#include "usb_mux.h" #include "usb_pd.h" #include "usb_pd_dpm.h" #include "usb_pd_tcpm.h" +#include "usb_pd_pdo.h" #include "usb_tbt_alt_mode.h" #ifdef CONFIG_COMMON_RUNTIME @@ -234,6 +236,7 @@ static void dpm_attempt_mode_entry(int port) enum tcpci_msg_type tx_type = TCPCI_MSG_SOP; bool enter_mode_requested = IS_ENABLED(CONFIG_USB_PD_REQUIRE_AP_MODE_ENTRY) ? false : true; + enum dpm_msg_setup_status status = MSG_SETUP_UNSUPPORTED; if (pd_get_data_role(port) != PD_ROLE_DFP) { if (DPM_CHK_FLAG(port, DPM_FLAG_ENTER_DP | @@ -276,6 +279,14 @@ static void dpm_attempt_mode_entry(int port) return; } + /* + * If muxes are still settling, then wait on our next VDM. We must + * ensure we correctly sequence actions such as USB safe state with TBT + * entry or DP configuration. + */ + if (IS_ENABLED(CONFIG_USBC_SS_MUX) && !usb_mux_set_completed(port)) + return; + /* Check if port, port partner and cable support USB4. */ if (IS_ENABLED(CONFIG_USB_PD_USB4) && board_is_tbt_usb4_port(port) && @@ -287,8 +298,9 @@ static void dpm_attempt_mode_entry(int port) * cable and USB4 mode with the port partner. */ if (tbt_cable_entry_required_for_usb4(port)) { - vdo_count = tbt_setup_next_vdm(port, - ARRAY_SIZE(vdm), vdm, &tx_type); + vdo_count = ARRAY_SIZE(vdm); + status = tbt_setup_next_vdm(port, &vdo_count, vdm, + &tx_type); } else { pd_dpm_request(port, DPM_REQUEST_ENTER_USB); return; @@ -302,24 +314,32 @@ static void dpm_attempt_mode_entry(int port) USB_VID_INTEL) && dpm_mode_entry_requested(port, TYPEC_MODE_TBT)) { enter_mode_requested = true; - vdo_count = tbt_setup_next_vdm(port, - ARRAY_SIZE(vdm), vdm, &tx_type); + vdo_count = ARRAY_SIZE(vdm); + status = tbt_setup_next_vdm(port, &vdo_count, vdm, + &tx_type); } /* If not, check if they support DisplayPort alt mode. */ - if (vdo_count == 0 && !DPM_CHK_FLAG(port, DPM_FLAG_MODE_ENTRY_DONE) && + if (status == MSG_SETUP_UNSUPPORTED && + !DPM_CHK_FLAG(port, DPM_FLAG_MODE_ENTRY_DONE) && pd_is_mode_discovered_for_svid(port, TCPCI_MSG_SOP, - USB_SID_DISPLAYPORT) && + USB_SID_DISPLAYPORT) && dpm_mode_entry_requested(port, TYPEC_MODE_DP)) { enter_mode_requested = true; - vdo_count = dp_setup_next_vdm(port, ARRAY_SIZE(vdm), vdm); + vdo_count = ARRAY_SIZE(vdm); + status = dp_setup_next_vdm(port, &vdo_count, vdm); } + /* Not ready to send a VDM, check again next cycle */ + if (status == MSG_SETUP_MUX_WAIT) + return; + /* * If the PE didn't discover any supported (requested) alternate mode, * just mark setup done and get out of here. */ - if (vdo_count == 0 && !DPM_CHK_FLAG(port, DPM_FLAG_MODE_ENTRY_DONE)) { + if (status != MSG_SETUP_SUCCESS && + !DPM_CHK_FLAG(port, DPM_FLAG_MODE_ENTRY_DONE)) { if (enter_mode_requested) { /* * TODO(b/168030639): Notify the AP that mode entry @@ -335,7 +355,7 @@ static void dpm_attempt_mode_entry(int port) return; } - if (vdo_count < 0) { + if (status != MSG_SETUP_SUCCESS) { dpm_set_mode_entry_done(port); CPRINTS("C%d: Couldn't construct alt mode VDM", port); return; @@ -355,8 +375,9 @@ static void dpm_attempt_mode_entry(int port) static void dpm_attempt_mode_exit(int port) { - uint32_t vdm = 0; - int vdo_count = 0; + uint32_t vdm[VDO_MAX_SIZE]; + int vdo_count = ARRAY_SIZE(vdm); + enum dpm_msg_setup_status status = MSG_SETUP_ERROR; enum tcpci_msg_type tx_type = TCPCI_MSG_SOP; if (IS_ENABLED(CONFIG_USB_PD_USB4) && @@ -364,6 +385,15 @@ static void dpm_attempt_mode_exit(int port) CPRINTS("C%d: USB4 teardown", port); usb4_exit_mode_request(port); } + + /* + * If muxes are still settling, then wait on our next VDM. We must + * ensure we correctly sequence actions such as USB safe state with TBT + * or DP mode exit. + */ + if (IS_ENABLED(CONFIG_USBC_SS_MUX) && !usb_mux_set_completed(port)) + return; + if (IS_ENABLED(CONFIG_USB_PD_TBT_COMPAT_MODE) && tbt_is_active(port)) { /* @@ -373,18 +403,21 @@ static void dpm_attempt_mode_exit(int port) */ CPRINTS("C%d: TBT teardown", port); tbt_exit_mode_request(port); - vdo_count = tbt_setup_next_vdm(port, VDO_MAX_SIZE, &vdm, - &tx_type); + status = tbt_setup_next_vdm(port, &vdo_count, vdm, &tx_type); } else if (dp_is_active(port)) { CPRINTS("C%d: DP teardown", port); - vdo_count = dp_setup_next_vdm(port, VDO_MAX_SIZE, &vdm); + status = dp_setup_next_vdm(port, &vdo_count, vdm); } else { /* Clear exit mode request */ dpm_clear_mode_exit_request(port); return; } - if (!pd_setup_vdm_request(port, tx_type, &vdm, vdo_count)) { + /* This covers error, wait mux, and unsupported cases */ + if (status != MSG_SETUP_SUCCESS) + return; + + if (!pd_setup_vdm_request(port, tx_type, vdm, vdo_count)) { dpm_clear_mode_exit_request(port); return; } @@ -467,7 +500,7 @@ static void balance_source_ports(void) uint32_t removed_ports, new_ports; static bool deferred_waiting; - if (task_get_current() == TASK_ID_HOOKS) + if (in_deferred_context()) deferred_waiting = false; /* diff --git a/common/usbc/usb_pd_timer.c b/common/usbc/usb_pd_timer.c index 67a574904f..97aa699737 100644 --- a/common/usbc/usb_pd_timer.c +++ b/common/usbc/usb_pd_timer.c @@ -4,32 +4,54 @@ */ #include "assert.h" +#include "atomic.h" #include "common.h" #include "console.h" #include "limits.h" +#include "math_util.h" #include "system.h" #include "usb_pd_timer.h" #include "usb_tc_sm.h" #define MAX_PD_PORTS CONFIG_USB_PD_PORT_MAX_COUNT #define MAX_PD_TIMERS PD_TIMER_COUNT -#define PD_TIMERS_ALL_MASK ((uint32_t)(((uint64_t)1 << PD_TIMER_COUNT) - 1)) +#define PD_TIMERS_ALL_MASK (UINT64_MAX >> (64 - PD_TIMER_COUNT)) #define MAX_EXPIRE (0x7FFFFFFF) #define NO_TIMEOUT (-1) #define EXPIRE_NOW (0) -#define PD_SET_ACTIVE(p, m) atomic_or(&timer_active[p], (m)) -#define PD_CLR_ACTIVE(p, m) atomic_clear_bits(&timer_active[p], (m)) -#define PD_CHK_ACTIVE(p, m) (timer_active[p] & (m)) - -#define PD_SET_DISABLED(p, m) atomic_or(&timer_disabled[p], (m)) -#define PD_CLR_DISABLED(p, m) atomic_clear_bits(&timer_disabled[p], (m)) -#define PD_CHK_DISABLED(p, m) (timer_disabled[p] & (m)) - -static uint32_t timer_active[MAX_PD_PORTS]; -static uint32_t timer_disabled[MAX_PD_PORTS]; +#define PD_SET_ACTIVE(p, m) pd_timer_atomic_op( \ + atomic_or, \ + timer_active[p], \ + (m)) +#define PD_CLR_ACTIVE(p, m) pd_timer_atomic_op( \ + atomic_clear_bits, \ + timer_active[p], \ + (m)) +#define PD_CHK_ACTIVE(p, m) ((timer_active[p][0] & ((m) >> 32)) | \ + (timer_active[p][1] & (m))) + +#define PD_SET_DISABLED(p, m) pd_timer_atomic_op( \ + atomic_or, \ + timer_disabled[p], \ + (m)) +#define PD_CLR_DISABLED(p, m) pd_timer_atomic_op( \ + atomic_clear_bits, \ + timer_disabled[p], \ + (m)) +#define PD_CHK_DISABLED(p, m) ((timer_disabled[p][0] & ((m) >> 32)) | \ + (timer_disabled[p][1] & (m))) + +#define TIMER_FIELD_NUM_UINT32S 2 + +test_mockable_static +uint32_t timer_active[MAX_PD_PORTS][TIMER_FIELD_NUM_UINT32S]; +test_mockable_static +uint32_t timer_disabled[MAX_PD_PORTS][TIMER_FIELD_NUM_UINT32S]; static uint64_t timer_expires[MAX_PD_PORTS][MAX_PD_TIMERS]; +BUILD_ASSERT(sizeof(timer_active[0]) * CHAR_BIT >= PD_TIMER_COUNT); +BUILD_ASSERT(sizeof(timer_disabled[0]) * CHAR_BIT >= PD_TIMER_COUNT); /* * CONFIG_CMD_PD_TIMER debug variables @@ -82,9 +104,52 @@ __maybe_unused static __const_data const char * const pd_timer_names[] = { * already and will always return that it is still expired. This timer state * will not adjust the task scheduling timeout value. */ + +/* + * Performs an atomic operation on a PD timer bit field. Atomic operations + * require 32-bit operands, but there are more than 32 timers, so choose the + * correct operand and modify the mask accordingly. + * + * @param op Atomic operation function to call + * @param timer_field Array of timer fields to operate on + * @param mask_val 64-bit mask to apply to the timer field + */ +test_mockable_static void pd_timer_atomic_op( + atomic_val_t (*op)(atomic_t*, atomic_val_t), + uint32_t *const timer_field, const uint64_t mask_val) +{ + uint32_t *atomic_timer_field; + union mask64_t { + struct { +#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + uint32_t lo; + uint32_t hi; +#elif (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + uint32_t hi; + uint32_t lo; +#endif + }; + uint64_t val; + } mask; + + /* + * High-order mask bits correspond to field [0]. Low-order mask bits + * correspond to field [1]. + */ + mask.val = mask_val; + if (mask.hi) { + atomic_timer_field = timer_field; + (void)op(atomic_timer_field, mask.hi); + } + if (mask.lo) { + atomic_timer_field = timer_field + 1; + (void)op(atomic_timer_field, mask.lo); + } +} + static void pd_timer_inactive(int port, enum pd_task_timer timer) { - uint32_t mask = 1 << timer; + uint64_t mask = bitmask_uint64(timer); if (PD_CHK_ACTIVE(port, mask)) { PD_CLR_ACTIVE(port, mask); @@ -97,14 +162,14 @@ static void pd_timer_inactive(int port, enum pd_task_timer timer) static bool pd_timer_is_active(int port, enum pd_task_timer timer) { - uint32_t mask = 1 << timer; + uint64_t mask = bitmask_uint64(timer); return PD_CHK_ACTIVE(port, mask); } static bool pd_timer_is_inactive(int port, enum pd_task_timer timer) { - uint32_t mask = 1 << timer; + uint64_t mask = bitmask_uint64(timer); return !PD_CHK_ACTIVE(port, mask) && !PD_CHK_DISABLED(port, mask); } @@ -123,7 +188,7 @@ void pd_timer_init(int port) void pd_timer_enable(int port, enum pd_task_timer timer, uint32_t expires_us) { - uint32_t mask = 1 << timer; + uint64_t mask = bitmask_uint64(timer); if (!PD_CHK_ACTIVE(port, mask)) { PD_SET_ACTIVE(port, mask); @@ -140,7 +205,7 @@ void pd_timer_enable(int port, enum pd_task_timer timer, uint32_t expires_us) void pd_timer_disable(int port, enum pd_task_timer timer) { - uint32_t mask = 1 << timer; + uint64_t mask = bitmask_uint64(timer); if (PD_CHK_ACTIVE(port, mask)) { PD_CLR_ACTIVE(port, mask); @@ -179,7 +244,7 @@ void pd_timer_disable_range(int port, enum pd_timer_range range) bool pd_timer_is_disabled(int port, enum pd_task_timer timer) { - uint32_t mask = 1 << timer; + uint64_t mask = bitmask_uint64(timer); return PD_CHK_DISABLED(port, mask); } diff --git a/common/usbc/usb_pe_drp_sm.c b/common/usbc/usb_pe_drp_sm.c index c86819d0f7..431dcdc9af 100644 --- a/common/usbc/usb_pe_drp_sm.c +++ b/common/usbc/usb_pe_drp_sm.c @@ -1373,9 +1373,8 @@ static void pe_clear_port_data(int port) pd_set_src_caps(port, 0, NULL); pe_set_snk_caps(port, 0, NULL); - /* Clear any stored modes and discovery data */ + /* Clear any stored discovery data, but leave modes for alt mode exit */ pd_dfp_discovery_init(port); - pd_dfp_mode_init(port); dpm_remove_sink(port); dpm_remove_source(port); diff --git a/common/usbc/usb_tc_drp_acc_trysrc_sm.c b/common/usbc/usb_tc_drp_acc_trysrc_sm.c index 182ea686ec..3fa9528699 100644 --- a/common/usbc/usb_tc_drp_acc_trysrc_sm.c +++ b/common/usbc/usb_tc_drp_acc_trysrc_sm.c @@ -700,9 +700,11 @@ __maybe_unused static void tc_enable_try_src(int en) } /* - * Exit all modes due to a detach event + * Exit all modes due to a detach event or hard reset + * * Note: this skips the ExitMode VDM steps in the PE because it is assumed the - * partner is not present to receive them, and the PE will no longer be running. + * partner is not present to receive them, and the PE will no longer be running, + * or we've forced an abrupt mode exit through a hard reset. */ static void tc_set_modes_exit(int port) { @@ -1304,6 +1306,13 @@ static bool tc_perform_src_hard_reset(int port) /* Set role to DFP */ tc_set_data_role(port, PD_ROLE_DFP); + /* + * USB PD Rev 3.0 Ver 2.0 6.8.3.2: "A Hard Reset Shall cause + * all Active Modes to be exited by both Port Partners and any + * Cable Plugs" + */ + tc_set_modes_exit(port); + tc[port].ps_reset_state = PS_STATE1; pd_timer_enable(port, TC_TIMER_TIMEOUT, PD_T_SRC_RECOVER); return false; @@ -1349,6 +1358,13 @@ static bool tc_perform_snk_hard_reset(int port) tc_set_data_role(port, PD_ROLE_UFP); /* + * USB PD Rev 3.0 Ver 2.0 6.8.3.2: "A Hard Reset Shall cause + * all Active Modes to be exited by both Port Partners and any + * Cable Plugs" + */ + tc_set_modes_exit(port); + + /* * When VCONN is supported, the Hard Reset Shall cause * the Port with the Rd resistor asserted to turn off * VCONN. @@ -1574,9 +1590,9 @@ void tc_state_init(int port) } /* - * If this is non-EFS2 device, battery is not present and EC RO doesn't - * keep power-on reset flag after reset caused by H1, then don't apply - * CC open because it will cause brown out. + * If this is non-EFS2 device, battery is not present or at some minimum + * voltage and EC RO doesn't keep power-on reset flag after reset caused + * by H1, then don't apply CC open because it will cause brown out. * * Please note that we are checking if CONFIG_BOARD_RESET_AFTER_POWER_ON * is defined now, but actually we need to know if it was enabled in @@ -1585,7 +1601,7 @@ void tc_state_init(int port) */ if (!IS_ENABLED(CONFIG_BOARD_RESET_AFTER_POWER_ON) && !IS_ENABLED(CONFIG_VBOOT_EFS2) && IS_ENABLED(CONFIG_BATTERY) && - (battery_is_present() == BP_NO)) { + !pd_is_battery_capable()) { first_state = TC_UNATTACHED_SNK; } @@ -1745,8 +1761,14 @@ void tc_event_check(int port, int evt) } } - if (evt & PD_EVENT_UPDATE_DUAL_ROLE) + if (evt & PD_EVENT_UPDATE_DUAL_ROLE) { + /* If TCPC is idle, start the wake process */ + if (IS_ENABLED(CONFIG_USB_PD_TCPC_LOW_POWER) && + get_state_tc(port) == TC_LOW_POWER_MODE) + tcpm_wake_low_power_mode(port); + pd_update_dual_role_config(port); + } } /* @@ -1893,11 +1915,14 @@ __maybe_unused static void handle_new_power_state(int port) /* * If the sink port was sourcing Vconn, and can no longer, request a - * hard reset on this port to restore Vconn to the source. + * hard reset on this port to restore Vconn to the source. If we do not + * have sufficient battery to withstand Vbus loss, then continue with + * the inconsistent Vconn state in order to keep the board powered. */ if (IS_ENABLED(CONFIG_USB_PE_SM)) { if (tc_is_vconn_src(port) && tc_is_attached_snk(port) && - !pd_check_vconn_swap(port)) + !pd_check_vconn_swap(port) && + pd_is_battery_capable()) pd_dpm_request(port, DPM_REQUEST_HARD_RESET_SEND); } |