| /* 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. |
| */ |
| |
| /* LCOV_EXCL_START - TCPMv1 is difficult to meaningfully test: b/304349098. */ |
| |
| #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]) |
| crec_usleep(svdm_hpd_deadline[port] - now); |
| |
| /* generate IRQ_HPD pulse */ |
| svdm_set_hpd_gpio(port, 0); |
| crec_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 */ |
| |
| /* LCOV_EXCL_STOP */ |