/* Copyright 2014 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "atomic.h" #include "builtin/assert.h" #include "charge_manager.h" #include "chipset.h" #include "common.h" #include "console.h" #include "cros_version.h" #include "ec_commands.h" #include "flash.h" #include "gpio.h" #include "hooks.h" #include "host_command.h" #include "mkbp_event.h" #include "registers.h" #include "rsa.h" #include "sha256.h" #include "system.h" #include "task.h" #include "tcpm/tcpm.h" #include "timer.h" #include "typec_control.h" #include "usb_api.h" #include "usb_common.h" #include "usb_mux.h" #include "usb_pd.h" #include "usb_pd_tcpm.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 /* * This file is currently only used for TCPMv1, and would need changes before * being used for TCPMv2. One example: PD_FLAGS_* are TCPMv1 only. */ #ifndef CONFIG_USB_PD_TCPMV1 #error This file must only be used with TCPMv1 #endif #ifdef CONFIG_USB_PD_ALT_MODE_DFP #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, }; #endif /* CONFIG_USB_PD_ALT_MODE_DFP */ static int rw_flash_changed = 1; __overridable void pd_check_pr_role(int port, enum pd_power_role pr_role, int flags) { /* * If partner is dual-role power and dualrole toggling is on, consider * if a power swap is necessary. */ if ((flags & PD_FLAGS_PARTNER_DR_POWER) && pd_get_dual_role(port) == PD_DRP_TOGGLE_ON) { /* * If we are a sink and partner is not unconstrained, then * swap to become a source. If we are source and partner is * unconstrained, swap to become a sink. */ int partner_unconstrained = flags & PD_FLAGS_PARTNER_UNCONSTR; if ((!partner_unconstrained && pr_role == PD_ROLE_SINK) || (partner_unconstrained && pr_role == PD_ROLE_SOURCE)) pd_request_power_swap(port); } } __overridable void pd_check_dr_role(int port, enum pd_data_role dr_role, int flags) { /* If UFP, try to switch to DFP */ if ((flags & PD_FLAGS_PARTNER_DR_DATA) && dr_role == PD_ROLE_UFP) pd_request_data_swap(port); } /* Last received source cap */ static uint32_t pd_src_caps[CONFIG_USB_PD_PORT_MAX_COUNT][PDO_MAX_OBJECTS]; static uint8_t pd_src_cap_cnt[CONFIG_USB_PD_PORT_MAX_COUNT]; const uint32_t *const pd_get_src_caps(int port) { return pd_src_caps[port]; } void pd_set_src_caps(int port, int cnt, uint32_t *src_caps) { int i; pd_src_cap_cnt[port] = cnt; for (i = 0; i < cnt; i++) pd_src_caps[port][i] = *src_caps++; } uint8_t pd_get_src_cap_cnt(int port) { return pd_src_cap_cnt[port]; } #ifdef CONFIG_USB_PD_ALT_MODE #ifdef CONFIG_USB_PD_ALT_MODE_DFP static struct pd_discovery discovery[CONFIG_USB_PD_PORT_MAX_COUNT] [DISCOVERY_TYPE_COUNT]; static struct partner_active_modes partner_amodes[CONFIG_USB_PD_PORT_MAX_COUNT] [AMODE_TYPE_COUNT]; void pd_dfp_discovery_init(int port) { memset(&discovery[port], 0, sizeof(struct pd_discovery)); } void pd_dfp_mode_init(int port) { memset(&partner_amodes[port], 0, sizeof(partner_amodes[0])); } static int dfp_discover_svids(uint32_t *payload) { payload[0] = VDO(USB_SID_PD, 1, CMD_DISCOVER_SVID); return 1; } struct pd_discovery * pd_get_am_discovery_and_notify_access(int port, enum tcpci_msg_type type) { return (struct pd_discovery *)pd_get_am_discovery(port, type); } const struct pd_discovery *pd_get_am_discovery(int port, enum tcpci_msg_type type) { return &discovery[port][type]; } struct partner_active_modes * pd_get_partner_active_modes(int port, enum tcpci_msg_type type) { assert(type < AMODE_TYPE_COUNT); return &partner_amodes[port][type]; } /* Note: Enter mode flag is not needed by TCPMv1 */ void pd_set_dfp_enter_mode_flag(int port, bool set) { } /** * Return the discover alternate mode payload data * * @param port USB-C port number * @param payload Pointer to payload data to fill * @return 1 if valid SVID present else 0 */ static int dfp_discover_modes(int port, uint32_t *payload) { const struct pd_discovery *disc = pd_get_am_discovery(port, TCPCI_MSG_SOP); uint16_t svid = disc->svids[disc->svid_idx].svid; if (disc->svid_idx >= disc->svid_cnt) return 0; payload[0] = VDO(svid, 1, CMD_DISCOVER_MODES); return 1; } static int process_am_discover_ident_sop(int port, int cnt, uint32_t head, uint32_t *payload, enum tcpci_msg_type *rtype) { pd_dfp_discovery_init(port); pd_dfp_mode_init(port); dfp_consume_identity(port, TCPCI_MSG_SOP, cnt, payload); return dfp_discover_svids(payload); } static int process_am_discover_svids(int port, int cnt, uint32_t *payload, enum tcpci_msg_type sop, enum tcpci_msg_type *rtype) { /* * The pd_discovery structure stores SOP and SOP' discovery results * separately, but TCPMv1 depends on one-dimensional storage of SVIDs * and modes. Therefore, always use TCPCI_MSG_SOP in TCPMv1. */ dfp_consume_svids(port, sop, cnt, payload); return dfp_discover_modes(port, payload); } #endif /* CONFIG_USB_PD_ALT_MODE_DFP */ int pd_svdm(int port, int cnt, uint32_t *payload, uint32_t **rpayload, uint32_t head, enum tcpci_msg_type *rtype) { int cmd = PD_VDO_CMD(payload[0]); int cmd_type = PD_VDO_CMDT(payload[0]); int (*func)(int port, uint32_t *payload) = NULL; int rsize = 1; /* VDM header at a minimum */ #ifdef CONFIG_USB_PD_ALT_MODE_DFP enum tcpci_msg_type sop = PD_HEADER_GET_SOP(head); #endif /* Transmit SOP messages by default */ *rtype = TCPCI_MSG_SOP; payload[0] &= ~VDO_CMDT_MASK; *rpayload = payload; if (cmd_type == CMDT_INIT) { switch (cmd) { case CMD_DISCOVER_IDENT: func = svdm_rsp.identity; break; case CMD_DISCOVER_SVID: func = svdm_rsp.svids; break; case CMD_DISCOVER_MODES: func = svdm_rsp.modes; break; case CMD_ENTER_MODE: func = svdm_rsp.enter_mode; break; case CMD_DP_STATUS: if (svdm_rsp.amode) func = svdm_rsp.amode->status; break; case CMD_DP_CONFIG: if (svdm_rsp.amode) func = svdm_rsp.amode->config; break; case CMD_EXIT_MODE: func = svdm_rsp.exit_mode; break; #ifdef CONFIG_USB_PD_ALT_MODE_DFP case CMD_ATTENTION: /* * attention is only SVDM with no response * (just goodCRC) return zero here. */ dfp_consume_attention(port, payload); return 0; #endif default: CPRINTF("ERR:CMD:%d\n", cmd); rsize = 0; } if (func) rsize = func(port, payload); else /* not supported : NACK it */ rsize = 0; if (rsize >= 1) payload[0] |= VDO_CMDT(CMDT_RSP_ACK); else if (!rsize) { payload[0] |= VDO_CMDT(CMDT_RSP_NAK); rsize = 1; } else { payload[0] |= VDO_CMDT(CMDT_RSP_BUSY); rsize = 1; } payload[0] |= VDO_SVDM_VERS(pd_get_vdo_ver(port, TCPCI_MSG_SOP)); } else if (cmd_type == CMDT_RSP_ACK) { #ifdef CONFIG_USB_PD_ALT_MODE_DFP struct svdm_amode_data *modep; modep = pd_get_amode_data(port, TCPCI_MSG_SOP, PD_VDO_VID(payload[0])); #endif switch (cmd) { #ifdef CONFIG_USB_PD_ALT_MODE_DFP case CMD_DISCOVER_IDENT: /* Received a SOP Discover Ident Message */ rsize = process_am_discover_ident_sop(port, cnt, head, payload, rtype); break; case CMD_DISCOVER_SVID: rsize = process_am_discover_svids(port, cnt, payload, sop, rtype); break; case CMD_DISCOVER_MODES: dfp_consume_modes(port, sop, cnt, payload); rsize = dfp_discover_modes(port, payload); /* enter the default mode for DFP */ if (!rsize) { payload[0] = pd_dfp_enter_mode( port, TCPCI_MSG_SOP, 0, 0); if (payload[0]) rsize = 1; } break; case CMD_ENTER_MODE: if (!modep) { rsize = 0; } else { if (!modep->opos) pd_dfp_enter_mode(port, TCPCI_MSG_SOP, 0, 0); if (modep->opos) { rsize = modep->fx->status(port, payload); payload[0] |= PD_VDO_OPOS(modep->opos); } } break; case CMD_DP_STATUS: /* * Note: DP status response & UFP's DP attention have * the same payload */ dfp_consume_attention(port, payload); if (modep && modep->opos) { /* * 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); rsize = modep->fx->config(port, payload); } else { rsize = 0; } break; case CMD_DP_CONFIG: if (modep && modep->opos && modep->fx->post_config) modep->fx->post_config(port); /* no response after DFPs ack */ rsize = 0; break; case CMD_EXIT_MODE: /* no response after DFPs ack */ rsize = 0; break; #endif case CMD_ATTENTION: /* no response after DFPs ack */ rsize = 0; break; default: CPRINTF("ERR:CMD:%d\n", cmd); rsize = 0; } payload[0] |= VDO_CMDT(CMDT_INIT); payload[0] |= VDO_SVDM_VERS(pd_get_vdo_ver(port, TCPCI_MSG_SOP)); #ifdef CONFIG_USB_PD_ALT_MODE_DFP } else if (cmd_type == CMDT_RSP_BUSY) { switch (cmd) { case CMD_DISCOVER_IDENT: case CMD_DISCOVER_SVID: case CMD_DISCOVER_MODES: /* resend if its discovery */ rsize = 1; break; case CMD_ENTER_MODE: /* Error */ CPRINTF("ERR:ENTBUSY\n"); rsize = 0; break; case CMD_EXIT_MODE: rsize = 0; break; default: rsize = 0; } } else if (cmd_type == CMDT_RSP_NAK) { rsize = 0; #endif /* CONFIG_USB_PD_ALT_MODE_DFP */ } else { CPRINTF("ERR:CMDT:%d\n", cmd); /* do not answer */ rsize = 0; } return rsize; } #else int pd_svdm(int port, int cnt, uint32_t *payload, uint32_t **rpayload, uint32_t head, enum tcpci_msg_type *rtype) { return 0; } #endif /* CONFIG_USB_PD_ALT_MODE */ #define FW_RW_END \ (CONFIG_EC_WRITABLE_STORAGE_OFF + CONFIG_RW_STORAGE_OFF + \ CONFIG_RW_SIZE) uint8_t *flash_hash_rw(void) { static struct sha256_ctx ctx; /* re-calculate RW hash when changed as its time consuming */ if (rw_flash_changed) { rw_flash_changed = 0; SHA256_init(&ctx); SHA256_update(&ctx, (void *)CONFIG_PROGRAM_MEMORY_BASE + CONFIG_RW_MEM_OFF, CONFIG_RW_SIZE - RSANUMBYTES); return SHA256_final(&ctx); } else { return ctx.buf; } } void pd_get_info(uint32_t *info_data) { void *rw_hash = flash_hash_rw(); /* copy first 20 bytes of RW hash */ memcpy(info_data, rw_hash, 5 * sizeof(uint32_t)); /* copy other info into data msg */ #if defined(CONFIG_USB_PD_HW_DEV_ID_BOARD_MAJOR) && \ defined(CONFIG_USB_PD_HW_DEV_ID_BOARD_MINOR) info_data[5] = VDO_INFO(CONFIG_USB_PD_HW_DEV_ID_BOARD_MAJOR, CONFIG_USB_PD_HW_DEV_ID_BOARD_MINOR, ver_get_num_commits(system_get_image_copy()), (system_get_image_copy() != EC_IMAGE_RO)); #else info_data[5] = 0; #endif } int pd_custom_flash_vdm(int port, int cnt, uint32_t *payload) { static int flash_offset; int rsize = 1; /* default is just VDM header returned */ switch (PD_VDO_CMD(payload[0])) { case VDO_CMD_VERSION: memcpy(payload + 1, ¤t_image_data.version, 24); rsize = 7; break; case VDO_CMD_REBOOT: /* ensure the power supply is in a safe state */ pd_power_supply_reset(0); system_reset(0); break; case VDO_CMD_READ_INFO: /* copy info into response */ pd_get_info(payload + 1); rsize = 7; break; case VDO_CMD_FLASH_ERASE: /* do not kill the code under our feet */ if (system_get_image_copy() != EC_IMAGE_RO) break; pd_log_event(PD_EVENT_ACC_RW_ERASE, 0, 0, NULL); flash_offset = CONFIG_EC_WRITABLE_STORAGE_OFF + CONFIG_RW_STORAGE_OFF; crec_flash_physical_erase(CONFIG_EC_WRITABLE_STORAGE_OFF + CONFIG_RW_STORAGE_OFF, CONFIG_RW_SIZE); rw_flash_changed = 1; break; case VDO_CMD_FLASH_WRITE: /* do not kill the code under our feet */ if ((system_get_image_copy() != EC_IMAGE_RO) || (flash_offset < CONFIG_EC_WRITABLE_STORAGE_OFF + CONFIG_RW_STORAGE_OFF)) break; crec_flash_physical_write(flash_offset, 4 * (cnt - 1), (const char *)(payload + 1)); flash_offset += 4 * (cnt - 1); rw_flash_changed = 1; break; case VDO_CMD_ERASE_SIG: /* this is not touching the code area */ { uint32_t zero = 0; int offset; /* zeroes the area containing the RSA signature */ for (offset = FW_RW_END - RSANUMBYTES; offset < FW_RW_END; offset += 4) crec_flash_physical_write(offset, 4, (const char *)&zero); } break; default: /* Unknown : do not answer */ return 0; } return rsize; } #ifdef CONFIG_USB_PD_ALT_MODE_DFP static enum ec_status hc_remote_pd_set_amode(struct host_cmd_handler_args *args) { const struct ec_params_usb_pd_set_mode_request *p = args->params; if ((p->port >= board_get_usb_pd_port_count()) || (!p->svid) || (!p->opos)) return EC_RES_INVALID_PARAM; switch (p->cmd) { case PD_EXIT_MODE: if (pd_dfp_exit_mode(p->port, TCPCI_MSG_SOP, p->svid, p->opos)) pd_send_vdm(p->port, p->svid, CMD_EXIT_MODE | VDO_OPOS(p->opos), NULL, 0); else { CPRINTF("Failed exit mode\n"); return EC_RES_ERROR; } break; case PD_ENTER_MODE: if (pd_dfp_enter_mode(p->port, TCPCI_MSG_SOP, p->svid, p->opos)) pd_send_vdm(p->port, p->svid, CMD_ENTER_MODE | VDO_OPOS(p->opos), NULL, 0); break; default: return EC_RES_INVALID_PARAM; } return EC_RES_SUCCESS; } DECLARE_HOST_COMMAND(EC_CMD_USB_PD_SET_AMODE, hc_remote_pd_set_amode, EC_VER_MASK(0)); 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; } static enum ec_status hc_remote_pd_get_amode(struct host_cmd_handler_args *args) { struct svdm_amode_data *modep; const struct ec_params_usb_pd_get_mode_request *p = args->params; struct ec_params_usb_pd_get_mode_response *r = args->response; if (p->port >= board_get_usb_pd_port_count()) return EC_RES_INVALID_PARAM; /* no more to send */ /* TODO(b/148528713): Use TCPMv2's separate storage for SOP'. */ if (p->svid_idx >= pd_get_svid_count(p->port, TCPCI_MSG_SOP)) { r->svid = 0; args->response_size = sizeof(r->svid); return EC_RES_SUCCESS; } r->svid = pd_get_svid(p->port, p->svid_idx, TCPCI_MSG_SOP); r->opos = 0; memcpy(r->vdo, pd_get_mode_vdo(p->port, p->svid_idx, TCPCI_MSG_SOP), sizeof(uint32_t) * VDO_MAX_OBJECTS); modep = pd_get_amode_data(p->port, TCPCI_MSG_SOP, r->svid); if (modep) r->opos = pd_alt_mode(p->port, TCPCI_MSG_SOP, r->svid); args->response_size = sizeof(*r); return EC_RES_SUCCESS; } DECLARE_HOST_COMMAND(EC_CMD_USB_PD_GET_AMODE, hc_remote_pd_get_amode, EC_VER_MASK(0)); 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; } } #ifdef CONFIG_USB_PD_DP_MODE /* * 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); } #endif /* CONFIG_USB_PD_DP_MODE */ 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)); } 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; } 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); } 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 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); } #ifdef CONFIG_USB_PD_DP_MODE __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 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]); } 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: Assumes that pins have already been set in safe state if necessary */ __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); 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. */ 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 } #endif /* CONFIG_USB_PD_DP_MODE */ __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; } const struct svdm_amode_fx supported_modes[] = { #ifdef CONFIG_USB_PD_DP_MODE { .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, }, #endif /* CONFIG_USB_PD_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, }, }; const int supported_modes_cnt = ARRAY_SIZE(supported_modes); #if defined(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_PARAM1; 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_PARAM2; 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 /* CONFIG_CMD_MFALLOW */ #ifdef CONFIG_COMMON_RUNTIME static enum ec_status hc_remote_pd_dev_info(struct host_cmd_handler_args *args) { const struct ec_params_usb_pd_info_request *p = args->params; struct ec_params_usb_pd_rw_hash_entry *r = args->response; uint16_t dev_id; uint32_t current_image; if (p->port >= board_get_usb_pd_port_count()) return EC_RES_INVALID_PARAM; pd_dev_get_rw_hash(p->port, &dev_id, r->dev_rw_hash, ¤t_image); r->dev_id = dev_id; r->current_image = current_image; args->response_size = sizeof(*r); return EC_RES_SUCCESS; } DECLARE_HOST_COMMAND(EC_CMD_USB_PD_DEV_INFO, hc_remote_pd_dev_info, EC_VER_MASK(0)); #endif /* CONFIG_COMMON_RUNTIME */ #endif /* CONFIG_USB_PD_ALT_MODE_DFP */