diff options
Diffstat (limited to 'common/usbc/dp_alt_mode.c')
-rw-r--r-- | common/usbc/dp_alt_mode.c | 429 |
1 files changed, 402 insertions, 27 deletions
diff --git a/common/usbc/dp_alt_mode.c b/common/usbc/dp_alt_mode.c index 22975aff5b..7ece3573a8 100644 --- a/common/usbc/dp_alt_mode.c +++ b/common/usbc/dp_alt_mode.c @@ -11,9 +11,14 @@ #include "atomic.h" #include "builtin/assert.h" +#include "chipset.h" #include "console.h" +#include "gpio.h" +#include "timer.h" +#include "typec_control.h" #include "usb_common.h" #include "usb_dp_alt_mode.h" +#include "usb_mux.h" #include "usb_pd.h" #include "usb_pd_tcpm.h" @@ -28,6 +33,33 @@ #define CPRINTS(format, args...) #endif +/* TODO(b/270409742): Remove this macro system for determining the GPIO */ +#ifndef PORT_TO_HPD +#define PORT_TO_HPD(port) ((port) ? GPIO_USB_C1_DP_HPD : GPIO_USB_C0_DP_HPD) +#endif /* PORT_TO_HPD */ + +/* + * Note: the following DP-related variables must be kept as-is since + * some boards are using them in their board-specific code. + * TODO(b/267545470): Fold board DP code into the DP module + */ + +/* + * 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 +}; + /* The state of the DP negotiation */ enum dp_states { DP_START = 0, @@ -64,6 +96,9 @@ static atomic_t dpm_dp_flags[CONFIG_USB_PD_PORT_MAX_COUNT]; #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)) +/* Note: There is only one DP mode currently specified */ +static const int dp_opos = 1; + bool dp_is_active(int port) { return dp_state[port] == DP_ACTIVE || dp_state[port] == DP_PREPARE_EXIT; @@ -116,9 +151,9 @@ static bool dp_response_valid(int port, enum tcpci_msg_type type, char *cmdt, static void dp_exit_to_usb_mode(int port) { - int opos = pd_alt_mode(port, TCPCI_MSG_SOP, USB_SID_DISPLAYPORT); + svdm_exit_dp_mode(port); + pd_set_dfp_enter_mode_flag(port, false); - pd_dfp_exit_mode(port, TCPCI_MSG_SOP, USB_SID_DISPLAYPORT, opos); set_usb_mux_with_current_data_role(port); CPRINTS("C%d: Exited DP mode", port); @@ -134,8 +169,6 @@ static void dp_exit_to_usb_mode(int port) void dp_vdm_acked(int port, enum tcpci_msg_type type, int vdo_count, uint32_t *vdm) { - const struct svdm_amode_data *modep = - pd_get_amode_data(port, type, USB_SID_DISPLAYPORT); const uint8_t vdm_cmd = PD_VDO_CMD(vdm[0]); if (!dp_response_valid(port, type, "ACK", vdm_cmd)) @@ -156,8 +189,7 @@ void dp_vdm_acked(int port, enum tcpci_msg_type type, int vdo_count, dp_state[port] = DP_STATUS_ACKED; break; case DP_PREPARE_CONFIG: - if (modep && modep->opos && modep->fx->post_config) - modep->fx->post_config(port); + svdm_dp_post_config(port); dp_state[port] = DP_ACTIVE; CPRINTS("C%d: Entered DP mode", port); break; @@ -228,8 +260,7 @@ void dp_vdm_naked(int port, enum tcpci_msg_type type, uint8_t vdm_cmd) 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); + uint32_t mode_vdos[PDO_MODES]; int vdo_count_ret; if (*vdo_count < VDO_MAX_SIZE) @@ -239,10 +270,14 @@ enum dpm_msg_setup_status dp_setup_next_vdm(int port, int *vdo_count, case DP_START: case DP_ENTER_RETRY: /* Enter the first supported mode for DisplayPort. */ - vdm[0] = pd_dfp_enter_mode(port, TCPCI_MSG_SOP, - USB_SID_DISPLAYPORT, 0); - if (vdm[0] == 0) + if (pd_get_mode_vdo_for_svid(port, TCPCI_MSG_SOP, + USB_SID_DISPLAYPORT, + mode_vdos) == 0) return MSG_SETUP_ERROR; + if (svdm_enter_dp_mode(port, mode_vdos[dp_opos - 1]) < 0) + return MSG_SETUP_ERROR; + vdm[0] = VDO(USB_SID_DISPLAYPORT, 1, + CMD_ENTER_MODE | VDO_OPOS(dp_opos)); /* 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)); @@ -251,20 +286,14 @@ enum dpm_msg_setup_status dp_setup_next_vdm(int port, int *vdo_count, CPRINTS("C%d: Attempting to enter DP mode", port); break; case DP_ENTER_ACKED: - if (!(modep && modep->opos)) - return MSG_SETUP_ERROR; - - vdo_count_ret = modep->fx->status(port, vdm); + vdo_count_ret = svdm_dp_status(port, vdm); if (vdo_count_ret == 0) return MSG_SETUP_ERROR; - vdm[0] |= PD_VDO_OPOS(modep->opos); + vdm[0] |= PD_VDO_OPOS(dp_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 MSG_SETUP_ERROR; - if (!get_dp_pin_mode(port)) return MSG_SETUP_ERROR; @@ -286,7 +315,7 @@ enum dpm_msg_setup_status dp_setup_next_vdm(int port, int *vdo_count, /* Fall through if no mux set is needed */ __fallthrough; case DP_PREPARE_CONFIG: - vdo_count_ret = modep->fx->config(port, vdm); + vdo_count_ret = svdm_dp_config(port, vdm); if (vdo_count_ret == 0) return MSG_SETUP_ERROR; vdm[0] |= VDO_CMDT(CMDT_INIT); @@ -303,22 +332,16 @@ enum dpm_msg_setup_status dp_setup_next_vdm(int port, int *vdo_count, * when an initial request to enter the mode is NAK'ed. * This can happen if the EC is restarted (e.g to go * into recovery mode) while DP alt mode is active. - * It would be good to invoke modep->fx->exit but - * this doesn't set up the VDM, it clears state. - * TODO(b/159856063): Clean up the API to the fx functions. */ usb_mux_set_safe_mode_exit(port); dp_state[port] = DP_PREPARE_EXIT; return MSG_SETUP_MUX_WAIT; case DP_PREPARE_EXIT: - if (!(modep && modep->opos)) - return MSG_SETUP_ERROR; - /* DPM should call setup only after safe state is set */ vdm[0] = VDO(USB_SID_DISPLAYPORT, 1, /* structured */ CMD_EXIT_MODE); - vdm[0] |= VDO_OPOS(modep->opos); + vdm[0] |= VDO_OPOS(dp_opos); vdm[0] |= VDO_CMDT(CMDT_INIT); vdm[0] |= VDO_SVDM_VERS(pd_get_vdo_ver(port, TCPCI_MSG_SOP)); vdo_count_ret = 1; @@ -341,3 +364,355 @@ enum dpm_msg_setup_status dp_setup_next_vdm(int port, int *vdo_count, return MSG_SETUP_UNSUPPORTED; } + +int svdm_dp_status(int port, uint32_t *payload) +{ + payload[0] = + VDO(USB_SID_DISPLAYPORT, 1, CMD_DP_STATUS | VDO_OPOS(dp_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; +}; + +/* + * 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) +{ + uint32_t mode_vdos[PDO_MODES]; + 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 (pd_get_mode_vdo_for_svid(port, TCPCI_MSG_SOP, USB_SID_DISPLAYPORT, + mode_vdos) == 0) + return 0; + + mode_caps = mode_vdos[dp_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); +} + +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; +} + +/* + * Note: the following DP-related overridables must be kept as-is since + * some boards are using them in their board-specific code. + * TODO(b/267545470): Fold board DP code into the DP module + */ +__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 CONFIG_AP_POWER_CONTROL + 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 uint8_t get_dp_pin_mode(int port) +{ + return pd_dfp_dp_get_pin_mode(port, dp_status[port]); +} + +/* Note: Assumes that pins have already been set in safe state if necessary */ +__overridable int svdm_dp_config(int port, uint32_t *payload) +{ + 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); + + payload[0] = + VDO(USB_SID_DISPLAYPORT, 1, CMD_DP_CONFIG | VDO_OPOS(dp_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. */ + typec_set_sbu(port, true); + + 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 +} + +#ifdef CONFIG_CMD_MFALLOW +static int command_mfallow(int argc, const 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 |