blob: 6ab5d54d00c985ce379978a07590b90d0009b653 [file] [log] [blame]
/* 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_prl_sm.h"
#include "usb_tbt_alt_mode.h"
#include "usbc_ppc.h"
#include <stdbool.h>
#include <stdint.h>
#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);
}
static enum usb_rev30_ss
tbt_to_usb4_speed(int port, enum tbt_compat_cable_speed tbt_speed)
{
const struct pd_discovery *disc =
pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME);
if (pd_get_rev(port, TCPCI_MSG_SOP_PRIME) == PD_REV30) {
if (tbt_speed == TBT_SS_TBT_GEN3)
return disc->identity.product_t1.p_rev30.ss;
else
return USB_R30_SS_U32_U40_GEN2;
} else {
if (tbt_speed == TBT_SS_TBT_GEN3)
return USB_R30_SS_U40_GEN3;
return USB_R30_SS_U32_U40_GEN2;
}
}
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 (prl_get_rev(port, TCPCI_MSG_SOP) < PD_REV30)
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) >= SVDM_VER_2_0 &&
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''
*/
usb_mux_set_safe_mode(port);
if (pd_get_vdo_ver(port, TCPCI_MSG_SOP_PRIME) < SVDM_VER_2_0 ||
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_to_usb4_speed(port, tbt_speed);
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;
}
OSZAR »