/* 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. * * Dual Role (Source & Sink) USB-PD module. */ #include "charge_manager.h" #include "charge_state.h" #include "dps.h" #include "system.h" #include "usb_common.h" #include "usb_pd.h" #include "util.h" #define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) /* The macro is used to prevent a DBZ exception while decoding PDOs. */ #define PROCESS_ZERO_DIVISOR(x) ((x) == 0 ? 1 : (x)) #if defined(PD_MAX_VOLTAGE_MV) && defined(PD_OPERATING_POWER_MW) /* * As a sink, this is the max voltage (in millivolts) we can request * before getting source caps */ static unsigned int max_request_mv = PD_MAX_VOLTAGE_MV; /* TODO(b:169532537): deprecate CONFIG_USB_PD_PREFER_MV */ STATIC_IF_NOT(CONFIG_USB_PD_PREFER_MV) struct pd_pref_config_t __maybe_unused pd_pref_config; void pd_set_max_voltage(unsigned int mv) { max_request_mv = mv; } unsigned int pd_get_max_voltage(void) { return max_request_mv; } /* * 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. */ 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 has_preferred_pdo; int prefer_cur; int desired_uw = 0; const int prefer_mv = pd_pref_config.mv; const int type = pd_pref_config.type; int __attribute__((unused)) cur_mv = 0; if (IS_ENABLED(CONFIG_USB_PD_PREFER_MV)) desired_uw = charge_get_plt_plus_bat_desired_mw() * 1000; /* 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++) { if (IS_ENABLED(CONFIG_USB_PD_ONLY_FIXED_PDOS) && (src_caps[i] & PDO_TYPE_MASK) != PDO_TYPE_FIXED) continue; /* 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 favor of voltage */ 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; } else if (IS_ENABLED(CONFIG_USB_PD_PREFER_MV)) { /* Pick if the PDO provides more than desired. */ if (uw >= desired_uw) { /* pick if cur_uw is less than desired watt */ if (cur_uw < desired_uw) prefer_cur = 1; else if (type == PD_PREFER_BUCK) { /* * pick the smallest mV above prefer_mv */ if (mv >= prefer_mv && mv < cur_mv) prefer_cur = 1; /* * pick if cur_mv is less than * prefer_mv, and we have higher mV */ else if (cur_mv < prefer_mv && mv > cur_mv) prefer_cur = 1; } else if (type == PD_PREFER_BOOST) { /* * pick the largest mV below prefer_mv */ if (mv <= prefer_mv && mv > cur_mv) prefer_cur = 1; /* * pick if cur_mv is larger than * prefer_mv, and we have lower mV */ else if (cur_mv > prefer_mv && mv < cur_mv) prefer_cur = 1; } /* * pick the largest power if we don't see one staisfy * desired power */ } else if (cur_uw == 0 || uw > cur_uw) { prefer_cur = 1; } } /* Prefer higher power, except for tiebreaker */ has_preferred_pdo = prefer_cur || (!IS_ENABLED(CONFIG_USB_PD_PREFER_MV) && uw > cur_uw); if (has_preferred_pdo) { 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 *max_mv, uint32_t *min_mv) { int max_ma, mw; if ((pdo & PDO_TYPE_MASK) == PDO_TYPE_FIXED) { *max_mv = PDO_FIXED_VOLTAGE(pdo); *min_mv = *max_mv; } else if ((pdo & PDO_TYPE_MASK) == PDO_TYPE_AUGMENTED) { *max_mv = PDO_AUG_MAX_VOLTAGE(pdo); *min_mv = PDO_AUG_MIN_VOLTAGE(pdo); } else if ((pdo & PDO_TYPE_MASK) == PDO_TYPE_VARIABLE) { *max_mv = PDO_VAR_MAX_VOLTAGE(pdo); *min_mv = PDO_VAR_MIN_VOLTAGE(pdo); } else { *max_mv = PDO_BATT_MAX_VOLTAGE(pdo); *min_mv = PDO_BATT_MIN_VOLTAGE(pdo); } if (*max_mv == 0) { *ma = 0; *min_mv = 0; return; } if ((pdo & PDO_TYPE_MASK) == PDO_TYPE_FIXED) { max_ma = PDO_FIXED_CURRENT(pdo); } else if ((pdo & PDO_TYPE_MASK) == PDO_TYPE_AUGMENTED) { max_ma = PDO_AUG_MAX_CURRENT(pdo); } else if ((pdo & PDO_TYPE_MASK) == PDO_TYPE_VARIABLE) { max_ma = PDO_VAR_MAX_CURRENT(pdo); } else { mw = PDO_BATT_MAX_POWER(pdo); max_ma = 1000 * mw / PROCESS_ZERO_DIVISOR(*min_mv); } max_ma = MIN(max_ma, PD_MAX_POWER_MW * 1000 / PROCESS_ZERO_DIVISOR(*min_mv)); *ma = MIN(max_ma, PD_MAX_CURRENT_MA); } void pd_build_request(int32_t vpd_vdo, uint32_t *rdo, uint32_t *ma, uint32_t *mv, int port) { 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; uint32_t src_cap_cnt = pd_get_src_cap_cnt(port); const uint32_t * const src_caps = pd_get_src_caps(port); int charging_allowed; int max_request_allowed; uint32_t max_request_mv = pd_get_max_voltage(); uint32_t unused; /* * If this port is the current charge port, or if there isn't an active * charge port, set this value to true. If CHARGE_PORT_NONE isn't * considered, then there can be a race condition in PD negotiation and * the charge manager which forces an incorrect request for * vSafe5V. This can then lead to a brownout condition when the input * current limit gets incorrectly set to 0.5A. */ if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) { int chg_port = charge_manager_get_selected_charge_port(); charging_allowed = (chg_port == port || chg_port == CHARGE_PORT_NONE); } else { charging_allowed = 1; } if (IS_ENABLED(CONFIG_USB_PD_CHECK_MAX_REQUEST_ALLOWED)) max_request_allowed = pd_is_max_request_allowed(); else max_request_allowed = 1; if (IS_ENABLED(CONFIG_USB_PD_DPS) && dps_is_enabled()) max_request_mv = MIN(max_request_mv, dps_get_dynamic_voltage()); /* * If currently charging on a different port, or we are not allowed to * request the max voltage, then select vSafe5V */ if (charging_allowed && max_request_allowed) { /* find pdo index for max voltage we can request */ pdo_index = pd_find_pdo_index(src_cap_cnt, src_caps, max_request_mv, &pdo); } else { /* src cap 0 should be vSafe5V */ pdo_index = 0; pdo = src_caps[0]; } pd_extract_pdo_power(pdo, ma, mv, &unused); /* * 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); } /* * Ref: USB Power Delivery Specification * (Revision 3.0, Version 2.0 / Revision 2.0, Version 1.3) * 6.4.2.4 USB Communications Capable * 6.4.2.5 No USB Suspend * * If the port partner is capable of USB communication set the * USB Communications Capable flag. * If the port partner is sink device do not suspend USB as the * power can be used for charging. */ if (pd_get_partner_usb_comm_capable(port)) { *rdo |= RDO_COMM_CAP; if (pd_get_power_role(port) == PD_ROLE_SINK) *rdo |= RDO_NO_SUSPEND; } } void pd_process_source_cap(int port, int cnt, uint32_t *src_caps) { pd_set_src_caps(port, cnt, src_caps); if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) { uint32_t ma, mv, pdo, unused; uint32_t max_mv = pd_get_max_voltage(); if (IS_ENABLED(CONFIG_USB_PD_DPS) && dps_is_enabled()) max_mv = MIN(max_mv, dps_get_dynamic_voltage()); /* Get max power info that we could request */ pd_find_pdo_index(pd_get_src_cap_cnt(port), pd_get_src_caps(port), max_mv, &pdo); pd_extract_pdo_power(pdo, &ma, &mv, &unused); /* Set max. limit, but apply 500mA ceiling */ charge_manager_set_ceil(port, CEIL_REQUESTOR_PD, PD_MIN_MA); pd_set_input_current_limit(port, ma, mv); } } #endif /* defined(PD_MAX_VOLTAGE_MV) && defined(PD_OPERATING_POWER_MW) */ bool pd_is_battery_capable(void) { bool capable; /* Battery is present and at some minimum percentage. */ capable = (usb_get_battery_soc() >= CONFIG_USB_PD_TRY_SRC_MIN_BATT_SOC); #ifdef CONFIG_BATTERY_REVIVE_DISCONNECT /* * Not capable if the battery is in the disconnect state. The discharge * FET may not be enabled and so attempting being a SRC may cut off * our only power source at the time. */ capable &= (battery_get_disconnect_state() == BATTERY_NOT_DISCONNECTED); #elif defined(CONFIG_BATTERY_PRESENT_CUSTOM) || \ defined(CONFIG_BATTERY_PRESENT_GPIO) /* * When battery is cutoff in ship mode it may not be reliable to * check if battery is present with its state of charge. * Also check if battery is initialized and ready to provide power. */ capable &= (battery_is_present() == BP_YES); #endif /* CONFIG_BATTERY_PRESENT_[CUSTOM|GPIO] */ return capable; } #ifdef CONFIG_USB_PD_TRY_SRC bool pd_is_try_source_capable(void) { int i; uint8_t try_src = 0; bool new_try_src; for (i = 0; i < board_get_usb_pd_port_count(); i++) try_src |= (pd_get_dual_role(i) == PD_DRP_TOGGLE_ON); /* * Enable try source when dual-role toggling AND battery is capable * of powering the whole system. */ new_try_src = (try_src && pd_is_battery_capable()); #if CONFIG_DEDICATED_CHARGE_PORT_COUNT > 0 /* * If a dedicated supplier is present, power is not a concern and * therefore allow Try.Src if we're toggling. */ new_try_src = try_src && (charge_manager_get_supplier() == CHARGE_SUPPLIER_DEDICATED); #endif /* CONFIG_DEDICATED_CHARGE_PORT_COUNT */ return new_try_src; } #endif /* CONFIG_USB_PD_TRY_SRC */ static int get_bbram_idx(uint8_t port) { if (port < MAX_SYSTEM_BBRAM_IDX_PD_PORTS) return (port + SYSTEM_BBRAM_IDX_PD0); return -1; } int pd_get_saved_port_flags(int port, uint8_t *flags) { if (system_get_bbram(get_bbram_idx(port), flags) != EC_SUCCESS) { #ifndef CHIP_HOST ccprintf("PD NVRAM FAIL"); #endif return EC_ERROR_UNKNOWN; } return EC_SUCCESS; } static void pd_set_saved_port_flags(int port, uint8_t flags) { if (system_set_bbram(get_bbram_idx(port), flags) != EC_SUCCESS) { #ifndef CHIP_HOST ccprintf("PD NVRAM FAIL"); #endif } } void pd_update_saved_port_flags(int port, uint8_t flag, uint8_t do_set) { uint8_t saved_flags; if (pd_get_saved_port_flags(port, &saved_flags) != EC_SUCCESS) return; if (do_set) saved_flags |= flag; else saved_flags &= ~flag; pd_set_saved_port_flags(port, saved_flags); }