/* Copyright 2020 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * * Alternate Mode Downstream Facing Port (DFP) USB-PD module. */ #include "chipset.h" #include "console.h" #include "task.h" #include "task_id.h" #include "timer.h" #include "usb_common.h" #include "usb_charge.h" #include "usb_dp_alt_mode.h" #include "usb_mux.h" #include "usb_pd.h" #include "usb_pd_tcpm.h" #include "usb_tbt_alt_mode.h" #include "usbc_ppc.h" #include "util.h" #ifdef CONFIG_COMMON_RUNTIME #define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) #define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) #else #define CPRINTS(format, args...) #define CPRINTF(format, args...) #endif #ifndef PORT_TO_HPD #define PORT_TO_HPD(port) ((port) ? GPIO_USB_C1_DP_HPD : GPIO_USB_C0_DP_HPD) #endif /* PORT_TO_HPD */ /* Tracker for which task is waiting on sysjump prep to finish */ static volatile task_id_t sysjump_task_waiting = TASK_ID_INVALID; /* * timestamp of the next possible toggle to ensure the 2-ms spacing * between IRQ_HPD. Since this is used in overridable functions, this * has to be global. */ uint64_t svdm_hpd_deadline[CONFIG_USB_PD_PORT_MAX_COUNT]; int dp_flags[CONFIG_USB_PD_PORT_MAX_COUNT]; uint32_t dp_status[CONFIG_USB_PD_PORT_MAX_COUNT]; /* Console command multi-function preference set for a PD port. */ __maybe_unused bool dp_port_mf_allow[CONFIG_USB_PD_PORT_MAX_COUNT] = { [0 ... CONFIG_USB_PD_PORT_MAX_COUNT - 1] = true}; __overridable const struct svdm_response svdm_rsp = { .identity = NULL, .svids = NULL, .modes = NULL, }; static int pd_get_mode_idx(int port, enum tcpci_msg_type type, uint16_t svid) { int amode_idx; struct partner_active_modes *active = pd_get_partner_active_modes(port, type); for (amode_idx = 0; amode_idx < PD_AMODE_COUNT; amode_idx++) { if (active->amodes[amode_idx].fx && (active->amodes[amode_idx].fx->svid == svid)) return amode_idx; } return -1; } static int pd_allocate_mode(int port, enum tcpci_msg_type type, uint16_t svid) { int i, j; struct svdm_amode_data *modep; int mode_idx = pd_get_mode_idx(port, type, svid); const struct pd_discovery *disc = pd_get_am_discovery(port, type); struct partner_active_modes *active = pd_get_partner_active_modes(port, type); assert(active); if (mode_idx != -1) return mode_idx; /* There's no space to enter another mode */ if (active->amode_idx == PD_AMODE_COUNT) { CPRINTF("ERR:NO AMODE SPACE\n"); return -1; } /* Allocate ... if SVID == 0 enter default supported policy */ for (i = 0; i < supported_modes_cnt; i++) { for (j = 0; j < disc->svid_cnt; j++) { const struct svid_mode_data *svidp = &disc->svids[j]; /* * Looking for a match between supported_modes and * discovered SVIDs; must also match the passed-in SVID * if that was non-zero. Otherwise, go to the next * discovered SVID. * TODO(b/155890173): Support AP-directed mode entry * where the mode is unknown to the TCPM. */ if ((svidp->svid != supported_modes[i].svid) || (svid && (svidp->svid != svid))) continue; modep = &active->amodes[active->amode_idx]; modep->fx = &supported_modes[i]; modep->data = &disc->svids[j]; active->amode_idx++; return active->amode_idx - 1; } } return -1; } static int validate_mode_request(struct svdm_amode_data *modep, uint16_t svid, int opos) { if (!modep->fx) return 0; if (svid != modep->fx->svid) { CPRINTF("ERR:svid r:0x%04x != c:0x%04x\n", svid, modep->fx->svid); return 0; } if (opos != modep->opos) { CPRINTF("ERR:opos r:%d != c:%d\n", opos, modep->opos); return 0; } return 1; } void pd_prepare_sysjump(void) { int i; /* Exit modes before sysjump so we can cleanly enter again later */ for (i = 0; i < board_get_usb_pd_port_count(); i++) { /* * If the port is not capable of Alternate mode no need to * send the event. */ if (!pd_alt_mode_capable(i)) continue; sysjump_task_waiting = task_get_current(); task_set_event(PD_PORT_TO_TASK_ID(i), PD_EVENT_SYSJUMP); task_wait_event_mask(TASK_EVENT_SYSJUMP_READY, -1); sysjump_task_waiting = TASK_ID_INVALID; } } /* * This algorithm defaults to choosing higher pin config over lower ones in * order to prefer multi-function if desired. * * NAME | SIGNALING | OUTPUT TYPE | MULTI-FUNCTION | PIN CONFIG * ------------------------------------------------------------- * A | USB G2 | ? | no | 00_0001 * B | USB G2 | ? | yes | 00_0010 * C | DP | CONVERTED | no | 00_0100 * D | PD | CONVERTED | yes | 00_1000 * E | DP | DP | no | 01_0000 * F | PD | DP | yes | 10_0000 * * if UFP has NOT asserted multi-function preferred code masks away B/D/F * leaving only A/C/E. For single-output dongles that should leave only one * possible pin config depending on whether its a converter DP->(VGA|HDMI) or DP * output. If UFP is a USB-C receptacle it may assert C/D/E/F. The DFP USB-C * receptacle must always choose C/D in those cases. */ int pd_dfp_dp_get_pin_mode(int port, uint32_t status) { struct svdm_amode_data *modep = pd_get_amode_data(port, TCPCI_MSG_SOP, USB_SID_DISPLAYPORT); uint32_t mode_caps; uint32_t pin_caps; int mf_pref; /* * Default dp_port_mf_allow is true, we allow mf operation * if UFP_D supports it. */ if (IS_ENABLED(CONFIG_CMD_MFALLOW)) mf_pref = PD_VDO_DPSTS_MF_PREF(dp_status[port]) && dp_port_mf_allow[port]; else mf_pref = PD_VDO_DPSTS_MF_PREF(dp_status[port]); if (!modep) return 0; mode_caps = modep->data->mode_vdo[modep->opos - 1]; /* TODO(crosbug.com/p/39656) revisit with DFP that can be a sink */ pin_caps = PD_DP_PIN_CAPS(mode_caps); /* if don't want multi-function then ignore those pin configs */ if (!mf_pref) pin_caps &= ~MODE_DP_PIN_MF_MASK; /* TODO(crosbug.com/p/39656) revisit if DFP drives USB Gen 2 signals */ pin_caps &= ~MODE_DP_PIN_BR2_MASK; /* if C/D present they have precedence over E/F for USB-C->USB-C */ if (pin_caps & (MODE_DP_PIN_C | MODE_DP_PIN_D)) pin_caps &= ~(MODE_DP_PIN_E | MODE_DP_PIN_F); /* get_next_bit returns undefined for zero */ if (!pin_caps) return 0; return 1 << get_next_bit(&pin_caps); } struct svdm_amode_data *pd_get_amode_data(int port, enum tcpci_msg_type type, uint16_t svid) { int idx = pd_get_mode_idx(port, type, svid); struct partner_active_modes *active = pd_get_partner_active_modes(port, type); assert(active); return (idx == -1) ? NULL : &active->amodes[idx]; } /* * Enter default mode ( payload[0] == 0 ) or attempt to enter mode via svid & * opos */ uint32_t pd_dfp_enter_mode(int port, enum tcpci_msg_type type, uint16_t svid, int opos) { int mode_idx = pd_allocate_mode(port, type, svid); struct svdm_amode_data *modep; uint32_t mode_caps; if (mode_idx == -1) return 0; modep = &pd_get_partner_active_modes(port, type)->amodes[mode_idx]; if (!opos) { /* choose the lowest as default */ modep->opos = 1; } else if (opos <= modep->data->mode_cnt) { modep->opos = opos; } else { CPRINTS("C%d: Invalid opos %d for SVID %x", port, opos, svid); return 0; } mode_caps = modep->data->mode_vdo[modep->opos - 1]; if (modep->fx->enter(port, mode_caps) == -1) return 0; /* * Strictly speaking, this should only happen when the request * has been ACKed. * For TCPMV1, still set modal flag pre-emptively. For TCPMv2, the modal * flag is set when the ENTER command is ACK'd for each alt mode that is * supported. */ if (IS_ENABLED(CONFIG_USB_PD_TCPMV1)) pd_set_dfp_enter_mode_flag(port, true); /* SVDM to send to UFP for mode entry */ return VDO(modep->fx->svid, 1, CMD_ENTER_MODE | VDO_OPOS(modep->opos)); } /* TODO(b/170372521) : Incorporate exit mode specific changes to DPM SM */ int pd_dfp_exit_mode(int port, enum tcpci_msg_type type, uint16_t svid, int opos) { struct svdm_amode_data *modep; struct partner_active_modes *active = pd_get_partner_active_modes(port, type); int idx; /* * Empty svid signals we should reset DFP VDM state by exiting all * entered modes then clearing state. This occurs when we've * disconnected or for hard reset. */ if (!svid) { for (idx = 0; idx < PD_AMODE_COUNT; idx++) if (active->amodes[idx].fx) active->amodes[idx].fx->exit(port); pd_dfp_mode_init(port); return 0; } /* * TODO(crosbug.com/p/33946) : below needs revisited to allow multiple * mode exit. Additionally it should honor OPOS == 7 as DFP's request * to exit all modes. We currently don't have any UFPs that support * multiple modes on one SVID. */ modep = pd_get_amode_data(port, type, svid); if (!modep || !validate_mode_request(modep, svid, opos)) return 0; /* call DFPs exit function */ modep->fx->exit(port); pd_set_dfp_enter_mode_flag(port, false); /* exit the mode */ modep->opos = 0; return 1; } /* * Check if the SVID has been recorded previously. Some peripherals provide * duplicated SVID. */ static bool is_svid_duplicated(const struct pd_discovery *disc, uint16_t svid) { int i; for (i = 0; i < disc->svid_cnt; ++i) if (disc->svids[i].svid == svid) { CPRINTF("ERR:SVIDDUP\n"); return true; } return false; } void dfp_consume_attention(int port, uint32_t *payload) { uint16_t svid = PD_VDO_VID(payload[0]); int opos = PD_VDO_OPOS(payload[0]); struct svdm_amode_data *modep = pd_get_amode_data(port, TCPCI_MSG_SOP, svid); if (!modep || !validate_mode_request(modep, svid, opos)) return; if (modep->fx->attention) modep->fx->attention(port, payload); } void dfp_consume_identity(int port, enum tcpci_msg_type type, int cnt, uint32_t *payload) { int ptype; struct pd_discovery *disc; size_t identity_size; if (type == TCPCI_MSG_SOP_PRIME && !IS_ENABLED(CONFIG_USB_PD_DECODE_SOP)) { CPRINTF("ERR:Unexpected cable response\n"); return; } ptype = PD_IDH_PTYPE(payload[VDO_I(IDH)]); disc = pd_get_am_discovery_and_notify_access(port, type); identity_size = MIN(sizeof(union disc_ident_ack), (cnt - 1) * sizeof(uint32_t)); /* Note: only store VDOs, not the VDM header */ memcpy(disc->identity.raw_value, payload + 1, identity_size); disc->identity_cnt = identity_size / sizeof(uint32_t); switch (ptype) { case IDH_PTYPE_AMA: /* Leave vbus ON if the following macro is false */ if (IS_ENABLED(CONFIG_USB_PD_DUAL_ROLE) && IS_ENABLED(CONFIG_USBC_VCONN_SWAP)) { /* Adapter is requesting vconn, try to supply it */ if (PD_VDO_AMA_VCONN_REQ(payload[VDO_I(AMA)])) pd_try_vconn_src(port); /* Only disable vbus if vconn was requested */ if (PD_VDO_AMA_VCONN_REQ(payload[VDO_I(AMA)]) && !PD_VDO_AMA_VBUS_REQ(payload[VDO_I(AMA)])) pd_power_supply_reset(port); } break; default: break; } pd_set_identity_discovery(port, type, PD_DISC_COMPLETE); } void dfp_consume_svids(int port, enum tcpci_msg_type type, int cnt, uint32_t *payload) { int i; uint32_t *ptr = payload + 1; int vdo = 1; uint16_t svid0, svid1; struct pd_discovery *disc = pd_get_am_discovery_and_notify_access(port, type); for (i = disc->svid_cnt; i < disc->svid_cnt + 12; i += 2) { if (i >= SVID_DISCOVERY_MAX) { CPRINTF("ERR:SVIDCNT\n"); break; } /* * Verify we're still within the valid packet (count will be one * for the VDM header + xVDOs) */ if (vdo >= cnt) break; svid0 = PD_VDO_SVID_SVID0(*ptr); if (!svid0) break; if (!is_svid_duplicated(disc, svid0)) disc->svids[disc->svid_cnt++].svid = svid0; svid1 = PD_VDO_SVID_SVID1(*ptr); if (!svid1) break; if (!is_svid_duplicated(disc, svid1)) disc->svids[disc->svid_cnt++].svid = svid1; ptr++; vdo++; } /* TODO(tbroch) need to re-issue discover svids if > 12 */ if (i && ((i % 12) == 0)) CPRINTF("ERR:SVID+12\n"); pd_set_svids_discovery(port, type, PD_DISC_COMPLETE); } void dfp_consume_modes(int port, enum tcpci_msg_type type, int cnt, uint32_t *payload) { int svid_idx; struct svid_mode_data *mode_discovery = NULL; struct pd_discovery *disc = pd_get_am_discovery_and_notify_access(port, type); uint16_t response_svid = (uint16_t) PD_VDO_VID(payload[0]); for (svid_idx = 0; svid_idx < disc->svid_cnt; ++svid_idx) { uint16_t svid = disc->svids[svid_idx].svid; if (svid == response_svid) { mode_discovery = &disc->svids[svid_idx]; break; } } if (!mode_discovery) { const struct svid_mode_data *requested_mode_data = pd_get_next_mode(port, type); CPRINTF("C%d: Mode response for undiscovered SVID %x, but TCPM " "requested SVID %x\n", port, response_svid, requested_mode_data->svid); /* * Although SVIDs discovery seemed like it succeeded before, the * partner is now responding with undiscovered SVIDs. Discovery * cannot reasonably continue under these circumstances. */ pd_set_modes_discovery(port, type, requested_mode_data->svid, PD_DISC_FAIL); return; } mode_discovery->mode_cnt = cnt - 1; if (mode_discovery->mode_cnt < 1) { CPRINTF("ERR:NOMODE\n"); pd_set_modes_discovery(port, type, mode_discovery->svid, PD_DISC_FAIL); return; } memcpy(mode_discovery->mode_vdo, &payload[1], sizeof(uint32_t) * mode_discovery->mode_cnt); disc->svid_idx++; pd_set_modes_discovery(port, type, mode_discovery->svid, PD_DISC_COMPLETE); } int pd_alt_mode(int port, enum tcpci_msg_type type, uint16_t svid) { struct svdm_amode_data *modep = pd_get_amode_data(port, type, svid); return (modep) ? modep->opos : -1; } void pd_set_identity_discovery(int port, enum tcpci_msg_type type, enum pd_discovery_state disc) { struct pd_discovery *pd = pd_get_am_discovery_and_notify_access(port, type); pd->identity_discovery = disc; } enum pd_discovery_state pd_get_identity_discovery(int port, enum tcpci_msg_type type) { const struct pd_discovery *disc = pd_get_am_discovery(port, type); return disc->identity_discovery; } const union disc_ident_ack *pd_get_identity_response(int port, enum tcpci_msg_type type) { if (type >= DISCOVERY_TYPE_COUNT) return NULL; return &pd_get_am_discovery(port, type)->identity; } uint16_t pd_get_identity_vid(int port) { const union disc_ident_ack *resp = pd_get_identity_response(port, TCPCI_MSG_SOP); return resp->idh.usb_vendor_id; } uint16_t pd_get_identity_pid(int port) { const union disc_ident_ack *resp = pd_get_identity_response(port, TCPCI_MSG_SOP); return resp->product.product_id; } uint8_t pd_get_product_type(int port) { const union disc_ident_ack *resp = pd_get_identity_response(port, TCPCI_MSG_SOP); return resp->idh.product_type; } void pd_set_svids_discovery(int port, enum tcpci_msg_type type, enum pd_discovery_state disc) { struct pd_discovery *pd = pd_get_am_discovery_and_notify_access(port, type); pd->svids_discovery = disc; } enum pd_discovery_state pd_get_svids_discovery(int port, enum tcpci_msg_type type) { const struct pd_discovery *disc = pd_get_am_discovery(port, type); return disc->svids_discovery; } int pd_get_svid_count(int port, enum tcpci_msg_type type) { const struct pd_discovery *disc = pd_get_am_discovery(port, type); return disc->svid_cnt; } uint16_t pd_get_svid(int port, uint16_t svid_idx, enum tcpci_msg_type type) { const struct pd_discovery *disc = pd_get_am_discovery(port, type); return disc->svids[svid_idx].svid; } void pd_set_modes_discovery(int port, enum tcpci_msg_type type, uint16_t svid, enum pd_discovery_state disc) { struct pd_discovery *pd = pd_get_am_discovery_and_notify_access(port, type); int svid_idx; for (svid_idx = 0; svid_idx < pd->svid_cnt; ++svid_idx) { struct svid_mode_data *mode_data = &pd->svids[svid_idx]; if (mode_data->svid != svid) continue; mode_data->discovery = disc; return; } } enum pd_discovery_state pd_get_modes_discovery(int port, enum tcpci_msg_type type) { const struct svid_mode_data *mode_data = pd_get_next_mode(port, type); /* * If there are no SVIDs for which to discover modes, mode discovery is * trivially complete. */ if (!mode_data) return PD_DISC_COMPLETE; return mode_data->discovery; } int pd_get_mode_vdo_for_svid(int port, enum tcpci_msg_type type, uint16_t svid, uint32_t *vdo_out) { int idx; const struct pd_discovery *disc; if (type >= DISCOVERY_TYPE_COUNT) return 0; disc = pd_get_am_discovery(port, type); for (idx = 0; idx < disc->svid_cnt; ++idx) { if (pd_get_svid(port, idx, type) == svid) { memcpy(vdo_out, disc->svids[idx].mode_vdo, sizeof(uint32_t) * disc->svids[idx].mode_cnt); return disc->svids[idx].mode_cnt; } } return 0; } const struct svid_mode_data *pd_get_next_mode(int port, enum tcpci_msg_type type) { const struct pd_discovery *disc = pd_get_am_discovery(port, type); const struct svid_mode_data *failed_mode_data = NULL; bool svid_good_discovery = false; int svid_idx; /* Walk through all of the discovery mode entries */ for (svid_idx = 0; svid_idx < disc->svid_cnt; ++svid_idx) { const struct svid_mode_data *mode_data = &disc->svids[svid_idx]; /* Discovery is needed, so send this one back now */ if (mode_data->discovery == PD_DISC_NEEDED) return mode_data; /* Discovery already succeeded, save that it was seen */ if (mode_data->discovery == PD_DISC_COMPLETE) svid_good_discovery = true; /* Discovery already failed, save first failure */ else if (!failed_mode_data) failed_mode_data = mode_data; } /* If no good entries were located, then return last failed */ if (!svid_good_discovery) return failed_mode_data; /* * Mode discovery has been attempted for every discovered SVID (if * any exist) */ return NULL; } const uint32_t *pd_get_mode_vdo(int port, uint16_t svid_idx, enum tcpci_msg_type type) { const struct pd_discovery *disc = pd_get_am_discovery(port, type); return disc->svids[svid_idx].mode_vdo; } bool pd_is_mode_discovered_for_svid(int port, enum tcpci_msg_type type, uint16_t svid) { const struct pd_discovery *disc = pd_get_am_discovery(port, type); const struct svid_mode_data *mode_data; for (mode_data = disc->svids; mode_data < disc->svids + disc->svid_cnt; ++mode_data) { if (mode_data->svid == svid && mode_data->discovery == PD_DISC_COMPLETE) return true; } return false; } void notify_sysjump_ready(void) { /* * If event was set from pd_prepare_sysjump, wake the * task waiting on us to complete. */ if (sysjump_task_waiting != TASK_ID_INVALID) task_set_event(sysjump_task_waiting, TASK_EVENT_SYSJUMP_READY); } static inline bool is_pd_rev3(int port, enum tcpci_msg_type type) { return pd_get_rev(port, type) == PD_REV30; } /* * ############################################################################ * * (Charge Through) Vconn Powered Device functions * * ############################################################################ */ bool is_vpd_ct_supported(int port) { const struct pd_discovery *disc = pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME); union vpd_vdo vpd = disc->identity.product_t1.vpd; return vpd.ct_support; } uint8_t get_vpd_ct_gnd_impedance(int port) { const struct pd_discovery *disc = pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME); union vpd_vdo vpd = disc->identity.product_t1.vpd; return vpd.gnd_impedance; } uint8_t get_vpd_ct_vbus_impedance(int port) { const struct pd_discovery *disc = pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME); union vpd_vdo vpd = disc->identity.product_t1.vpd; return vpd.vbus_impedance; } uint8_t get_vpd_ct_current_support(int port) { const struct pd_discovery *disc = pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME); union vpd_vdo vpd = disc->identity.product_t1.vpd; return vpd.ct_current_support; } uint8_t get_vpd_ct_max_vbus_voltage(int port) { const struct pd_discovery *disc = pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME); union vpd_vdo vpd = disc->identity.product_t1.vpd; return vpd.max_vbus_voltage; } uint8_t get_vpd_ct_vdo_version(int port) { const struct pd_discovery *disc = pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME); union vpd_vdo vpd = disc->identity.product_t1.vpd; return vpd.vdo_version; } uint8_t get_vpd_ct_firmware_verion(int port) { const struct pd_discovery *disc = pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME); union vpd_vdo vpd = disc->identity.product_t1.vpd; return vpd.firmware_version; } uint8_t get_vpd_ct_hw_version(int port) { const struct pd_discovery *disc = pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME); union vpd_vdo vpd = disc->identity.product_t1.vpd; return vpd.hw_version; } /* * ############################################################################ * * Cable communication functions * * ############################################################################ */ enum idh_ptype get_usb_pd_cable_type(int port) { const struct pd_discovery *disc = pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME); return disc->identity.idh.product_type; } bool is_usb2_cable_support(int port) { const struct pd_discovery *disc = pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME); return disc->identity.idh.product_type == IDH_PTYPE_PCABLE || pd_get_vdo_ver(port, TCPCI_MSG_SOP_PRIME) < VDM_VER20 || disc->identity.product_t2.a2_rev30.usb_20_support == USB2_SUPPORTED; } bool is_cable_speed_gen2_capable(int port) { const struct pd_discovery *disc = pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME); switch (pd_get_rev(port, TCPCI_MSG_SOP_PRIME)) { case PD_REV20: return disc->identity.product_t1.p_rev20.ss == USB_R20_SS_U31_GEN1_GEN2; case PD_REV30: return disc->identity.product_t1.p_rev30.ss == USB_R30_SS_U32_U40_GEN2 || disc->identity.product_t1.p_rev30.ss == USB_R30_SS_U40_GEN3; default: return false; } } bool is_active_cable_element_retimer(int port) { const struct pd_discovery *disc = pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME); /* Ref: USB PD Spec 2.0 Table 6-29 Active Cable VDO * Revision 2 Active cables do not have Active element support. */ return is_pd_rev3(port, TCPCI_MSG_SOP_PRIME) && disc->identity.idh.product_type == IDH_PTYPE_ACABLE && disc->identity.product_t2.a2_rev30.active_elem == ACTIVE_RETIMER; } /* * ############################################################################ * * Thunderbolt-Compatible functions * * ############################################################################ */ uint32_t pd_get_tbt_mode_vdo(int port, enum tcpci_msg_type type) { uint32_t tbt_mode_vdo[PDO_MODES]; return pd_get_mode_vdo_for_svid(port, type, USB_VID_INTEL, tbt_mode_vdo) ? tbt_mode_vdo[0] : 0; } /* TODO (b/148528713): Need to enable Thunderbolt-compatible mode on TCPMv2 */ void set_tbt_compat_mode_ready(int port) { if (IS_ENABLED(CONFIG_USBC_SS_MUX) && IS_ENABLED(CONFIG_USB_PD_TBT_COMPAT_MODE)) { /* Connect the SBU and USB lines to the connector. */ if (IS_ENABLED(CONFIG_USBC_PPC_SBU)) ppc_set_sbu(port, 1); /* Set usb mux to Thunderbolt-compatible mode */ usb_mux_set(port, USB_PD_MUX_TBT_COMPAT_ENABLED, USB_SWITCH_CONNECT, polarity_rm_dts(pd_get_polarity(port))); } } /* * Ref: USB Type-C Cable and Connector Specification * Figure F-1 TBT3 Discovery Flow */ static bool is_tbt_cable_superspeed(int port) { const struct pd_discovery *disc; if (!IS_ENABLED(CONFIG_USB_PD_TBT_COMPAT_MODE) || !IS_ENABLED(CONFIG_USB_PD_DECODE_SOP)) return false; disc = pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME); /* Product type is Active cable, hence don't check for speed */ if (disc->identity.idh.product_type == IDH_PTYPE_ACABLE) return true; if (disc->identity.idh.product_type != IDH_PTYPE_PCABLE) return false; if (IS_ENABLED(CONFIG_USB_PD_REV30) && is_pd_rev3(port, TCPCI_MSG_SOP_PRIME)) return disc->identity.product_t1.p_rev30.ss == USB_R30_SS_U32_U40_GEN1 || disc->identity.product_t1.p_rev30.ss == USB_R30_SS_U32_U40_GEN2 || disc->identity.product_t1.p_rev30.ss == USB_R30_SS_U40_GEN3; return disc->identity.product_t1.p_rev20.ss == USB_R20_SS_U31_GEN1 || disc->identity.product_t1.p_rev20.ss == USB_R20_SS_U31_GEN1_GEN2; } static enum tbt_compat_cable_speed usb_rev30_to_tbt_speed(enum usb_rev30_ss ss) { switch (ss) { case USB_R30_SS_U32_U40_GEN1: return TBT_SS_U31_GEN1; case USB_R30_SS_U32_U40_GEN2: return TBT_SS_U32_GEN1_GEN2; case USB_R30_SS_U40_GEN3: return TBT_SS_TBT_GEN3; default: return TBT_SS_U32_GEN1_GEN2; } } enum tbt_compat_cable_speed get_tbt_cable_speed(int port) { union tbt_mode_resp_cable cable_mode_resp; enum tbt_compat_cable_speed max_tbt_speed; enum tbt_compat_cable_speed cable_tbt_speed; if (!is_tbt_cable_superspeed(port)) return TBT_SS_RES_0; cable_mode_resp.raw_value = pd_get_tbt_mode_vdo(port, TCPCI_MSG_SOP_PRIME); max_tbt_speed = board_get_max_tbt_speed(port); /* * Ref: TBT4 PD Discovery Flow Application Notes Revision 0.9, Figure 2 * For passive cable, if cable doesn't support USB_VID_INTEL, enter * Thunderbolt alternate mode with speed from USB Highest Speed field of * the Passive Cable VDO * For active cable, if the cable doesn't support USB_VID_INTEL, do not * enter Thunderbolt alternate mode. */ if (!cable_mode_resp.raw_value) { const struct pd_discovery *disc; if (get_usb_pd_cable_type(port) == IDH_PTYPE_ACABLE) return TBT_SS_RES_0; disc = pd_get_am_discovery(port, TCPCI_MSG_SOP_PRIME); cable_tbt_speed = usb_rev30_to_tbt_speed(disc->identity.product_t1.p_rev30.ss); } else { cable_tbt_speed = cable_mode_resp.tbt_cable_speed; } return max_tbt_speed < cable_tbt_speed ? max_tbt_speed : cable_tbt_speed; } int enter_tbt_compat_mode(int port, enum tcpci_msg_type sop, uint32_t *payload) { union tbt_dev_mode_enter_cmd enter_dev_mode = { .raw_value = 0 }; union tbt_mode_resp_device dev_mode_resp; union tbt_mode_resp_cable cable_mode_resp; enum tcpci_msg_type enter_mode_sop = sop == TCPCI_MSG_SOP_PRIME_PRIME ? TCPCI_MSG_SOP_PRIME : sop; /* Table F-12 TBT3 Cable Enter Mode Command */ /* * The port doesn't query Discover SOP'' to the cable so, the port * doesn't have opos for SOP''. Hence, send Enter Mode SOP'' with same * opos and revision as SOP'. */ payload[0] = pd_dfp_enter_mode(port, enter_mode_sop, USB_VID_INTEL, 0) | VDO_CMDT(CMDT_INIT) | VDO_SVDM_VERS(pd_get_vdo_ver(port, enter_mode_sop)); /* * 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); /* For TBT3 Cable Enter Mode Command, number of Objects is 1 */ if ((sop == TCPCI_MSG_SOP_PRIME) || (sop == TCPCI_MSG_SOP_PRIME_PRIME)) return 1; dev_mode_resp.raw_value = pd_get_tbt_mode_vdo(port, TCPCI_MSG_SOP); cable_mode_resp.raw_value = pd_get_tbt_mode_vdo(port, TCPCI_MSG_SOP_PRIME); /* Table F-13 TBT3 Device Enter Mode Command */ enter_dev_mode.vendor_spec_b1 = dev_mode_resp.vendor_spec_b1; enter_dev_mode.vendor_spec_b0 = dev_mode_resp.vendor_spec_b0; enter_dev_mode.intel_spec_b0 = dev_mode_resp.intel_spec_b0; if (get_usb_pd_cable_type(port) == IDH_PTYPE_ACABLE || cable_mode_resp.tbt_active_passive == TBT_CABLE_ACTIVE) enter_dev_mode.cable = TBT_ENTER_ACTIVE_CABLE; enter_dev_mode.lsrx_comm = cable_mode_resp.lsrx_comm; enter_dev_mode.retimer_type = cable_mode_resp.retimer_type; enter_dev_mode.tbt_cable = cable_mode_resp.tbt_cable; enter_dev_mode.tbt_rounded = cable_mode_resp.tbt_rounded; enter_dev_mode.tbt_cable_speed = get_tbt_cable_speed(port); enter_dev_mode.tbt_alt_mode = TBT_ALTERNATE_MODE; payload[1] = enter_dev_mode.raw_value; /* For TBT3 Device Enter Mode Command, number of Objects are 2 */ return 2; } enum tbt_compat_rounded_support get_tbt_rounded_support(int port) { union tbt_mode_resp_cable cable_mode_resp = { .raw_value = pd_get_tbt_mode_vdo(port, TCPCI_MSG_SOP_PRIME) }; /* tbt_rounded_support is zero when uninitialized */ return cable_mode_resp.tbt_rounded; } __overridable enum tbt_compat_cable_speed board_get_max_tbt_speed(int port) { return TBT_SS_TBT_GEN3; } /* * ############################################################################ * * USB4 functions * * ############################################################################ */ /* * 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) && is_pd_rev3(port, TCPCI_MSG_SOP_PRIME)) { 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 (is_pd_rev3(port, TCPCI_MSG_SOP_PRIME)) { 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; } __overridable bool board_is_tbt_usb4_port(int port) { return true; } __overridable void svdm_safe_dp_mode(int port) { /* make DP interface safe until configure */ dp_flags[port] = 0; dp_status[port] = 0; usb_mux_set_safe_mode(port); } __overridable int svdm_enter_dp_mode(int port, uint32_t mode_caps) { /* * Don't enter the mode if the SoC is off. * * There's no need to enter the mode while the SoC is off; we'll * actually enter the mode on the chipset resume hook. Entering DP Alt * Mode twice will confuse some monitors and require and unplug/replug * to get them to work again. The DP Alt Mode on USB-C spec says that * if we don't need to maintain HPD connectivity info in a low power * mode, then we shall exit DP Alt Mode. (This is why we don't enter * when the SoC is off as opposed to suspend where adding a display * could cause a wake up.) When in S5->S3 transition state, we * should treat it as a SoC off state. */ #ifdef HAS_TASK_CHIPSET if (!chipset_in_state(CHIPSET_STATE_ANY_SUSPEND | CHIPSET_STATE_ON)) return -1; #endif /* * TCPMv2: Enable logging of CCD line state CCD_MODE_ODL. * DisplayPort Alternate mode requires that the SBU lines are used for * AUX communication. * However, in Chromebooks SBU signals are repurposed as USB2 signals * for CCD. This functionality is accomplished by override fets whose * state is controlled by CCD_MODE_ODL. * * This condition helps in debugging unexpected AUX timeout issues by * indicating the state of the CCD override fets. */ #ifdef GPIO_CCD_MODE_ODL if (!gpio_get_level(GPIO_CCD_MODE_ODL)) CPRINTS("WARNING: Tried to EnterMode DP with [CCD on AUX/SBU]"); #endif /* Only enter mode if device is DFP_D capable */ if (mode_caps & MODE_DP_SNK) { svdm_safe_dp_mode(port); if (IS_ENABLED(CONFIG_MKBP_EVENT) && chipset_in_state(CHIPSET_STATE_ANY_SUSPEND)) /* * Wake the system up since we're entering DP AltMode. */ pd_notify_dp_alt_mode_entry(port); return 0; } return -1; } __overridable int svdm_dp_status(int port, uint32_t *payload) { int opos = pd_alt_mode(port, TCPCI_MSG_SOP, USB_SID_DISPLAYPORT); payload[0] = VDO(USB_SID_DISPLAYPORT, 1, CMD_DP_STATUS | VDO_OPOS(opos)); payload[1] = VDO_DP_STATUS(0, /* HPD IRQ ... not applicable */ 0, /* HPD level ... not applicable */ 0, /* exit DP? ... no */ 0, /* usb mode? ... no */ 0, /* multi-function ... no */ (!!(dp_flags[port] & DP_FLAGS_DP_ON)), 0, /* power low? ... no */ (!!DP_FLAGS_DP_ON)); return 2; }; __overridable uint8_t get_dp_pin_mode(int port) { return pd_dfp_dp_get_pin_mode(port, dp_status[port]); } static mux_state_t svdm_dp_get_mux_mode(int port) { int pin_mode = get_dp_pin_mode(port); /* Default dp_port_mf_allow is true */ int mf_pref; if (IS_ENABLED(CONFIG_CMD_MFALLOW)) mf_pref = PD_VDO_DPSTS_MF_PREF(dp_status[port]) && dp_port_mf_allow[port]; else mf_pref = PD_VDO_DPSTS_MF_PREF(dp_status[port]); /* * Multi-function operation is only allowed if that pin config is * supported. */ if ((pin_mode & MODE_DP_PIN_MF_MASK) && mf_pref) return USB_PD_MUX_DOCK; else return USB_PD_MUX_DP_ENABLED; } __overridable int svdm_dp_config(int port, uint32_t *payload) { int opos = pd_alt_mode(port, TCPCI_MSG_SOP, USB_SID_DISPLAYPORT); uint8_t pin_mode = get_dp_pin_mode(port); mux_state_t mux_mode = svdm_dp_get_mux_mode(port); /* Default dp_port_mf_allow is true */ int mf_pref; if (IS_ENABLED(CONFIG_CMD_MFALLOW)) mf_pref = PD_VDO_DPSTS_MF_PREF(dp_status[port]) && dp_port_mf_allow[port]; else mf_pref = PD_VDO_DPSTS_MF_PREF(dp_status[port]); if (!pin_mode) return 0; CPRINTS("pin_mode: %x, mf: %d, mux: %d", pin_mode, mf_pref, mux_mode); /* * 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 (mux_mode == USB_PD_MUX_DP_ENABLED) usb_mux_set_safe_mode(port); payload[0] = VDO(USB_SID_DISPLAYPORT, 1, CMD_DP_CONFIG | VDO_OPOS(opos)); payload[1] = VDO_DP_CFG(pin_mode, /* pin mode */ 1, /* DPv1.3 signaling */ 2); /* UFP connected */ return 2; }; #if defined(CONFIG_USB_PD_DP_HPD_GPIO) && \ !defined(CONFIG_USB_PD_DP_HPD_GPIO_CUSTOM) void svdm_set_hpd_gpio(int port, int en) { gpio_set_level(PORT_TO_HPD(port), en); } int svdm_get_hpd_gpio(int port) { return gpio_get_level(PORT_TO_HPD(port)); } #endif __overridable void svdm_dp_post_config(int port) { mux_state_t mux_mode = svdm_dp_get_mux_mode(port); /* Connect the SBU and USB lines to the connector. */ if (IS_ENABLED(CONFIG_USBC_PPC_SBU)) ppc_set_sbu(port, 1); usb_mux_set(port, mux_mode, USB_SWITCH_CONNECT, polarity_rm_dts(pd_get_polarity(port))); dp_flags[port] |= DP_FLAGS_DP_ON; if (!(dp_flags[port] & DP_FLAGS_HPD_HI_PENDING)) return; #ifdef CONFIG_USB_PD_DP_HPD_GPIO svdm_set_hpd_gpio(port, 1); /* set the minimum time delay (2ms) for the next HPD IRQ */ svdm_hpd_deadline[port] = get_time().val + HPD_USTREAM_DEBOUNCE_LVL; #endif /* CONFIG_USB_PD_DP_HPD_GPIO */ usb_mux_hpd_update(port, USB_PD_MUX_HPD_LVL | USB_PD_MUX_HPD_IRQ_DEASSERTED); #ifdef USB_PD_PORT_TCPC_MST if (port == USB_PD_PORT_TCPC_MST) baseboard_mst_enable_control(port, 1); #endif } __overridable int svdm_dp_attention(int port, uint32_t *payload) { int lvl = PD_VDO_DPSTS_HPD_LVL(payload[1]); int irq = PD_VDO_DPSTS_HPD_IRQ(payload[1]); #ifdef CONFIG_USB_PD_DP_HPD_GPIO int cur_lvl = svdm_get_hpd_gpio(port); #endif /* CONFIG_USB_PD_DP_HPD_GPIO */ mux_state_t mux_state; dp_status[port] = payload[1]; if (chipset_in_state(CHIPSET_STATE_ANY_SUSPEND) && (irq || lvl)) /* * Wake up the AP. IRQ or level high indicates a DP sink is now * present. */ if (IS_ENABLED(CONFIG_MKBP_EVENT)) pd_notify_dp_alt_mode_entry(port); /* Its initial DP status message prior to config */ if (!(dp_flags[port] & DP_FLAGS_DP_ON)) { if (lvl) dp_flags[port] |= DP_FLAGS_HPD_HI_PENDING; return 1; } #ifdef CONFIG_USB_PD_DP_HPD_GPIO if (irq && !lvl) { /* * IRQ can only be generated when the level is high, because * the IRQ is signaled by a short low pulse from the high level. */ CPRINTF("ERR:HPD:IRQ&LOW\n"); return 0; /* nak */ } if (irq && cur_lvl) { uint64_t now = get_time().val; /* wait for the minimum spacing between IRQ_HPD if needed */ if (now < svdm_hpd_deadline[port]) usleep(svdm_hpd_deadline[port] - now); /* generate IRQ_HPD pulse */ svdm_set_hpd_gpio(port, 0); usleep(HPD_DSTREAM_DEBOUNCE_IRQ); svdm_set_hpd_gpio(port, 1); } else { svdm_set_hpd_gpio(port, lvl); } /* set the minimum time delay (2ms) for the next HPD IRQ */ svdm_hpd_deadline[port] = get_time().val + HPD_USTREAM_DEBOUNCE_LVL; #endif /* CONFIG_USB_PD_DP_HPD_GPIO */ mux_state = (lvl ? USB_PD_MUX_HPD_LVL : USB_PD_MUX_HPD_LVL_DEASSERTED) | (irq ? USB_PD_MUX_HPD_IRQ : USB_PD_MUX_HPD_IRQ_DEASSERTED); usb_mux_hpd_update(port, mux_state); #ifdef USB_PD_PORT_TCPC_MST if (port == USB_PD_PORT_TCPC_MST) baseboard_mst_enable_control(port, lvl); #endif /* ack */ return 1; } __overridable void svdm_exit_dp_mode(int port) { dp_flags[port] = 0; dp_status[port] = 0; #ifdef CONFIG_USB_PD_DP_HPD_GPIO svdm_set_hpd_gpio(port, 0); #endif /* CONFIG_USB_PD_DP_HPD_GPIO */ usb_mux_hpd_update(port, USB_PD_MUX_HPD_LVL_DEASSERTED | USB_PD_MUX_HPD_IRQ_DEASSERTED); #ifdef USB_PD_PORT_TCPC_MST if (port == USB_PD_PORT_TCPC_MST) baseboard_mst_enable_control(port, 0); #endif } __overridable int svdm_enter_gfu_mode(int port, uint32_t mode_caps) { /* Always enter GFU mode */ return 0; } __overridable void svdm_exit_gfu_mode(int port) { } __overridable int svdm_gfu_status(int port, uint32_t *payload) { /* * This is called after enter mode is successful, send unstructured * VDM to read info. */ pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_READ_INFO, NULL, 0); return 0; } __overridable int svdm_gfu_config(int port, uint32_t *payload) { return 0; } __overridable int svdm_gfu_attention(int port, uint32_t *payload) { return 0; } #ifdef CONFIG_USB_PD_TBT_COMPAT_MODE __overridable int svdm_tbt_compat_enter_mode(int port, uint32_t mode_caps) { return 0; } __overridable void svdm_tbt_compat_exit_mode(int port) { } __overridable int svdm_tbt_compat_status(int port, uint32_t *payload) { return 0; } __overridable int svdm_tbt_compat_config(int port, uint32_t *payload) { return 0; } __overridable int svdm_tbt_compat_attention(int port, uint32_t *payload) { return 0; } #endif /* CONFIG_USB_PD_TBT_COMPAT_MODE */ /* * TODO: b:169262276: For TCPMv2, move alternate mode specific entry, exit and * configuration to Device Policy Manager. */ const struct svdm_amode_fx supported_modes[] = { { .svid = USB_SID_DISPLAYPORT, .enter = &svdm_enter_dp_mode, .status = &svdm_dp_status, .config = &svdm_dp_config, .post_config = &svdm_dp_post_config, .attention = &svdm_dp_attention, .exit = &svdm_exit_dp_mode, }, { .svid = USB_VID_GOOGLE, .enter = &svdm_enter_gfu_mode, .status = &svdm_gfu_status, .config = &svdm_gfu_config, .attention = &svdm_gfu_attention, .exit = &svdm_exit_gfu_mode, }, #ifdef CONFIG_USB_PD_TBT_COMPAT_MODE { .svid = USB_VID_INTEL, .enter = &svdm_tbt_compat_enter_mode, .status = &svdm_tbt_compat_status, .config = &svdm_tbt_compat_config, .attention = &svdm_tbt_compat_attention, .exit = &svdm_tbt_compat_exit_mode, }, #endif /* CONFIG_USB_PD_TBT_COMPAT_MODE */ }; const int supported_modes_cnt = ARRAY_SIZE(supported_modes); #ifdef CONFIG_CMD_MFALLOW static int command_mfallow(int argc, char **argv) { char *e; int port; if (argc < 3) return EC_ERROR_PARAM_COUNT; port = strtoi(argv[1], &e, 10); if (*e || port >= board_get_usb_pd_port_count()) return EC_ERROR_PARAM2; if (!strcasecmp(argv[2], "true")) dp_port_mf_allow[port] = true; else if (!strcasecmp(argv[2], "false")) dp_port_mf_allow[port] = false; else return EC_ERROR_PARAM1; ccprintf("Port: %d multi function allowed is %s ", port, argv[2]); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(mfallow, command_mfallow, "port [true | false]", "Controls Multifunction choice during DP Altmode."); #endif