/* 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. */ /* * Device Policy Manager implementation * Refer to USB PD 3.0 spec, version 2.0, sections 8.2 and 8.3 */ #include "charge_state.h" #include "compile_time_macros.h" #include "console.h" #include "ec_commands.h" #include "hooks.h" #include "system.h" #include "task.h" #include "tcpm/tcpm.h" #include "usb_dp_alt_mode.h" #include "usb_mode.h" #include "usb_pd.h" #include "usb_pd_dpm.h" #include "usb_pd_tcpm.h" #include "usb_tbt_alt_mode.h" #ifdef CONFIG_COMMON_RUNTIME #define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) #define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) #else #define CPRINTF(format, args...) #define CPRINTS(format, args...) #endif /* Max Attention length is header + 1 VDO */ #define DPM_ATTENION_MAX_VDO 2 static struct { uint32_t flags; uint32_t vdm_attention[DPM_ATTENION_MAX_VDO]; int vdm_cnt; mutex_t vdm_attention_mutex; } dpm[CONFIG_USB_PD_PORT_MAX_COUNT]; #define DPM_SET_FLAG(port, flag) atomic_or(&dpm[(port)].flags, (flag)) #define DPM_CLR_FLAG(port, flag) atomic_clear_bits(&dpm[(port)].flags, (flag)) #define DPM_CHK_FLAG(port, flag) (dpm[(port)].flags & (flag)) /* Flags for internal DPM state */ #define DPM_FLAG_MODE_ENTRY_DONE BIT(0) #define DPM_FLAG_EXIT_REQUEST BIT(1) #define DPM_FLAG_ENTER_DP BIT(2) #define DPM_FLAG_ENTER_TBT BIT(3) #define DPM_FLAG_ENTER_USB4 BIT(4) #define DPM_FLAG_SEND_ATTENTION BIT(5) #ifdef CONFIG_ZEPHYR static int init_vdm_attention_mutex(const struct device *dev) { int port; ARG_UNUSED(dev); for (port = 0; port < CONFIG_USB_PD_PORT_MAX_COUNT; port++) k_mutex_init(&dpm[port].vdm_attention_mutex); return 0; } SYS_INIT(init_vdm_attention_mutex, POST_KERNEL, 50); #endif /* CONFIG_ZEPHYR */ enum ec_status pd_request_vdm_attention(int port, const uint32_t *data, int vdo_count) { mutex_lock(&dpm[port].vdm_attention_mutex); /* Only one Attention message may be pending */ if (DPM_CHK_FLAG(port, DPM_FLAG_SEND_ATTENTION)) { mutex_unlock(&dpm[port].vdm_attention_mutex); return EC_RES_UNAVAILABLE; } /* SVDM Attention message must be 1 or 2 VDOs in length */ if (!vdo_count || (vdo_count > DPM_ATTENION_MAX_VDO)) { mutex_unlock(&dpm[port].vdm_attention_mutex); return EC_RES_INVALID_PARAM; } /* Save contents of Attention message */ memcpy(dpm[port].vdm_attention, data, vdo_count * sizeof(uint32_t)); dpm[port].vdm_cnt = vdo_count; /* * Indicate to DPM that an Attention message needs to be sent. This flag * will be cleared when the Attention message is sent to the policy * engine. */ DPM_SET_FLAG(port, DPM_FLAG_SEND_ATTENTION); mutex_unlock(&dpm[port].vdm_attention_mutex); return EC_RES_SUCCESS; } enum ec_status pd_request_enter_mode(int port, enum typec_mode mode) { if (port >= board_get_usb_pd_port_count()) return EC_RES_INVALID_PARAM; /* Only one enter request may be active at a time. */ if (DPM_CHK_FLAG(port, DPM_FLAG_ENTER_DP | DPM_FLAG_ENTER_TBT | DPM_FLAG_ENTER_USB4)) return EC_RES_BUSY; switch (mode) { case TYPEC_MODE_DP: DPM_SET_FLAG(port, DPM_FLAG_ENTER_DP); break; #ifdef CONFIG_USB_PD_TBT_COMPAT_MODE case TYPEC_MODE_TBT: DPM_SET_FLAG(port, DPM_FLAG_ENTER_TBT); break; #endif /* CONFIG_USB_PD_TBT_COMPAT_MODE */ #ifdef CONFIG_USB_PD_USB4 case TYPEC_MODE_USB4: DPM_SET_FLAG(port, DPM_FLAG_ENTER_USB4); break; #endif default: return EC_RES_INVALID_PARAM; } DPM_CLR_FLAG(port, DPM_FLAG_MODE_ENTRY_DONE); DPM_CLR_FLAG(port, DPM_FLAG_EXIT_REQUEST); return EC_RES_SUCCESS; } void dpm_init(int port) { dpm[port].flags = 0; } static void dpm_set_mode_entry_done(int port) { DPM_SET_FLAG(port, DPM_FLAG_MODE_ENTRY_DONE); DPM_CLR_FLAG(port, DPM_FLAG_ENTER_DP | DPM_FLAG_ENTER_TBT | DPM_FLAG_ENTER_USB4); } void dpm_set_mode_exit_request(int port) { DPM_SET_FLAG(port, DPM_FLAG_EXIT_REQUEST); } static void dpm_clear_mode_exit_request(int port) { DPM_CLR_FLAG(port, DPM_FLAG_EXIT_REQUEST); } /* * Returns true if the current policy requests that the EC try to enter this * mode on this port. If the EC is in charge of policy, the answer is always * yes. */ static bool dpm_mode_entry_requested(int port, enum typec_mode mode) { /* If the AP isn't controlling policy, the EC is. */ if (!IS_ENABLED(CONFIG_USB_PD_REQUIRE_AP_MODE_ENTRY)) return true; switch (mode) { case TYPEC_MODE_DP: return !!DPM_CHK_FLAG(port, DPM_FLAG_ENTER_DP); case TYPEC_MODE_TBT: return !!DPM_CHK_FLAG(port, DPM_FLAG_ENTER_TBT); case TYPEC_MODE_USB4: return !!DPM_CHK_FLAG(port, DPM_FLAG_ENTER_USB4); default: return false; } } void dpm_vdm_acked(int port, enum tcpci_msg_type type, int vdo_count, uint32_t *vdm) { const uint16_t svid = PD_VDO_VID(vdm[0]); assert(vdo_count >= 1); switch (svid) { case USB_SID_DISPLAYPORT: dp_vdm_acked(port, type, vdo_count, vdm); break; case USB_VID_INTEL: if (IS_ENABLED(CONFIG_USB_PD_TBT_COMPAT_MODE)) { intel_vdm_acked(port, type, vdo_count, vdm); break; } default: CPRINTS("C%d: Received unexpected VDM ACK for SVID %d", port, svid); } } void dpm_vdm_naked(int port, enum tcpci_msg_type type, uint16_t svid, uint8_t vdm_cmd) { switch (svid) { case USB_SID_DISPLAYPORT: dp_vdm_naked(port, type, vdm_cmd); break; case USB_VID_INTEL: if (IS_ENABLED(CONFIG_USB_PD_TBT_COMPAT_MODE)) { intel_vdm_naked(port, type, vdm_cmd); break; } default: CPRINTS("C%d: Received unexpected VDM NAK for SVID %d", port, svid); } } /* * Requests that the PE send one VDM, whichever is next in the mode entry * sequence. This only happens if preconditions for mode entry are met. If * CONFIG_USB_PD_REQUIRE_AP_MODE_ENTRY is enabled, this function waits for the * AP to direct mode entry. */ static void dpm_attempt_mode_entry(int port) { int vdo_count = 0; uint32_t vdm[VDO_MAX_SIZE]; enum tcpci_msg_type tx_type = TCPCI_MSG_SOP; bool enter_mode_requested = IS_ENABLED(CONFIG_USB_PD_REQUIRE_AP_MODE_ENTRY) ? false : true; if (pd_get_data_role(port) != PD_ROLE_DFP) { if (DPM_CHK_FLAG(port, DPM_FLAG_ENTER_DP | DPM_FLAG_ENTER_TBT | DPM_FLAG_ENTER_USB4)) DPM_CLR_FLAG(port, DPM_FLAG_ENTER_DP | DPM_FLAG_ENTER_TBT | DPM_FLAG_ENTER_USB4); /* * TODO(b/168030639): Notify the AP that the enter mode request * failed. */ return; } #ifdef HAS_TASK_CHIPSET /* * Do not try to enter mode while CPU is off. * CPU transitions (e.g b/158634281) can occur during the discovery * phase or during enter/exit negotiations, and the state * of the modes can get out of sync, causing the attempt to * enter the mode to fail prematurely. */ if (chipset_in_or_transitioning_to_state(CHIPSET_STATE_ANY_OFF)) return; #endif /* * If discovery has not occurred for modes, do not attempt to switch * to alt mode. */ if (pd_get_svids_discovery(port, TCPCI_MSG_SOP) != PD_DISC_COMPLETE || pd_get_modes_discovery(port, TCPCI_MSG_SOP) != PD_DISC_COMPLETE) return; if (dp_entry_is_done(port) || (IS_ENABLED(CONFIG_USB_PD_TBT_COMPAT_MODE) && tbt_entry_is_done(port)) || (IS_ENABLED(CONFIG_USB_PD_USB4) && enter_usb_entry_is_done(port))) { dpm_set_mode_entry_done(port); return; } /* Check if port, port partner and cable support USB4. */ if (IS_ENABLED(CONFIG_USB_PD_USB4) && board_is_tbt_usb4_port(port) && enter_usb_port_partner_is_capable(port) && enter_usb_cable_is_capable(port) && dpm_mode_entry_requested(port, TYPEC_MODE_USB4)) { /* * For certain cables, enter Thunderbolt alt mode with the * cable and USB4 mode with the port partner. */ if (tbt_cable_entry_required_for_usb4(port)) { vdo_count = tbt_setup_next_vdm(port, ARRAY_SIZE(vdm), vdm, &tx_type); } else { pd_dpm_request(port, DPM_REQUEST_ENTER_USB); return; } } /* If not, check if they support Thunderbolt alt mode. */ if (IS_ENABLED(CONFIG_USB_PD_TBT_COMPAT_MODE) && board_is_tbt_usb4_port(port) && pd_is_mode_discovered_for_svid(port, TCPCI_MSG_SOP, USB_VID_INTEL) && dpm_mode_entry_requested(port, TYPEC_MODE_TBT)) { enter_mode_requested = true; vdo_count = tbt_setup_next_vdm(port, ARRAY_SIZE(vdm), vdm, &tx_type); } /* If not, check if they support DisplayPort alt mode. */ if (vdo_count == 0 && !DPM_CHK_FLAG(port, DPM_FLAG_MODE_ENTRY_DONE) && pd_is_mode_discovered_for_svid(port, TCPCI_MSG_SOP, USB_SID_DISPLAYPORT) && dpm_mode_entry_requested(port, TYPEC_MODE_DP)) { enter_mode_requested = true; vdo_count = dp_setup_next_vdm(port, ARRAY_SIZE(vdm), vdm); } /* * If the PE didn't discover any supported (requested) alternate mode, * just mark setup done and get out of here. */ if (vdo_count == 0 && !DPM_CHK_FLAG(port, DPM_FLAG_MODE_ENTRY_DONE)) { if (enter_mode_requested) { /* * TODO(b/168030639): Notify the AP that mode entry * failed. */ CPRINTS("C%d: No supported alt mode discovered", port); } /* * If the AP did not request mode entry, it may do so in the * future, but the DPM is done trying for now. */ dpm_set_mode_entry_done(port); return; } if (vdo_count < 0) { dpm_set_mode_entry_done(port); CPRINTS("C%d: Couldn't construct alt mode VDM", port); return; } /* * TODO(b/155890173): Provide a host command to request that the PE send * an arbitrary VDM via this mechanism. */ if (!pd_setup_vdm_request(port, tx_type, vdm, vdo_count)) { dpm_set_mode_entry_done(port); return; } pd_dpm_request(port, DPM_REQUEST_VDM); } static void dpm_attempt_mode_exit(int port) { uint32_t vdm = 0; int vdo_count = 0; enum tcpci_msg_type tx_type = TCPCI_MSG_SOP; if (IS_ENABLED(CONFIG_USB_PD_USB4) && enter_usb_entry_is_done(port)) { CPRINTS("C%d: USB4 teardown", port); usb4_exit_mode_request(port); } if (IS_ENABLED(CONFIG_USB_PD_TBT_COMPAT_MODE) && tbt_is_active(port)) { /* * When the port is in USB4 mode and receives an exit request, * it leaves USB4 SOP in active state. * TODO(b/156749387): Support Data Reset for exiting USB4 SOP. */ CPRINTS("C%d: TBT teardown", port); tbt_exit_mode_request(port); vdo_count = tbt_setup_next_vdm(port, VDO_MAX_SIZE, &vdm, &tx_type); } else if (dp_is_active(port)) { CPRINTS("C%d: DP teardown", port); vdo_count = dp_setup_next_vdm(port, VDO_MAX_SIZE, &vdm); } else { /* Clear exit mode request */ dpm_clear_mode_exit_request(port); return; } if (!pd_setup_vdm_request(port, tx_type, &vdm, vdo_count)) { dpm_clear_mode_exit_request(port); return; } pd_dpm_request(port, DPM_REQUEST_VDM); } static void dpm_send_attention_vdm(int port) { /* Set up VDM ATTEN msg that was passed in previously */ if (pd_setup_vdm_request(port, TCPCI_MSG_SOP, dpm[port].vdm_attention, dpm[port].vdm_cnt) == true) /* Trigger PE to start a VDM command run */ pd_dpm_request(port, DPM_REQUEST_VDM); /* Clear flag after message is sent to PE layer */ DPM_CLR_FLAG(port, DPM_FLAG_SEND_ATTENTION); } void dpm_run(int port) { if (pd_get_data_role(port) == PD_ROLE_DFP) { /* Run DFP related DPM requests */ if (DPM_CHK_FLAG(port, DPM_FLAG_EXIT_REQUEST)) dpm_attempt_mode_exit(port); else if (!DPM_CHK_FLAG(port, DPM_FLAG_MODE_ENTRY_DONE)) dpm_attempt_mode_entry(port); } else { /* Run UFP related DPM requests */ if (DPM_CHK_FLAG(port, DPM_FLAG_SEND_ATTENTION)) dpm_send_attention_vdm(port); } } /* * Source-out policy variables and APIs * * Priority for the available 3.0 A ports is given in the following order: * - sink partners which report requiring > 1.5 A in their Sink_Capabilities */ /* * Bitmasks of port numbers in each following category * * Note: request bitmasks should be accessed atomically as other ports may alter * them */ static uint32_t max_current_claimed; K_MUTEX_DEFINE(max_current_claimed_lock); /* Ports with PD sink needing > 1.5 A */ static uint32_t sink_max_pdo_requested; /* Ports with FRS source needing > 1.5 A */ static uint32_t source_frs_max_requested; /* Ports with non-PD sinks, so current requirements are unknown */ static uint32_t non_pd_sink_max_requested; #define LOWEST_PORT(p) __builtin_ctz(p) /* Undefined behavior if p == 0 */ static int count_port_bits(uint32_t bitmask) { int i, total = 0; for (i = 0; i < board_get_usb_pd_port_count(); i++) { if (bitmask & BIT(i)) total++; } return total; } /* * Centralized, mutex-controlled updates to the claimed 3.0 A ports */ static void balance_source_ports(void); DECLARE_DEFERRED(balance_source_ports); static void balance_source_ports(void) { uint32_t removed_ports, new_ports; static bool deferred_waiting; if (task_get_current() == TASK_ID_HOOKS) deferred_waiting = false; /* * Ignore balance attempts while we're waiting for a downgraded port to * finish the downgrade. */ if (deferred_waiting) return; mutex_lock(&max_current_claimed_lock); /* Remove any ports which no longer require 3.0 A */ removed_ports = max_current_claimed & ~(sink_max_pdo_requested | source_frs_max_requested | non_pd_sink_max_requested); max_current_claimed &= ~removed_ports; /* Allocate 3.0 A to new PD sink ports that need it */ new_ports = sink_max_pdo_requested & ~max_current_claimed; while (new_ports) { int new_max_port = LOWEST_PORT(new_ports); if (count_port_bits(max_current_claimed) < CONFIG_USB_PD_3A_PORTS) { max_current_claimed |= BIT(new_max_port); typec_select_src_current_limit_rp(new_max_port, TYPEC_RP_3A0); } else if (non_pd_sink_max_requested & max_current_claimed) { /* Always downgrade non-PD ports first */ int rem_non_pd = LOWEST_PORT(non_pd_sink_max_requested & max_current_claimed); typec_select_src_current_limit_rp(rem_non_pd, typec_get_default_current_limit_rp(rem_non_pd)); max_current_claimed &= ~BIT(rem_non_pd); /* Wait tSinkAdj before using current */ deferred_waiting = true; hook_call_deferred(&balance_source_ports_data, PD_T_SINK_ADJ); goto unlock; } else if (source_frs_max_requested & max_current_claimed) { /* Downgrade lowest FRS port from 3.0 A slot */ int rem_frs = LOWEST_PORT(source_frs_max_requested & max_current_claimed); pd_dpm_request(rem_frs, DPM_REQUEST_FRS_DET_DISABLE); max_current_claimed &= ~BIT(rem_frs); /* Give 20 ms for the PD task to process DPM flag */ deferred_waiting = true; hook_call_deferred(&balance_source_ports_data, 20 * MSEC); goto unlock; } else { /* No lower priority ports to downgrade */ goto unlock; } new_ports &= ~BIT(new_max_port); } /* Allocate 3.0 A to any new FRS ports that need it */ new_ports = source_frs_max_requested & ~max_current_claimed; while (new_ports) { int new_frs_port = LOWEST_PORT(new_ports); if (count_port_bits(max_current_claimed) < CONFIG_USB_PD_3A_PORTS) { max_current_claimed |= BIT(new_frs_port); pd_dpm_request(new_frs_port, DPM_REQUEST_FRS_DET_ENABLE); } else if (non_pd_sink_max_requested & max_current_claimed) { int rem_non_pd = LOWEST_PORT(non_pd_sink_max_requested & max_current_claimed); typec_select_src_current_limit_rp(rem_non_pd, typec_get_default_current_limit_rp(rem_non_pd)); max_current_claimed &= ~BIT(rem_non_pd); /* Wait tSinkAdj before using current */ deferred_waiting = true; hook_call_deferred(&balance_source_ports_data, PD_T_SINK_ADJ); goto unlock; } else { /* No lower priority ports to downgrade */ goto unlock; } new_ports &= ~BIT(new_frs_port); } /* Allocate 3.0 A to any non-PD ports which could need it */ new_ports = non_pd_sink_max_requested & ~max_current_claimed; while (new_ports) { int new_max_port = LOWEST_PORT(new_ports); if (count_port_bits(max_current_claimed) < CONFIG_USB_PD_3A_PORTS) { max_current_claimed |= BIT(new_max_port); typec_select_src_current_limit_rp(new_max_port, TYPEC_RP_3A0); } else { /* No lower priority ports to downgrade */ goto unlock; } new_ports &= ~BIT(new_max_port); } unlock: mutex_unlock(&max_current_claimed_lock); } /* Process port's first Sink_Capabilities PDO for port current consideration */ void dpm_evaluate_sink_fixed_pdo(int port, uint32_t vsafe5v_pdo) { /* Verify partner supplied valid vSafe5V fixed object first */ if ((vsafe5v_pdo & PDO_TYPE_MASK) != PDO_TYPE_FIXED) return; if (PDO_FIXED_VOLTAGE(vsafe5v_pdo) != 5000) return; if (pd_get_power_role(port) == PD_ROLE_SOURCE) { if (CONFIG_USB_PD_3A_PORTS == 0) return; /* Valid PDO to process, so evaluate whether >1.5A is needed */ if (PDO_FIXED_CURRENT(vsafe5v_pdo) <= 1500) return; atomic_or(&sink_max_pdo_requested, BIT(port)); } else { int frs_current = vsafe5v_pdo & PDO_FIXED_FRS_CURR_MASK; if (!IS_ENABLED(CONFIG_USB_PD_FRS)) return; /* FRS is only supported in PD 3.0 and higher */ if (pd_get_rev(port, TCPCI_MSG_SOP) == PD_REV20) return; if ((vsafe5v_pdo & PDO_FIXED_DUAL_ROLE) && frs_current) { /* Always enable FRS when 3.0 A is not needed */ if (frs_current == PDO_FIXED_FRS_CURR_DFLT_USB_POWER || frs_current == PDO_FIXED_FRS_CURR_1A5_AT_5V) { pd_dpm_request(port, DPM_REQUEST_FRS_DET_ENABLE); return; } if (CONFIG_USB_PD_3A_PORTS == 0) return; atomic_or(&source_frs_max_requested, BIT(port)); } else { return; } } balance_source_ports(); } void dpm_add_non_pd_sink(int port) { if (CONFIG_USB_PD_3A_PORTS == 0) return; atomic_or(&non_pd_sink_max_requested, BIT(port)); balance_source_ports(); } void dpm_remove_sink(int port) { if (CONFIG_USB_PD_3A_PORTS == 0) return; if (!(BIT(port) & sink_max_pdo_requested) && !(BIT(port) & non_pd_sink_max_requested)) return; atomic_clear_bits(&sink_max_pdo_requested, BIT(port)); atomic_clear_bits(&non_pd_sink_max_requested, BIT(port)); /* Restore selected default Rp on the port */ typec_select_src_current_limit_rp(port, typec_get_default_current_limit_rp(port)); balance_source_ports(); } void dpm_remove_source(int port) { if (CONFIG_USB_PD_3A_PORTS == 0) return; if (!IS_ENABLED(CONFIG_USB_PD_FRS)) return; if (!(BIT(port) & source_frs_max_requested)) return; atomic_clear_bits(&source_frs_max_requested, BIT(port)); balance_source_ports(); } /* * Note: all ports receive the 1.5 A source offering until they are found to * match a criteria on the 3.0 A priority list (ex. through sink capability * probing), at which point they will be offered a new 3.0 A source capability. */ __overridable int dpm_get_source_pdo(const uint32_t **src_pdo, const int port) { /* Max PDO may not exist on boards which don't offer 3 A */ #if CONFIG_USB_PD_3A_PORTS > 0 if (max_current_claimed & BIT(port)) { *src_pdo = pd_src_pdo_max; return pd_src_pdo_max_cnt; } #endif *src_pdo = pd_src_pdo; return pd_src_pdo_cnt; } int dpm_get_source_current(const int port) { if (pd_get_power_role(port) == PD_ROLE_SINK) return 0; if (max_current_claimed & BIT(port)) return 3000; else if (typec_get_default_current_limit_rp(port) == TYPEC_RP_1A5) return 1500; else return 500; }