| /* Copyright 2019 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. |
| */ |
| |
| /* |
| * Contains common USB functions shared between the old (i.e. usb_pd_protocol) |
| * and the new (i.e. usb_sm_*) USB-C PD stacks. |
| */ |
| |
| #include "atomic.h" |
| #include "charge_state.h" |
| #include "common.h" |
| #include "hooks.h" |
| #include "task.h" |
| #include "usb_common.h" |
| #include "usb_pd.h" |
| #include "usb_pd_tcpm.h" |
| #include "usbc_ppc.h" |
| #include "util.h" |
| |
| #ifdef CONFIG_COMMON_RUNTIME |
| #define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) |
| #define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) |
| #endif |
| |
| int usb_get_battery_soc(void) |
| { |
| #if defined(CONFIG_CHARGER) |
| return charge_get_percent(); |
| #elif defined(CONFIG_BATTERY) |
| return board_get_battery_soc(); |
| #else |
| return 0; |
| #endif |
| } |
| |
| /* |
| * CC values for regular sources and Debug sources (aka DTS) |
| * |
| * Source type Mode of Operation CC1 CC2 |
| * --------------------------------------------- |
| * Regular Default USB Power RpUSB Open |
| * Regular USB-C @ 1.5 A Rp1A5 Open |
| * Regular USB-C @ 3 A Rp3A0 Open |
| * DTS Default USB Power Rp3A0 Rp1A5 |
| * DTS USB-C @ 1.5 A Rp1A5 RpUSB |
| * DTS USB-C @ 3 A Rp3A0 RpUSB |
| */ |
| |
| typec_current_t usb_get_typec_current_limit(enum pd_cc_polarity_type polarity, |
| enum tcpc_cc_voltage_status cc1, enum tcpc_cc_voltage_status cc2) |
| { |
| typec_current_t charge = 0; |
| enum tcpc_cc_voltage_status cc = polarity ? cc2 : cc1; |
| enum tcpc_cc_voltage_status cc_alt = polarity ? cc1 : cc2; |
| |
| switch (cc) { |
| case TYPEC_CC_VOLT_RP_3_0: |
| if (!cc_is_rp(cc_alt) || cc_alt == TYPEC_CC_VOLT_RP_DEF) |
| charge = 3000; |
| else if (cc_alt == TYPEC_CC_VOLT_RP_1_5) |
| charge = 500; |
| break; |
| case TYPEC_CC_VOLT_RP_1_5: |
| charge = 1500; |
| break; |
| case TYPEC_CC_VOLT_RP_DEF: |
| charge = 500; |
| break; |
| default: |
| break; |
| } |
| |
| if (IS_ENABLED(CONFIG_USBC_DISABLE_CHARGE_FROM_RP_DEF) && charge == 500) |
| charge = 0; |
| |
| if (cc_is_rp(cc_alt)) |
| charge |= TYPEC_CURRENT_DTS_MASK; |
| |
| return charge; |
| } |
| |
| enum pd_cc_polarity_type get_snk_polarity(enum tcpc_cc_voltage_status cc1, |
| enum tcpc_cc_voltage_status cc2) |
| { |
| /* The following assumes: |
| * |
| * TYPEC_CC_VOLT_RP_3_0 > TYPEC_CC_VOLT_RP_1_5 |
| * TYPEC_CC_VOLT_RP_1_5 > TYPEC_CC_VOLT_RP_DEF |
| * TYPEC_CC_VOLT_RP_DEF > TYPEC_CC_VOLT_OPEN |
| */ |
| return cc2 > cc1; |
| } |
| |
| enum pd_cc_states pd_get_cc_state( |
| enum tcpc_cc_voltage_status cc1, enum tcpc_cc_voltage_status cc2) |
| { |
| /* Port partner is a SNK */ |
| if (cc_is_snk_dbg_acc(cc1, cc2)) |
| return PD_CC_UFP_DEBUG_ACC; |
| if (cc_is_at_least_one_rd(cc1, cc2)) |
| return PD_CC_UFP_ATTACHED; |
| if (cc_is_audio_acc(cc1, cc2)) |
| return PD_CC_UFP_AUDIO_ACC; |
| |
| /* Port partner is a SRC */ |
| if (cc_is_rp(cc1) && cc_is_rp(cc2)) |
| return PD_CC_DFP_DEBUG_ACC; |
| if (cc_is_rp(cc1) || cc_is_rp(cc2)) |
| return PD_CC_DFP_ATTACHED; |
| |
| /* |
| * 1) Both lines are Vopen or |
| * 2) Only an e-marked cabled without a partner on the other side |
| */ |
| return PD_CC_NONE; |
| } |
| /* |
| * Zinger implements a board specific usb policy that does not define |
| * PD_MAX_VOLTAGE_MV and PD_OPERATING_POWER_MW. And in turn, does not |
| * use the following functions. |
| */ |
| #if defined(PD_MAX_VOLTAGE_MV) && defined(PD_OPERATING_POWER_MW) |
| int pd_find_pdo_index(uint32_t src_cap_cnt, const uint32_t * const src_caps, |
| int max_mv, uint32_t *selected_pdo) |
| { |
| int i, uw, mv; |
| int ret = 0; |
| int cur_uw = 0; |
| int prefer_cur; |
| |
| int __attribute__((unused)) cur_mv = 0; |
| |
| /* max voltage is always limited by this boards max request */ |
| max_mv = MIN(max_mv, PD_MAX_VOLTAGE_MV); |
| |
| /* Get max power that is under our max voltage input */ |
| for (i = 0; i < src_cap_cnt; i++) { |
| /* its an unsupported Augmented PDO (PD3.0) */ |
| if ((src_caps[i] & PDO_TYPE_MASK) == PDO_TYPE_AUGMENTED) |
| continue; |
| |
| mv = ((src_caps[i] >> 10) & 0x3FF) * 50; |
| /* Skip invalid voltage */ |
| if (!mv) |
| continue; |
| /* Skip any voltage not supported by this board */ |
| if (!pd_is_valid_input_voltage(mv)) |
| continue; |
| |
| if ((src_caps[i] & PDO_TYPE_MASK) == PDO_TYPE_BATTERY) { |
| uw = 250000 * (src_caps[i] & 0x3FF); |
| } else { |
| int ma = (src_caps[i] & 0x3FF) * 10; |
| |
| ma = MIN(ma, PD_MAX_CURRENT_MA); |
| uw = ma * mv; |
| } |
| |
| if (mv > max_mv) |
| continue; |
| uw = MIN(uw, PD_MAX_POWER_MW * 1000); |
| prefer_cur = 0; |
| |
| /* Apply special rules in case of 'tie' */ |
| if (IS_ENABLED(PD_PREFER_LOW_VOLTAGE)) { |
| if (uw == cur_uw && mv < cur_mv) |
| prefer_cur = 1; |
| } else if (IS_ENABLED(PD_PREFER_HIGH_VOLTAGE)) { |
| if (uw == cur_uw && mv > cur_mv) |
| prefer_cur = 1; |
| } |
| |
| /* Prefer higher power, except for tiebreaker */ |
| if (uw > cur_uw || prefer_cur) { |
| ret = i; |
| cur_uw = uw; |
| cur_mv = mv; |
| } |
| } |
| |
| if (selected_pdo) |
| *selected_pdo = src_caps[ret]; |
| |
| return ret; |
| } |
| |
| void pd_extract_pdo_power(uint32_t pdo, uint32_t *ma, uint32_t *mv) |
| { |
| int max_ma, uw; |
| |
| *mv = ((pdo >> 10) & 0x3FF) * 50; |
| |
| if (*mv == 0) { |
| *ma = 0; |
| return; |
| } |
| |
| if ((pdo & PDO_TYPE_MASK) == PDO_TYPE_BATTERY) { |
| uw = 250000 * (pdo & 0x3FF); |
| max_ma = 1000 * MIN(1000 * uw, PD_MAX_POWER_MW) / *mv; |
| } else { |
| max_ma = 10 * (pdo & 0x3FF); |
| max_ma = MIN(max_ma, PD_MAX_POWER_MW * 1000 / *mv); |
| } |
| |
| *ma = MIN(max_ma, PD_MAX_CURRENT_MA); |
| } |
| |
| void pd_build_request(uint32_t src_cap_cnt, const uint32_t * const src_caps, |
| int32_t vpd_vdo, uint32_t *rdo, uint32_t *ma, |
| uint32_t *mv, enum pd_request_type req_type, |
| uint32_t max_request_mv) |
| { |
| uint32_t pdo; |
| int pdo_index, flags = 0; |
| int uw; |
| int max_or_min_ma; |
| int max_or_min_mw; |
| int max_vbus; |
| int vpd_vbus_dcr; |
| int vpd_gnd_dcr; |
| |
| if (req_type == PD_REQUEST_VSAFE5V) { |
| /* src cap 0 should be vSafe5V */ |
| pdo_index = 0; |
| pdo = src_caps[0]; |
| } else { |
| /* find pdo index for max voltage we can request */ |
| pdo_index = pd_find_pdo_index(src_cap_cnt, src_caps, |
| max_request_mv, &pdo); |
| } |
| |
| pd_extract_pdo_power(pdo, ma, mv); |
| |
| /* |
| * Adjust VBUS current if CTVPD device was detected. |
| */ |
| if (vpd_vdo > 0) { |
| max_vbus = VPD_VDO_MAX_VBUS(vpd_vdo); |
| vpd_vbus_dcr = VPD_VDO_VBUS_IMP(vpd_vdo) << 1; |
| vpd_gnd_dcr = VPD_VDO_GND_IMP(vpd_vdo); |
| |
| /* |
| * Valid max_vbus values: |
| * 00b - 20000 mV |
| * 01b - 30000 mV |
| * 10b - 40000 mV |
| * 11b - 50000 mV |
| */ |
| max_vbus = 20000 + max_vbus * 10000; |
| if (*mv > max_vbus) |
| *mv = max_vbus; |
| |
| /* |
| * 5000 mA cable: 150 = 750000 / 50000 |
| * 3000 mA cable: 250 = 750000 / 30000 |
| */ |
| if (*ma > 3000) |
| *ma = 750000 / (150 + vpd_vbus_dcr + vpd_gnd_dcr); |
| else |
| *ma = 750000 / (250 + vpd_vbus_dcr + vpd_gnd_dcr); |
| } |
| |
| uw = *ma * *mv; |
| /* Mismatch bit set if less power offered than the operating power */ |
| if (uw < (1000 * PD_OPERATING_POWER_MW)) |
| flags |= RDO_CAP_MISMATCH; |
| |
| #ifdef CONFIG_USB_PD_GIVE_BACK |
| /* Tell source we are give back capable. */ |
| flags |= RDO_GIVE_BACK; |
| |
| /* |
| * BATTERY PDO: Inform the source that the sink will reduce |
| * power to this minimum level on receipt of a GotoMin Request. |
| */ |
| max_or_min_mw = PD_MIN_POWER_MW; |
| |
| /* |
| * FIXED or VARIABLE PDO: Inform the source that the sink will |
| * reduce current to this minimum level on receipt of a GotoMin |
| * Request. |
| */ |
| max_or_min_ma = PD_MIN_CURRENT_MA; |
| #else |
| /* |
| * Can't give back, so set maximum current and power to |
| * operating level. |
| */ |
| max_or_min_ma = *ma; |
| max_or_min_mw = uw / 1000; |
| #endif |
| |
| if ((pdo & PDO_TYPE_MASK) == PDO_TYPE_BATTERY) { |
| int mw = uw / 1000; |
| *rdo = RDO_BATT(pdo_index + 1, mw, max_or_min_mw, flags); |
| } else { |
| *rdo = RDO_FIXED(pdo_index + 1, *ma, max_or_min_ma, flags); |
| } |
| } |
| #endif |
| |
| #ifdef CONFIG_USB_PD_ALT_MODE_DFP |
| void notify_sysjump_ready(volatile const task_id_t * const sysjump_task_waiting) |
| { |
| /* |
| * 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, 0); |
| } |
| #endif |
| |
| __attribute__((weak)) uint8_t board_get_usb_pd_port_count(void) |
| { |
| return CONFIG_USB_PD_PORT_MAX_COUNT; |
| } |
| |
| enum pd_drp_next_states drp_auto_toggle_next_state( |
| uint64_t *drp_sink_time, |
| enum pd_power_role power_role, |
| enum pd_dual_role_states drp_state, |
| enum tcpc_cc_voltage_status cc1, |
| enum tcpc_cc_voltage_status cc2) |
| { |
| /* Set to appropriate port state */ |
| if (cc_is_open(cc1, cc2)) { |
| /* |
| * If nothing is attached then use drp_state to determine next |
| * state. If DRP auto toggle is still on, then remain in the |
| * DRP_AUTO_TOGGLE state. Otherwise, stop dual role toggling |
| * and go to a disconnected state. |
| */ |
| switch (drp_state) { |
| case PD_DRP_TOGGLE_OFF: |
| return DRP_TC_DEFAULT; |
| case PD_DRP_FREEZE: |
| if (power_role == PD_ROLE_SINK) |
| return DRP_TC_UNATTACHED_SNK; |
| else |
| return DRP_TC_UNATTACHED_SRC; |
| case PD_DRP_FORCE_SINK: |
| return DRP_TC_UNATTACHED_SNK; |
| case PD_DRP_FORCE_SOURCE: |
| return DRP_TC_UNATTACHED_SRC; |
| case PD_DRP_TOGGLE_ON: |
| default: |
| return DRP_TC_DRP_AUTO_TOGGLE; |
| } |
| } else if ((cc_is_rp(cc1) || cc_is_rp(cc2)) && |
| drp_state != PD_DRP_FORCE_SOURCE) { |
| /* SNK allowed unless ForceSRC */ |
| return DRP_TC_UNATTACHED_SNK; |
| } else if (cc_is_at_least_one_rd(cc1, cc2) || |
| cc_is_audio_acc(cc1, cc2)) { |
| /* |
| * SRC allowed unless ForceSNK or Toggle Off |
| * |
| * Ideally we wouldn't use auto-toggle when drp_state is |
| * TOGGLE_OFF/FORCE_SINK, but for some TCPCs, auto-toggle can't |
| * be prevented in low power mode. Try being a sink in case the |
| * connected device is dual-role (this ensures reliable charging |
| * from a hub, b/72007056). 100 ms is enough time for a |
| * dual-role partner to switch from sink to source. If the |
| * connected device is sink-only, then we will attempt |
| * TC_UNATTACHED_SNK twice (due to debounce time), then return |
| * to low power mode (and stay there). After 200 ms, reset |
| * ready for a new connection. |
| */ |
| if (drp_state == PD_DRP_TOGGLE_OFF || |
| drp_state == PD_DRP_FORCE_SINK) { |
| if (get_time().val > *drp_sink_time + 200*MSEC) |
| *drp_sink_time = get_time().val; |
| if (get_time().val < *drp_sink_time + 100*MSEC) |
| return DRP_TC_UNATTACHED_SNK; |
| else |
| return DRP_TC_DRP_AUTO_TOGGLE; |
| } else { |
| return DRP_TC_UNATTACHED_SRC; |
| } |
| } else { |
| /* Anything else, keep toggling */ |
| return DRP_TC_DRP_AUTO_TOGGLE; |
| } |
| } |
| |
| #ifdef CONFIG_USBC_PPC |
| |
| static void pd_send_hard_reset(int port) |
| { |
| task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_SEND_HARD_RESET, 0); |
| } |
| |
| static uint32_t port_oc_reset_req; |
| |
| static void re_enable_ports(void) |
| { |
| uint32_t ports = atomic_read_clear(&port_oc_reset_req); |
| |
| while (ports) { |
| int port = __fls(ports); |
| |
| ports &= ~BIT(port); |
| |
| /* |
| * Let the board know that the overcurrent is |
| * over since we're going to attempt re-enabling |
| * the port. |
| */ |
| board_overcurrent_event(port, 0); |
| |
| pd_send_hard_reset(port); |
| /* |
| * TODO(b/117854867): PD3.0 to send an alert message |
| * indicating OCP after explicit contract. |
| */ |
| } |
| } |
| DECLARE_DEFERRED(re_enable_ports); |
| |
| void pd_handle_overcurrent(int port) |
| { |
| /* Keep track of the overcurrent events. */ |
| CPRINTS("C%d: overcurrent!", port); |
| |
| if (IS_ENABLED(CONFIG_USB_PD_LOGGING)) |
| pd_log_event(PD_EVENT_PS_FAULT, PD_LOG_PORT_SIZE(port, 0), |
| PS_FAULT_OCP, NULL); |
| |
| ppc_add_oc_event(port); |
| /* Let the board specific code know about the OC event. */ |
| board_overcurrent_event(port, 1); |
| |
| /* Wait 1s before trying to re-enable the port. */ |
| atomic_or(&port_oc_reset_req, BIT(port)); |
| hook_call_deferred(&re_enable_ports_data, SECOND); |
| } |
| |
| void pd_handle_cc_overvoltage(int port) |
| { |
| pd_send_hard_reset(port); |
| } |
| |
| #endif /* CONFIG_USBC_PPC */ |