/* Copyright 2020 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ /* * USB4 mode support * Refer USB Type-C Cable and Connector Specification Release 2.0 Section 5 and * USB Power Delivery Specification Revision 3.0, Version 2.0 Section 6.4.8 */ #include "compile_time_macros.h" #include "console.h" #include "tcpm/tcpm.h" #include "typec_control.h" #include "usb_common.h" #include "usb_mode.h" #include "usb_mux.h" #include "usb_pd.h" #include "usb_pd_dpm_sm.h" #include "usb_pd_tcpm.h" #include "usb_pe_sm.h" #include "usb_tbt_alt_mode.h" #include "usbc_ppc.h" #include #include #ifdef CONFIG_COMMON_RUNTIME #define CPRINTF(format, args...) cprintf(CC_USBPD, format, ##args) #define CPRINTS(format, args...) cprints(CC_USBPD, format, ##args) #else #define CPRINTF(format, args...) #define CPRINTS(format, args...) #endif enum usb4_mode_status { USB4_MODE_FAILURE, USB4_MODE_SUCCESS, }; enum usb4_states { USB4_START, USB4_ENTER_SOP, USB4_ENTER_SOP_PRIME, USB4_ENTER_SOP_PRIME_PRIME, USB4_ACTIVE, USB4_INACTIVE, USB4_STATE_COUNT, }; /* * USB4 PD flow: * * Cable type * | * |-------- Passive ---|---- Active -----| * | | * USB Highest Speed Structured VDM version * | (cable revision)-- <2.0---->| * --------|--------|------| | | * | | | | >=2.0 | * >=Gen3 Gen2 Gen1 USB2.0 | | * | | | | VDO version--- <1.3 ---> Modal op? -- N --| * Enter USB | | | (B21:23 of | | * SOP with | | | Discover ID SOP'- y | * Gen3 cable | | Skip Active cable VDO1) | | * speed | | USB4 | TBT SVID? -- N --| * | | mode >=1.3 | | * Is modal op? | entry | y | * | | Cable USB4 - N | | * y | support? | Gen4 cable? - N - Skip * | | | Skip USB4 | USB4 * Is TBT SVID? -N- Enter | mode entry | mode * | USB4 SOP | | entry * y with Gen2 y | * | cable speed | | * | | | * Is Discover mode | | * SOP' B25? - N - Enter Enter USB4 mode | * | USB4 SOP (SOP, SOP', SOP'') | * | with speed | * y from TBT mode | * | SOP' VDO | * | |<-- NAK -- Enter mode TBT SOP'<---| * |---->Enter TBT SOP'-------NAK------>| | | | * | | | | ACK | * | ACK | | | | * | | | |<-- NAK -- Enter mode TBT SOP'' | * | Enter USB4 SOP | | | | * | with speed from Exit TBT mode SOP ACK | * | TBT mode SOP' VDO | | | | * | ACK/NAK Enter USB4 SOP | * | | | with speed from | * | Exit TBT mode SOP'' TBT mode SOP' VDO | * | | | | * | ACK/NAK | * | | | | * | Exit TBT mode SOP' | * | | | | * | ACK/NAK | * | | | | * |---- N ----Retry done? -------------| |--------Retry done? ---- N -------| * | | * y y * | | * Skip USB4 mode entry Skip USB4 mode entry */ static enum usb4_states usb4_state[CONFIG_USB_PD_PORT_MAX_COUNT]; static void usb4_debug_prints(int port, enum usb4_mode_status usb4_status) { CPRINTS("C%d: USB4: State:%d Status:%d", port, usb4_state[port], usb4_status); } bool enter_usb_entry_is_done(int port) { return usb4_state[port] == USB4_ACTIVE || usb4_state[port] == USB4_INACTIVE; } void usb4_exit_mode_request(int port) { usb4_state[port] = USB4_START; usb_mux_set_safe_mode_exit(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) { usb4_state[port] = USB4_START; } void enter_usb_failed(int port) { /* * Since Enter USB sets the mux state to SAFE mode, fall back * to USB mode on receiving a NAK. */ usb_mux_set(port, USB_PD_MUX_USB_ENABLED, USB_SWITCH_CONNECT, polarity_rm_dts(pd_get_polarity(port))); usb4_debug_prints(port, USB4_MODE_FAILURE); usb4_state[port] = USB4_INACTIVE; } static bool enter_usb_response_valid(int port, enum tcpci_msg_type type) { /* * Check for an unexpected response. */ if (get_usb_pd_cable_type(port) == IDH_PTYPE_PCABLE && type != TCPCI_MSG_SOP) { enter_usb_failed(port); return false; } return true; } bool enter_usb_port_partner_is_capable(int port) { const struct pd_discovery *disc = pd_get_am_discovery(port, TCPCI_MSG_SOP); if (usb4_state[port] == USB4_INACTIVE) return false; if (!PD_PRODUCT_IS_USB4(disc->identity.product_t1.raw_value)) return false; return true; } bool enter_usb_cable_is_capable(int port) { if (get_usb_pd_cable_type(port) == IDH_PTYPE_PCABLE) { if (get_usb4_cable_speed(port) < USB_R30_SS_U32_U40_GEN1) return false; } else if (get_usb_pd_cable_type(port) == IDH_PTYPE_ACABLE) { const struct pd_discovery *disc_sop_prime = pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME); if (pd_get_vdo_ver(port, TCPCI_MSG_SOP_PRIME) >= VDM_VER20 && disc_sop_prime->identity.product_t1.a_rev30.vdo_ver >= VDO_VERSION_1_3) { union active_cable_vdo2_rev30 a2_rev30 = disc_sop_prime->identity.product_t2.a2_rev30; /* * For VDM version >= 2.0 and VD0 version is >= 1.3, * do not enter USB4 mode if the cable isn't USB4 * capable. */ if (a2_rev30.usb_40_support == USB4_NOT_SUPPORTED) return false; /* * For VDM version < 2.0 or VDO version < 1.3, do not * enter USB4 mode if the cable - doesn't support modal * operation or doesn't support Intel SVID or doesn't * have rounded support. */ } else { const struct pd_discovery *disc = pd_get_am_discovery(port, TCPCI_MSG_SOP); union tbt_mode_resp_cable cable_mode_resp = { .raw_value = pd_get_tbt_mode_vdo( port, TCPCI_MSG_SOP_PRIME) }; if (!disc->identity.idh.modal_support || !pd_is_mode_discovered_for_svid( port, TCPCI_MSG_SOP_PRIME, USB_VID_INTEL) || cable_mode_resp.tbt_rounded != TBT_GEN3_GEN4_ROUNDED_NON_ROUNDED) return false; } } else { /* Not Emark cable */ return false; } return true; } void enter_usb_accepted(int port, enum tcpci_msg_type type) { const struct pd_discovery *disc; if (!enter_usb_response_valid(port, type)) return; switch (usb4_state[port]) { case USB4_ENTER_SOP_PRIME: disc = pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME); if (disc->identity.product_t1.a_rev20.sop_p_p) usb4_state[port] = USB4_ENTER_SOP_PRIME_PRIME; else usb4_state[port] = USB4_ENTER_SOP; break; case USB4_ENTER_SOP_PRIME_PRIME: usb4_state[port] = USB4_ENTER_SOP; break; case USB4_ENTER_SOP: /* Connect the SBU and USB lines to the connector */ typec_set_sbu(port, true); usb4_state[port] = USB4_ACTIVE; /* Set usb mux to USB4 mode */ usb_mux_set(port, USB_PD_MUX_USB4_ENABLED, USB_SWITCH_CONNECT, polarity_rm_dts(pd_get_polarity(port))); usb4_debug_prints(port, USB4_MODE_SUCCESS); break; case USB4_ACTIVE: break; default: enter_usb_failed(port); } } void enter_usb_rejected(int port, enum tcpci_msg_type type) { if (!enter_usb_response_valid(port, type) || usb4_state[port] == USB4_ACTIVE) return; enter_usb_failed(port); } uint32_t enter_usb_setup_next_msg(int port, enum tcpci_msg_type *type) { const struct pd_discovery *disc_sop_prime; switch (usb4_state[port]) { case USB4_START: disc_sop_prime = pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME); /* * Ref: Tiger Lake Platform PD Controller Interface Requirements * for Integrated USBC, section A.2.2: USB4 as DFP. * Enter safe mode before sending Enter USB SOP/SOP'/SOP'' * TODO (b/156749387): Remove once data reset feature is in * place. */ usb_mux_set_safe_mode(port); if (pd_get_vdo_ver(port, TCPCI_MSG_SOP_PRIME) < VDM_VER20 || disc_sop_prime->identity.product_t1.a_rev30.vdo_ver < VDO_VERSION_1_3 || get_usb_pd_cable_type(port) == IDH_PTYPE_PCABLE) { usb4_state[port] = USB4_ENTER_SOP; } else { usb4_state[port] = USB4_ENTER_SOP_PRIME; *type = TCPCI_MSG_SOP_PRIME; } break; case USB4_ENTER_SOP_PRIME: *type = TCPCI_MSG_SOP_PRIME; break; case USB4_ENTER_SOP_PRIME_PRIME: *type = TCPCI_MSG_SOP_PRIME_PRIME; break; case USB4_ENTER_SOP: *type = TCPCI_MSG_SOP; break; case USB4_ACTIVE: return -1; default: return 0; } return get_enter_usb_msg_payload(port); } /* * For Cable rev 3.0: USB4 cable speed is set according to speed supported by * the port and the response received from the cable, whichever is least. * * For Cable rev 2.0: If get_tbt_cable_speed() is less than * TBT_SS_U31_GEN1, return USB_R30_SS_U2_ONLY speed since the board * doesn't support superspeed else the USB4 cable speed is set according to * the cable response. */ enum usb_rev30_ss get_usb4_cable_speed(int port) { enum tbt_compat_cable_speed tbt_speed = get_tbt_cable_speed(port); enum usb_rev30_ss max_usb4_speed; if (tbt_speed < TBT_SS_U31_GEN1) return USB_R30_SS_U2_ONLY; /* * Converting Thunderbolt-Compatible board speed to equivalent USB4 * speed. */ max_usb4_speed = tbt_speed == TBT_SS_TBT_GEN3 ? USB_R30_SS_U40_GEN3 : USB_R30_SS_U32_U40_GEN2; if ((get_usb_pd_cable_type(port) == IDH_PTYPE_ACABLE) && pd_get_rev(port, TCPCI_MSG_SOP_PRIME) == PD_REV30) { const struct pd_discovery *disc = pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME); union active_cable_vdo1_rev30 a_rev30 = disc->identity.product_t1.a_rev30; if (a_rev30.vdo_ver >= VDO_VERSION_1_3) { return max_usb4_speed < a_rev30.ss ? max_usb4_speed : a_rev30.ss; } } return max_usb4_speed; } uint32_t get_enter_usb_msg_payload(int port) { /* * Ref: USB Power Delivery Specification Revision 3.0, Version 2.0 * Table 6-47 Enter_USB Data Object */ union enter_usb_data_obj eudo; const struct pd_discovery *disc; union tbt_mode_resp_cable cable_mode_resp; if (!IS_ENABLED(CONFIG_USB_PD_USB4)) return 0; disc = pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME); eudo.mode = USB_PD_40; eudo.usb4_drd_cap = IS_ENABLED(CONFIG_USB_PD_USB4_DRD); eudo.usb3_drd_cap = IS_ENABLED(CONFIG_USB_PD_USB32_DRD); eudo.cable_speed = get_usb4_cable_speed(port); if (disc->identity.idh.product_type == IDH_PTYPE_ACABLE) { if (pd_get_rev(port, TCPCI_MSG_SOP_PRIME) == PD_REV30) { enum retimer_active_element active_element = disc->identity.product_t2.a2_rev30.active_elem; eudo.cable_type = active_element == ACTIVE_RETIMER ? CABLE_TYPE_ACTIVE_RETIMER : CABLE_TYPE_ACTIVE_REDRIVER; } else { cable_mode_resp.raw_value = pd_get_tbt_mode_vdo(port, TCPCI_MSG_SOP_PRIME); eudo.cable_type = cable_mode_resp.retimer_type == USB_RETIMER ? CABLE_TYPE_ACTIVE_RETIMER : CABLE_TYPE_ACTIVE_REDRIVER; } } else { cable_mode_resp.raw_value = pd_get_tbt_mode_vdo(port, TCPCI_MSG_SOP_PRIME); eudo.cable_type = cable_mode_resp.tbt_active_passive == TBT_CABLE_ACTIVE ? CABLE_TYPE_ACTIVE_REDRIVER : CABLE_TYPE_PASSIVE; } switch (disc->identity.product_t1.p_rev20.vbus_cur) { case USB_VBUS_CUR_3A: eudo.cable_current = USB4_CABLE_CURRENT_3A; break; case USB_VBUS_CUR_5A: eudo.cable_current = USB4_CABLE_CURRENT_5A; break; default: eudo.cable_current = USB4_CABLE_CURRENT_INVALID; break; } eudo.pcie_supported = IS_ENABLED(CONFIG_USB_PD_PCIE_TUNNELING); eudo.dp_supported = IS_ENABLED(CONFIG_USB_PD_ALT_MODE_DFP); eudo.tbt_supported = IS_ENABLED(CONFIG_USB_PD_TBT_COMPAT_MODE); eudo.host_present = 1; return eudo.raw_value; }