blob: 982acc74f92b5007733d2825dd4439c4642357ef [file] [log] [blame]
/* 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 "adc.h"
#include "atomic.h"
#include "battery.h"
#include "builtin/assert.h"
#include "charge_manager.h"
#include "charge_ramp.h"
#include "charge_state.h"
#include "charger.h"
#include "console.h"
#include "dps.h"
#include "extpower.h"
#include "gpio.h"
#include "hooks.h"
#include "host_command.h"
#include "system.h"
#include "task.h"
#include "tcpm/tcpm.h"
#include "timer.h"
#include "typec_control.h"
#include "usb_common.h"
#include "usb_pd.h"
#include "usb_pd_dpm_sm.h"
#include "usb_pd_tcpm.h"
#include "util.h"
#include "zephyr/include/usbc/pdc_dpm.h"
#ifdef CONFIG_ZEPHYR
#include "zephyr/include/usbc/pdc_power_mgmt.h"
#endif
#ifdef HAS_MOCK_CHARGE_MANAGER
#error Mock defined HAS_MOCK_CHARGE_MANAGER
#endif
#define CPRINTS(format, args...) cprints(CC_USBCHARGE, format, ##args)
#define POWER(charge_port) ((charge_port.current) * (charge_port.voltage))
/* Timeout for delayed override power swap, allow for 500ms extra */
#define POWER_SWAP_TIMEOUT \
(PD_T_SRC_RECOVER_MAX + PD_T_SRC_TURN_ON + PD_T_SAFE_0V + 500 * MSEC)
K_MUTEX_DEFINE(cm_refresh);
/*
* Default charge supplier priority
*
* - Always pick dedicated charge if present since that is the best product
* decision.
* - Pick PD negotiated chargers over everything else since they have the most
* power potential and they may not currently be negotiated at a high power.
* (and they can at least provide 15W)
* - Pick Type-C which supplier current >= 1.5A, which has higher prioirty
* than the BC1.2 and Type-C with current under 1.5A. (USB-C spec 1.3
* Table 4-17: TYPEC 3.0A, 1.5A > BC1.2 > TYPEC under 1.5A)
* - Then pick among the propreitary and BC1.2 chargers which ever has the
* highest available power.
* - Last, pick one from the rest suppliers. Also note that some boards assume
* wireless suppliers as low priority.
*/
__overridable const int supplier_priority[] = {
#if CONFIG_DEDICATED_CHARGE_PORT_COUNT > 0
[CHARGE_SUPPLIER_DEDICATED] = 0,
#endif
[CHARGE_SUPPLIER_PD] = 1,
[CHARGE_SUPPLIER_TYPEC] = 2,
[CHARGE_SUPPLIER_TYPEC_DTS] = 2,
#ifdef CONFIG_USB_CHARGER
[CHARGE_SUPPLIER_PROPRIETARY] = 3,
[CHARGE_SUPPLIER_BC12_DCP] = 3,
[CHARGE_SUPPLIER_BC12_CDP] = 3,
[CHARGE_SUPPLIER_BC12_SDP] = 3,
[CHARGE_SUPPLIER_TYPEC_UNDER_1_5A] = 4,
[CHARGE_SUPPLIER_OTHER] = 4,
[CHARGE_SUPPLIER_VBUS] = 4,
#endif
};
BUILD_ASSERT(ARRAY_SIZE(supplier_priority) == CHARGE_SUPPLIER_COUNT);
const char *charge_supplier_name[] = { CHARGE_SUPPLIER_NAME };
BUILD_ASSERT(ARRAY_SIZE(charge_supplier_name) == CHARGE_SUPPLIER_COUNT);
/* Keep track of available charge for each charge port. */
static struct charge_port_info available_charge[CHARGE_SUPPLIER_COUNT]
[CHARGE_PORT_COUNT];
/* Keep track of when the supplier on each port is registered. */
static timestamp_t registration_time[CHARGE_PORT_COUNT];
/*
* Charge current ceiling (mA) for ports. This can be set to temporarily limit
* the charge pulled from a port, without influencing the port selection logic.
* The ceiling can be set independently from several requestors, with the
* minimum ceiling taking effect.
*/
static int charge_ceil[CHARGE_PORT_COUNT][CEIL_REQUESTOR_COUNT];
/* Dual-role capability of attached device (e.g. AC adapter, mobile battery). */
static enum dualrole_capabilities dualrole_capability[CHARGE_PORT_COUNT];
#ifdef CONFIG_USB_PD_LOGGING
/* Mark port as dirty when making changes, for later logging */
static int save_log[CHARGE_PORT_COUNT];
#endif
/* Store current state of port enable / charge current. */
test_export_static int charge_port = CHARGE_PORT_NONE;
static int charge_current = CHARGE_CURRENT_UNINITIALIZED;
static int charge_current_uncapped = CHARGE_CURRENT_UNINITIALIZED;
static int charge_voltage;
static int charge_supplier = CHARGE_SUPPLIER_NONE;
static int charge_pd_current_uncapped = CHARGE_CURRENT_UNINITIALIZED;
static int override_port = OVERRIDE_OFF;
static int delayed_override_port = OVERRIDE_OFF;
static timestamp_t delayed_override_deadline;
/* Source-out Rp values for TCPMv1 */
__maybe_unused static uint8_t source_port_rp[CONFIG_USB_PD_PORT_MAX_COUNT];
#ifdef CONFIG_USB_PD_MAX_TOTAL_SOURCE_CURRENT
/* 3A on one port and 1.5A on the rest */
BUILD_ASSERT(CONFIG_USB_PD_PORT_MAX_COUNT * 1500 + 1500 <=
CONFIG_USB_PD_MAX_TOTAL_SOURCE_CURRENT);
#endif
/*
* charge_manager initially operates in safe mode until asked to leave (through
* charge_manager_leave_safe_mode()). While in safe mode, the following
* behavior is altered:
*
* 1) All chargers are considered dedicated (and thus are valid charge source
* candidates) for the purpose of port selection.
* 2) Charge ceilings are ignored. Most significantly, ILIM won't drop on PD
* voltage transition. If current load is high during transition, some
* chargers may brown-out.
* 3) CHARGE_PORT_NONE will not be selected (POR default charge port will
* remain selected rather than CHARGE_PORT_NONE).
*
* After leaving safe mode, charge_manager reverts to its normal behavior and
* immediately selects charge port and current using standard rules.
*/
#ifdef CONFIG_CHARGE_MANAGER_SAFE_MODE
static int left_safe_mode;
#else
static const int left_safe_mode = 1;
#endif
enum charge_manager_change_type {
CHANGE_CHARGE,
CHANGE_DUALROLE,
};
int is_pd_port(int port)
{
return port >= 0 && port < board_get_usb_pd_port_count();
}
static int is_sink(int port)
{
if (!is_pd_port(port))
return board_charge_port_is_sink(port);
return pd_get_power_role(port) == PD_ROLE_SINK;
}
/**
* Some of the SKUs in certain boards have less number of USB PD ports than
* defined in CONFIG_USB_PD_PORT_MAX_COUNT. With the charge port configuration
* for DEDICATED_PORT towards the end, this will lead to holes in the static
* configuration. The ports that fall in that hole are invalid and this function
* is used to check the validity of the ports.
*/
static int is_valid_port(int port)
{
if (port < 0 || port >= CHARGE_PORT_COUNT)
return 0;
/* Check if the port falls in the hole */
if (port >= board_get_usb_pd_port_count() &&
port < CONFIG_USB_PD_PORT_MAX_COUNT)
return 0;
return 1;
}
static bool is_valid_override_port(int port)
{
return (OVERRIDE_DONT_CHARGE <= port) && (port < CHARGE_PORT_COUNT);
}
static int is_connected(int port)
{
if (!is_pd_port(port))
return board_charge_port_is_connected(port);
return pd_is_connected(port);
}
#ifndef CONFIG_CHARGE_MANAGER_DRP_CHARGING
/**
* In certain cases we need to override the default behavior of not charging
* from non-dedicated chargers. If the system is in RO and locked, we have no
* way of determining the actual dualrole capability of the charger because
* PD communication is not allowed, so we must assume that it is dedicated.
* Also, if no battery is present, the charger may be our only source of power,
* so again we must assume that the charger is dedicated.
*
* @return 1 when we need to override the a non-dedicated charger
* to be a dedicated one, 0 otherwise.
*/
static int charge_manager_spoof_dualrole_capability(void)
{
return (system_get_image_copy() == EC_IMAGE_RO && system_is_locked()) ||
!left_safe_mode;
}
#endif /* !CONFIG_CHARGE_MANAGER_DRP_CHARGING */
/**
* Initialize available charge. Run before board init, so board init can
* initialize data, if needed.
*/
static void charge_manager_init(void)
{
int i, j;
for (i = 0; i < CHARGE_PORT_COUNT; ++i) {
if (!is_valid_port(i))
continue;
for (j = 0; j < CHARGE_SUPPLIER_COUNT; ++j) {
available_charge[j][i].current =
CHARGE_CURRENT_UNINITIALIZED;
available_charge[j][i].voltage =
CHARGE_VOLTAGE_UNINITIALIZED;
}
for (j = 0; j < CEIL_REQUESTOR_COUNT; ++j)
charge_ceil[i][j] = CHARGE_CEIL_NONE;
if (!is_pd_port(i))
dualrole_capability[i] = CAP_DEDICATED;
if (is_pd_port(i) && !IS_ENABLED(CONFIG_USB_PD_TCPMV2))
source_port_rp[i] = CONFIG_USB_PD_PULLUP;
}
}
#ifndef CONFIG_USB_PDC_POWER_MGMT
DECLARE_HOOK(HOOK_INIT, charge_manager_init, HOOK_PRIO_INIT_CHARGE_MANAGER);
#else
BUILD_ASSERT(CONFIG_CHARGE_MANAGER_SYS_INIT_PRIORITY <
CONFIG_PDC_POWER_MGMT_INIT_PRIORITY,
"The charge manager initialization must be higher priority than "
"the PDC power management");
/* When CONFIG_USB_PDC_POWER_MGMT is used, we need to init the
* charge manager before PDC power management subsystem.
*/
static int charge_manager_sys_init(void)
{
charge_manager_init();
return 0;
}
SYS_INIT(charge_manager_sys_init, POST_KERNEL,
CONFIG_CHARGE_MANAGER_SYS_INIT_PRIORITY);
#endif
/**
* Check if the charge manager is seeded.
*
* @return 1 if all ports/suppliers have reported
* with some initial charge, 0 otherwise.
*/
bool charge_manager_is_seeded(void)
{
/* Once we're seeded, we don't need to check again. */
static bool is_seeded;
int i, j;
if (is_seeded)
return is_seeded;
for (i = 0; i < CHARGE_SUPPLIER_COUNT; ++i) {
for (j = 0; j < CHARGE_PORT_COUNT; ++j) {
if (!is_valid_port(j))
continue;
if (available_charge[i][j].current ==
CHARGE_CURRENT_UNINITIALIZED ||
available_charge[i][j].voltage ==
CHARGE_VOLTAGE_UNINITIALIZED)
return 0;
}
}
is_seeded = true;
return is_seeded;
}
int charge_manager_get_pd_current_uncapped(void)
{
return charge_pd_current_uncapped;
}
/**
* Get the maximum charge current for a port.
*
* @param port Charge port.
* @return Charge current (mA).
*/
static int charge_manager_get_source_current(int port)
{
if (!is_pd_port(port))
return 0;
if (IS_ENABLED(CONFIG_USB_PD_TCPMV2)) {
/* TCPMv2 policy manager tracks sourcing levels. */
return dpm_get_source_current(port);
}
if (IS_ENABLED(CONFIG_USB_PDC_POWER_MGMT)) {
/* PDC policy manager tracks sourcing levels. */
return pdc_dpm_get_source_current(port);
}
switch (source_port_rp[port]) {
case TYPEC_RP_3A0:
return 3000;
case TYPEC_RP_1A5:
return 1500;
case TYPEC_RP_USB:
default:
return 500;
}
}
/*
* Find a supplier considering available current, voltage, power, and priority.
*/
static enum charge_supplier find_supplier(int port, enum charge_supplier sup,
int min_cur)
{
int i;
for (i = 0; i < CHARGE_SUPPLIER_COUNT; ++i) {
if (available_charge[i][port].current <= min_cur ||
available_charge[i][port].voltage <= 0)
/* Doesn't meet volt or current requirement. Skip it. */
continue;
if (sup == CHARGE_SUPPLIER_NONE)
/* Haven't found any yet. Take it unconditionally. */
sup = i;
else if (supplier_priority[sup] < supplier_priority[i])
/* There is already a higher priority supplier. */
continue;
else if (supplier_priority[i] < supplier_priority[sup])
/* This has a higher priority. Take it. */
sup = i;
else if (POWER(available_charge[i][port]) >
POWER(available_charge[sup][port]))
/* Priority is tie. Take it if power is higher. */
sup = i;
}
return sup;
}
static enum charge_supplier get_current_supplier(int port)
{
enum charge_supplier supplier = CHARGE_SUPPLIER_NONE;
/* Determine supplier information to show. */
if (port == charge_port) {
supplier = charge_supplier;
} else {
/* Consider available current */
supplier = find_supplier(port, supplier, 0);
if (supplier == CHARGE_SUPPLIER_NONE)
/* Ignore available current */
supplier = find_supplier(port, supplier, -1);
}
return supplier;
}
static enum usb_power_roles
get_current_power_role(int port, enum charge_supplier supplier)
{
enum usb_power_roles role;
if (charge_port == port)
role = USB_PD_PORT_POWER_SINK;
else if (is_connected(port) && !is_sink(port))
role = USB_PD_PORT_POWER_SOURCE;
else if (supplier != CHARGE_SUPPLIER_NONE)
role = USB_PD_PORT_POWER_SINK_NOT_CHARGING;
else
role = USB_PD_PORT_POWER_DISCONNECTED;
return role;
}
__overridable int board_get_vbus_voltage(int port)
{
return 0;
}
static int get_vbus_voltage(int port, enum usb_power_roles current_role)
{
int voltage_mv;
/*
* If we are sourcing power or sinking but not charging, then VBUS must
* be 5V. If we are charging, then read VBUS ADC.
*/
if (current_role == USB_PD_PORT_POWER_SINK_NOT_CHARGING) {
voltage_mv = 5000;
} else {
#if defined(CONFIG_USB_PD_VBUS_MEASURE_CHARGER)
/*
* Try to get VBUS from the charger. If that fails, default to 0
* mV.
*/
if (charger_get_vbus_voltage(port, &voltage_mv))
voltage_mv = 0;
#elif defined(CONFIG_USB_PD_VBUS_MEASURE_TCPC)
voltage_mv = tcpc_get_vbus_voltage(port);
#elif defined(CONFIG_USB_PD_VBUS_MEASURE_ADC_EACH_PORT)
voltage_mv = adc_read_channel(board_get_vbus_adc(port));
#elif defined(CONFIG_USB_PD_VBUS_MEASURE_NOT_PRESENT)
/* No VBUS ADC channel - voltage is unknown */
voltage_mv = 0;
#elif defined(CONFIG_USB_PD_VBUS_MEASURE_BY_BOARD)
voltage_mv = board_get_vbus_voltage(port);
#elif defined(CONFIG_USB_PD_VBUS_MEASURE_PDC)
voltage_mv = pdc_power_mgmt_get_vbus_voltage(port);
#else
/* There is a single ADC that measures joint Vbus */
voltage_mv = adc_read_channel(ADC_VBUS);
#endif
}
return voltage_mv;
}
int charge_manager_get_vbus_voltage(int port)
{
return get_vbus_voltage(
port, get_current_power_role(port, get_current_supplier(port)));
}
#ifdef CONFIG_CMD_VBUS
static int command_vbus(int argc, const char **argv)
{
/* port = -1 to print all the ports */
int port = -1;
int vbus, vsys;
if (argc == 2)
port = atoi(argv[1]);
ccprintf(" VBUS VSYS\n");
for (int i = 0; i < CHARGE_PORT_COUNT; i++) {
if (port < 0 || i == port) {
vbus = charge_manager_get_vbus_voltage(i);
if (charger_get_vsys_voltage(i, &vsys))
vsys = -1;
ccprintf(" P%d %6dmV ", i, vbus);
if (vsys >= 0)
ccprintf("%6dmV\n", vsys);
else
ccprintf("(unknown)\n");
}
}
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(vbus, command_vbus, "[port]",
"Print VBUS & VSYS of the given port");
#endif
/**
* Fills passed power_info structure with current info about the passed port.
*
* @param port Charge port.
* @param r USB PD power info to be updated.
*/
static void
charge_manager_fill_power_info(int port,
struct ec_response_usb_pd_power_info *r)
{
enum charge_supplier sup = get_current_supplier(port);
/* Fill in power role */
r->role = get_current_power_role(port, sup);
/* Is port partner dual-role capable */
r->dualrole = (dualrole_capability[port] == CAP_DUALROLE);
if (sup == CHARGE_SUPPLIER_NONE ||
r->role == USB_PD_PORT_POWER_SOURCE) {
if (is_pd_port(port)) {
r->type = USB_CHG_TYPE_NONE;
r->meas.voltage_max = 0;
r->meas.voltage_now =
r->role == USB_PD_PORT_POWER_SOURCE ? 5000 : 0;
r->meas.current_max =
charge_manager_get_source_current(port);
r->max_power = 0;
} else {
r->type = USB_CHG_TYPE_NONE;
board_fill_source_power_info(port, r);
}
} else {
uint32_t max_mv, max_ma, pdo, unused;
switch (sup) {
case CHARGE_SUPPLIER_PD:
r->type = USB_CHG_TYPE_PD;
break;
case CHARGE_SUPPLIER_TYPEC:
case CHARGE_SUPPLIER_TYPEC_DTS:
r->type = USB_CHG_TYPE_C;
break;
#ifdef CONFIG_USB_CHARGER
case CHARGE_SUPPLIER_PROPRIETARY:
r->type = USB_CHG_TYPE_PROPRIETARY;
break;
case CHARGE_SUPPLIER_BC12_DCP:
r->type = USB_CHG_TYPE_BC12_DCP;
break;
case CHARGE_SUPPLIER_BC12_CDP:
r->type = USB_CHG_TYPE_BC12_CDP;
break;
case CHARGE_SUPPLIER_BC12_SDP:
r->type = USB_CHG_TYPE_BC12_SDP;
break;
case CHARGE_SUPPLIER_VBUS:
r->type = USB_CHG_TYPE_VBUS;
break;
#endif
#if CONFIG_DEDICATED_CHARGE_PORT_COUNT > 0
case CHARGE_SUPPLIER_DEDICATED:
r->type = USB_CHG_TYPE_DEDICATED;
break;
#endif
default:
r->type = USB_CHG_TYPE_OTHER;
}
if (IS_ENABLED(CONFIG_USB_PD_DPS) && dps_is_enabled() &&
sup == CHARGE_SUPPLIER_PD) {
/*
* Returns the maximum power the system can request when
* DPS enabled. This is to prevent the system think it's
* using a low power charger.
*/
pd_select_best_pdo(pd_get_src_cap_cnt(port),
pd_get_src_caps(port),
pd_get_max_voltage(), &pdo);
pd_extract_pdo_power(pdo, &max_ma, &max_mv, &unused);
} else {
max_mv = available_charge[sup][port].voltage;
max_ma = available_charge[sup][port].current;
}
r->meas.voltage_max = max_mv;
/*
* Report unknown charger CHARGE_DETECT_DELAY after supplier
* change since PD negotiation may take time.
*
* Do not debounce on batteryless systems because
* USB_CHG_TYPE_UNKNOWN implies the system is still on battery
* while some kind of negotiation happens, but by the time the
* host might request this in a battery-free configuration we
* must be stable (if not, the system is either up or about to
* lose power again).
*/
#ifdef CONFIG_BATTERY
if (get_time().val <
registration_time[port].val + CHARGE_DETECT_DELAY)
r->type = USB_CHG_TYPE_UNKNOWN;
#endif
#if defined(HAS_TASK_CHG_RAMP) || defined(CONFIG_CHARGE_RAMP_HW)
if ((charge_port == port) && chg_ramp_allowed(port, sup)) {
/* Current limit is output of ramp module */
r->meas.current_lim = chg_ramp_get_current_limit();
/*
* If ramp is allowed, then the max current depends
* on if ramp is stable. If ramp is stable, then
* max current is same as input current limit. If
* ramp is not stable, then we report the maximum
* current we could ramp up to for this supplier.
* If ramp is not allowed, max current is just the
* available charge current.
*/
r->meas.current_max =
chg_ramp_is_stable() ?
r->meas.current_lim :
chg_ramp_max(port, sup, max_ma);
} else {
r->meas.current_max = r->meas.current_lim = max_ma;
}
#else
r->meas.current_max = r->meas.current_lim = max_ma;
#endif
r->max_power = r->meas.current_max * r->meas.voltage_max;
r->meas.voltage_now = get_vbus_voltage(port, r->role);
}
}
#ifdef CONFIG_USB_PD_LOGGING
/**
* Saves a power state log entry with the current info about the passed port.
*/
void charge_manager_save_log(int port)
{
uint16_t flags = 0;
struct ec_response_usb_pd_power_info pinfo;
if (!is_pd_port(port))
return;
save_log[port] = 0;
charge_manager_fill_power_info(port, &pinfo);
/* Flags are stored in the data field */
if (port == override_port)
flags |= CHARGE_FLAGS_OVERRIDE;
if (port == delayed_override_port)
flags |= CHARGE_FLAGS_DELAYED_OVERRIDE;
flags |= pinfo.role | (pinfo.type << CHARGE_FLAGS_TYPE_SHIFT) |
(pinfo.dualrole ? CHARGE_FLAGS_DUAL_ROLE : 0);
pd_log_event(PD_EVENT_MCU_CHARGE,
PD_LOG_PORT_SIZE(port, sizeof(pinfo.meas)), flags,
&pinfo.meas);
}
#endif /* CONFIG_USB_PD_LOGGING */
/**
* Attempt to switch to power source on port if applicable.
*
* @param port USB-C port to be swapped.
*/
static void charge_manager_switch_to_source(int port)
{
if (!is_pd_port(port))
return;
/* If connected to dual-role device, then ask for a swap */
if (dualrole_capability[port] == CAP_DUALROLE && is_sink(port))
pd_request_power_swap(port);
}
/**
* Return the computed charge ceiling for a port, which represents the
* minimum ceiling among all valid requestors.
*
* @param port Charge port.
* @return Charge ceiling (mA) or CHARGE_CEIL_NONE.
*/
static int charge_manager_get_ceil(int port)
{
int ceil = CHARGE_CEIL_NONE;
int val, i;
if (!is_valid_port(port))
return ceil;
for (i = 0; i < CEIL_REQUESTOR_COUNT; ++i) {
val = charge_ceil[port][i];
if (val != CHARGE_CEIL_NONE &&
(ceil == CHARGE_CEIL_NONE || val < ceil))
ceil = val;
}
return ceil;
}
static int get_pd_port_max_power(int port)
{
uint32_t pdo, max_voltage, max_current, unused;
pd_select_best_pdo(pd_get_src_cap_cnt(port), pd_get_src_caps(port),
pd_get_max_voltage(), &pdo);
pd_extract_pdo_power(pdo, &max_current, &max_voltage, &unused);
return max_current * max_voltage;
}
static int get_candidate_port_power(int supplier, int port)
{
int candidate_port_power = POWER(available_charge[supplier][port]);
/* Check if PD port can provide more than negotiated PDC port.
* This can happen in dead battery scenarios. */
if (IS_ENABLED(CONFIG_USB_PDC_POWER_MGMT) && is_pd_port(port)) {
candidate_port_power =
MAX(get_pd_port_max_power(port), candidate_port_power);
}
return candidate_port_power;
}
/**
* Select the best charge port or the override port, as defined by the supplier
* hierarchy and the available power.
*
* @param new_port Pointer to the selected port.
* @param new_supplier Pointer to the selected supplier.
*/
static void charge_manager_get_best_port(int *new_port, int *new_supplier)
{
int supplier = CHARGE_SUPPLIER_NONE;
int port = CHARGE_PORT_NONE;
int best_port_power = -1, candidate_port_power;
int i, j;
if (override_port == OVERRIDE_DONT_CHARGE) {
*new_port = CHARGE_PORT_NONE;
*new_supplier = CHARGE_SUPPLIER_NONE;
return;
}
/*
* Charge supplier selection logic:
* 1. Prefer DPS charge port.
* 2. Prefer higher priority supply.
* 3. Prefer higher power over lower in case priority is tied.
* 4. Prefer current charge port over new port in case (1)
* and (2) are tied.
* available_charge can be changed at any time by other tasks,
* so make no assumptions about its consistency.
*/
for (i = 0; i < CHARGE_SUPPLIER_COUNT; ++i) {
for (j = 0; j < CHARGE_PORT_COUNT; ++j) {
/* Skip this port if it is not valid. */
if (!is_valid_port(j))
continue;
/*
* Skip this supplier if there is no
* available charge.
*/
if (available_charge[i][j].current == 0 ||
available_charge[i][j].voltage == 0)
continue;
/*
* Don't select this port if we have a
* charge on another override port.
*/
if (override_port != OVERRIDE_OFF &&
override_port == port && override_port != j)
continue;
#ifndef CONFIG_CHARGE_MANAGER_DRP_CHARGING
/*
* Don't charge from a dual-role port unless
* it is our override port.
*/
if (dualrole_capability[j] != CAP_DEDICATED &&
override_port != j &&
!charge_manager_spoof_dualrole_capability())
continue;
#endif
candidate_port_power = get_candidate_port_power(i, j);
/* Select DPS port if provided. */
if (IS_ENABLED(CONFIG_USB_PD_DPS) &&
override_port == OVERRIDE_OFF &&
i == CHARGE_SUPPLIER_PD &&
j == dps_get_charge_port()) {
supplier = i;
port = j;
break;
/* Select if no supplier chosen yet. */
} else if (supplier == CHARGE_SUPPLIER_NONE ||
/* ..or if supplier priority is higher. */
supplier_priority[i] <
supplier_priority[supplier] ||
/* ..or if this is our override port. */
(j == override_port &&
port != override_port) ||
/* ..or if priority is tied and.. */
(supplier_priority[i] ==
supplier_priority[supplier] &&
/*
* candidate port can supply more power or
* ..
*/
(candidate_port_power > best_port_power ||
/*
* candidate port is the active
* port and can supply the same
* amount of power.
*/
(candidate_port_power == best_port_power &&
charge_port == j)))) {
supplier = i;
port = j;
best_port_power = candidate_port_power;
}
}
}
#ifdef CONFIG_BATTERY
/*
* if no battery present then retain same charge port
* and charge supplier to avoid the port switching
*/
if (charge_port != CHARGE_PORT_NONE && charge_port != port &&
(battery_is_present() == BP_NO ||
battery_is_present() == BP_NOT_SURE ||
(battery_is_present() == BP_YES &&
battery_is_cut_off() != BATTERY_CUTOFF_STATE_NORMAL))) {
port = charge_port;
supplier = charge_supplier;
}
#endif
*new_port = port;
*new_supplier = supplier;
}
/**
* Charge manager refresh -- responsible for selecting the active charge port
* and charge power. Called as a deferred task.
*/
static void charge_manager_refresh(void)
{
/* Always initialize charge port on first pass */
static int active_charge_port_initialized;
int new_supplier, new_port;
int new_charge_current, new_charge_current_uncapped;
int new_charge_voltage, i;
int updated_new_port = CHARGE_PORT_NONE;
int updated_old_port = CHARGE_PORT_NONE;
int ceil;
int power_changed = 0;
mutex_lock(&cm_refresh);
/* Hunt for an acceptable charge port */
while (1) {
charge_manager_get_best_port(&new_port, &new_supplier);
if (!left_safe_mode && new_port == CHARGE_PORT_NONE) {
mutex_unlock(&cm_refresh);
return;
}
/*
* If the port and the supplier are the same, don't (attempt to)
* switch to the port unless active charge port hasn't been set.
*/
if (active_charge_port_initialized && new_port == charge_port &&
new_supplier == charge_supplier)
break;
/*
* For OCPC systems, reset the OCPC state to prevent current
* spikes.
*/
if (IS_ENABLED(CONFIG_OCPC)) {
charge_set_active_chg_chip(new_port);
trigger_ocpc_reset();
}
/*
* A different port or a supplier was selected. Make an attempt
* to switch to the port.
*/
if (board_set_active_charge_port(new_port) == EC_SUCCESS) {
if (IS_ENABLED(CONFIG_EXTPOWER))
board_check_extpower();
break;
}
/* 'Dont charge' request must be accepted. */
ASSERT(new_port != CHARGE_PORT_NONE);
/*
* The board rejected the offered port & supplier. Clear the
* available charge on the rejected port so that it is no longer
* chosen.
*/
for (i = 0; i < CHARGE_SUPPLIER_COUNT; ++i) {
available_charge[i][new_port].current = 0;
available_charge[i][new_port].voltage = 0;
}
}
active_charge_port_initialized = 1;
/*
* Clear override if it wasn't selected -- it means that no charge is
* available on the port, or the port was rejected.
*/
if (override_port >= 0 && override_port != new_port)
override_port = OVERRIDE_OFF;
if (new_supplier == CHARGE_SUPPLIER_NONE) {
#ifdef CONFIG_CHARGER_DEFAULT_CURRENT_LIMIT
new_charge_current = CONFIG_CHARGER_DEFAULT_CURRENT_LIMIT;
#else
new_charge_current = 0;
#endif
new_charge_current_uncapped = new_charge_current;
new_charge_voltage = 0;
} else {
new_charge_current_uncapped =
available_charge[new_supplier][new_port].current;
#ifdef CONFIG_CHARGE_RAMP_HW
/*
* Allow to set the maximum current value, so the hardware can
* know the range of acceptable current values for its ramping.
*/
if (chg_ramp_allowed(new_port, new_supplier))
new_charge_current_uncapped =
chg_ramp_max(new_port, new_supplier,
new_charge_current_uncapped);
#endif /* CONFIG_CHARGE_RAMP_HW */
/* Enforce port charge ceiling. */
ceil = charge_manager_get_ceil(new_port);
if (left_safe_mode && ceil != CHARGE_CEIL_NONE)
new_charge_current =
MIN(ceil, new_charge_current_uncapped);
else
new_charge_current = new_charge_current_uncapped;
new_charge_voltage =
available_charge[new_supplier][new_port].voltage;
}
/*
* Record the PD current limit to prevent from over-sinking
* the charger.
*/
if (new_supplier == CHARGE_SUPPLIER_PD)
charge_pd_current_uncapped = new_charge_current_uncapped;
else
charge_pd_current_uncapped = CHARGE_CURRENT_UNINITIALIZED;
/* Change the charge limit + charge port/supplier if modified. */
if (new_port != charge_port || new_charge_current != charge_current ||
new_supplier != charge_supplier) {
#ifdef HAS_TASK_CHG_RAMP
chg_ramp_charge_supplier_change(new_port, new_supplier,
new_charge_current,
registration_time[new_port],
new_charge_voltage);
#else
#ifdef CONFIG_CHARGE_RAMP_HW
/* Enable or disable charge ramp */
charger_set_hw_ramp(chg_ramp_allowed(new_port, new_supplier));
#endif
board_set_charge_limit(new_port, new_supplier,
new_charge_current,
new_charge_current_uncapped,
new_charge_voltage);
#endif /* HAS_TASK_CHG_RAMP */
power_changed = 1;
CPRINTS("CL: p%d s%d i%d v%d", new_port, new_supplier,
new_charge_current, new_charge_voltage);
/*
* (b:192638664) We try to check AC OK again to avoid
* unsuccessful detection in the initial detection.
*/
if (IS_ENABLED(CONFIG_EXTPOWER))
board_check_extpower();
}
/*
* Signal new power request only if the port changed, the voltage
* on the same port changed, or the actual uncapped current
* on the same port changed (don't consider ceil).
*/
if (new_port != CHARGE_PORT_NONE &&
(new_port != charge_port ||
new_charge_current_uncapped != charge_current_uncapped ||
new_charge_voltage != charge_voltage))
updated_new_port = new_port;
/* If charge port changed, cleanup old port */
if (charge_port != new_port && charge_port != CHARGE_PORT_NONE) {
/* Check if need power swap */
charge_manager_switch_to_source(charge_port);
/* Signal new power request on old port */
updated_old_port = charge_port;
}
/* Update globals to reflect current state. */
charge_current = new_charge_current;
charge_current_uncapped = new_charge_current_uncapped;
charge_voltage = new_charge_voltage;
charge_supplier = new_supplier;
charge_port = new_port;
#ifdef CONFIG_USB_PD_LOGGING
/*
* Write a log under the following conditions:
* 1. A port becomes active or
* 2. A port becomes inactive or
* 3. The active charge port power limit changes or
* 4. Any supplier change on an inactive port
*/
if (updated_new_port != CHARGE_PORT_NONE)
save_log[updated_new_port] = 1;
/* Don't log non-meaningful changes on charge port */
else if (charge_port != CHARGE_PORT_NONE)
save_log[charge_port] = 0;
if (updated_old_port != CHARGE_PORT_NONE)
save_log[updated_old_port] = 1;
for (i = 0; i < board_get_usb_pd_port_count(); ++i)
if (save_log[i])
charge_manager_save_log(i);
#endif
/* New power requests must be set only after updating the globals. */
if (is_pd_port(updated_new_port)) {
/* Check if we can get requested voltage/current */
if ((IS_ENABLED(CONFIG_USB_PD_TCPMV1) &&
IS_ENABLED(CONFIG_USB_PD_DUAL_ROLE)) ||
(IS_ENABLED(CONFIG_USB_PD_TCPMV2) &&
IS_ENABLED(CONFIG_USB_PE_SM)) ||
IS_ENABLED(CONFIG_USB_PDC_POWER_MGMT)) {
uint32_t pdo;
uint32_t max_voltage;
uint32_t max_current;
uint32_t unused;
bool new_req = false;
/*
* Check if new voltage/current is different
* than requested. If yes, send new power request
*/
if (pd_get_requested_voltage(updated_new_port) !=
charge_voltage ||
pd_get_requested_current(updated_new_port) !=
charge_current_uncapped)
new_req = true;
if (IS_ENABLED(CONFIG_USB_PD_DPS) && dps_is_enabled()) {
/* Fall-through. DPS control sink voltage */
} else {
/*
* Check if we can get more power from this
* port. If yes, send new power request
*/
pd_select_best_pdo(
pd_get_src_cap_cnt(updated_new_port),
pd_get_src_caps(updated_new_port),
pd_get_max_voltage(), &pdo);
pd_extract_pdo_power(pdo, &max_current,
&max_voltage, &unused);
if (charge_voltage != max_voltage ||
charge_current_uncapped != max_current)
new_req = true;
}
if (new_req)
pd_set_new_power_request(updated_new_port);
} else {
/*
* Functions for getting requested voltage/current
* are not available. Send new power request.
*/
pd_set_new_power_request(updated_new_port);
}
}
if (is_pd_port(updated_old_port))
pd_set_new_power_request(updated_old_port);
if (power_changed) {
hook_notify(HOOK_POWER_SUPPLY_CHANGE);
/* notify host of power info change */
pd_send_host_event(PD_EVENT_POWER_CHANGE);
}
mutex_unlock(&cm_refresh);
}
DECLARE_DEFERRED(charge_manager_refresh);
/**
* Called when charge override times out waiting for power swap.
*/
static void charge_override_timeout(void)
{
delayed_override_port = OVERRIDE_OFF;
pd_send_host_event(PD_EVENT_POWER_CHANGE);
}
DECLARE_DEFERRED(charge_override_timeout);
/**
* Called CHARGE_DETECT_DELAY after the most recent charge change on a port.
*/
static void charger_detect_debounced(void)
{
/* Inform host that charger detection is debounced. */
pd_send_host_event(PD_EVENT_POWER_CHANGE);
}
DECLARE_DEFERRED(charger_detect_debounced);
/**
* Update charge parameters for a given port / supplier.
*
* @param change Type of change.
* @param supplier Charge supplier to be updated.
* @param port Charge port to be updated.
* @param charge Charge port current / voltage.
*/
static void charge_manager_make_change(enum charge_manager_change_type change,
int supplier, int port,
const struct charge_port_info *charge)
{
int i;
int clear_override = 0;
if (!is_valid_port(port)) {
CPRINTS("%s: p%d invalid", __func__, port);
return;
}
/* Determine if this is a change which can affect charge status */
switch (change) {
case CHANGE_CHARGE:
/* Ignore changes where charge is identical */
if (available_charge[supplier][port].current ==
charge->current &&
available_charge[supplier][port].voltage == charge->voltage)
return;
if (charge->current > 0 &&
available_charge[supplier][port].current == 0)
/* A charger is plugged. Clear override. */
clear_override = 1;
#ifdef CONFIG_USB_PD_LOGGING
save_log[port] = 1;
#endif
break;
case CHANGE_DUALROLE:
/*
* Ignore all except for transition to non-dualrole,
* which may occur some time after we see a charge
*/
#ifndef CONFIG_CHARGE_MANAGER_DRP_CHARGING
if (dualrole_capability[port] != CAP_DEDICATED)
#endif
return;
/* Clear override only if a charge is present on the port */
for (i = 0; i < CHARGE_SUPPLIER_COUNT; ++i)
if (available_charge[i][port].current > 0) {
clear_override = 1;
break;
}
/*
* If there is no charge present on the port, the dualrole
* change is meaningless to charge_manager.
*/
if (!clear_override)
return;
break;
}
/* Remove override when a charger is plugged */
if (clear_override && override_port != port &&
override_port != OVERRIDE_DONT_CHARGE
#ifndef CONFIG_CHARGE_MANAGER_DRP_CHARGING
/* only remove override when it's a dedicated charger */
&& dualrole_capability[port] == CAP_DEDICATED
#endif
) {
override_port = OVERRIDE_OFF;
if (delayed_override_port != OVERRIDE_OFF) {
delayed_override_port = OVERRIDE_OFF;
hook_call_deferred(&charge_override_timeout_data, -1);
}
}
if (change == CHANGE_CHARGE) {
available_charge[supplier][port].current = charge->current;
available_charge[supplier][port].voltage = charge->voltage;
registration_time[port] = get_time();
/*
* After CHARGE_DETECT_DELAY, inform the host that charger
* detection has been debounced. Since only one deferred
* routine exists for all ports, the deferred call for a given
* port may potentially be cancelled. This is mostly harmless
* since cancellation implies that PD_EVENT_POWER_CHANGE was
* just sent due to the power change on another port.
*/
if (charge->current > 0)
hook_call_deferred(&charger_detect_debounced_data,
CHARGE_DETECT_DELAY);
/*
* If we have a charge on our delayed override port within
* the deadline, make it our override port.
*/
if (port == delayed_override_port && charge->current > 0 &&
is_sink(delayed_override_port) &&
get_time().val < delayed_override_deadline.val) {
delayed_override_port = OVERRIDE_OFF;
hook_call_deferred(&charge_override_timeout_data, -1);
charge_manager_set_override(port);
}
}
/*
* Don't call charge_manager_refresh unless all ports +
* suppliers have reported in. We don't want to make changes
* to our charge port until we are certain we know what is
* attached.
*/
if (charge_manager_is_seeded())
hook_call_deferred(&charge_manager_refresh_data, 0);
}
void pd_set_input_current_limit(int port, uint32_t max_ma,
uint32_t supply_voltage)
{
struct charge_port_info charge;
charge.current = max_ma;
charge.voltage = supply_voltage;
charge_manager_update_charge(CHARGE_SUPPLIER_PD, port, &charge);
}
void typec_set_input_current_limit(int port, typec_current_t max_ma,
uint32_t supply_voltage)
{
struct charge_port_info charge;
int i;
int supplier;
int dts = !!(max_ma & TYPEC_CURRENT_DTS_MASK);
static const enum charge_supplier typec_suppliers[] = {
CHARGE_SUPPLIER_TYPEC,
CHARGE_SUPPLIER_TYPEC_DTS,
#ifdef CONFIG_USB_CHARGER
CHARGE_SUPPLIER_TYPEC_UNDER_1_5A,
#endif /* CONFIG_USB_CHARGER */
};
charge.current = max_ma & TYPEC_CURRENT_ILIM_MASK;
charge.voltage = supply_voltage;
#if !defined(HAS_TASK_CHG_RAMP) && !defined(CONFIG_CHARGE_RAMP_HW)
/*
* DTS sources such as suzy-q may not be able to actually deliver
* their advertised current, so limit it to reduce chance of OC,
* if we can't ramp.
*/
if (dts)
charge.current = MIN(charge.current, 500);
#endif
supplier = dts ? CHARGE_SUPPLIER_TYPEC_DTS : CHARGE_SUPPLIER_TYPEC;
#ifdef CONFIG_USB_CHARGER
/*
* According to USB-C spec 1.3 Table 4-17 "Precedence of power source
* usage", the priority should be: USB-C 3.0A, 1.5A > BC1.2 > USB-C
* under 1.5A. Choosed the corresponding supplier type, according to
* charge current, to update.
*/
if (charge.current < 1500)
supplier = CHARGE_SUPPLIER_TYPEC_UNDER_1_5A;
#endif /* CONFIG_USB_CHARGER */
charge_manager_update_charge(supplier, port, &charge);
/*
* TYPEC / TYPEC-DTS / TYPEC-UNDER_1_5A should be mutually exclusive.
* Zero'ing all the other suppliers.
*/
for (i = 0; i < ARRAY_SIZE(typec_suppliers); ++i)
if (supplier != typec_suppliers[i])
charge_manager_update_charge(typec_suppliers[i], port,
NULL);
}
test_mockable void
charge_manager_update_charge(int supplier, int port,
const struct charge_port_info *charge)
{
struct charge_port_info zero = { 0 };
if (!charge)
charge = &zero;
charge_manager_make_change(CHANGE_CHARGE, supplier, port, charge);
}
void charge_manager_update_dualrole(int port, enum dualrole_capabilities cap)
{
if (!is_pd_port(port))
return;
/* Ignore when capability is unchanged */
if (cap != dualrole_capability[port]) {
dualrole_capability[port] = cap;
charge_manager_make_change(CHANGE_DUALROLE, 0, port, NULL);
}
}
#ifdef CONFIG_CHARGE_MANAGER_SAFE_MODE
__overridable int board_get_leave_safe_mode_delay_ms(void)
{
return 500;
}
void charge_manager_leave_safe_mode(void)
{
if (left_safe_mode)
return;
/*
* Sometimes the fuel gauge will report that it has
* sufficient state of charge and remaining capacity,
* but in actuality it doesn't. When the EC sees that
* information, it trusts it and leaves charge manager
* safe mode. Doing so will allow CHARGE_PORT_NONE to
* be selected, thereby cutting off the input FETs.
* When the battery cannot provide the charge it claims,
* the system loses power, shuts down, and the battery
* is not charged even though the charger is plugged in.
* By waiting 500ms, we can avoid the selection of
* CHARGE_PORT_NONE around init time and not cut off the
* input FETs.
*/
crec_msleep(board_get_leave_safe_mode_delay_ms());
CPRINTS("%s()", __func__);
left_safe_mode = 1;
if (charge_manager_is_seeded())
hook_call_deferred(&charge_manager_refresh_data, 0);
}
#endif
void charge_manager_set_ceil(int port, enum ceil_requestor requestor, int ceil)
{
if (!is_valid_port(port))
return;
mutex_lock(&cm_refresh);
if (charge_ceil[port][requestor] != ceil) {
charge_ceil[port][requestor] = ceil;
if (port == charge_port && charge_manager_is_seeded())
hook_call_deferred(&charge_manager_refresh_data, 0);
}
mutex_unlock(&cm_refresh);
}
void charge_manager_force_ceil(int port, int ceil)
{
/*
* Force our input current to ceil if we're exceeding it, without
* waiting for our deferred task to run.
*/
if (left_safe_mode && port == charge_port && ceil < charge_current) {
board_set_charge_limit(port, CHARGE_SUPPLIER_PD, ceil,
charge_current_uncapped, charge_voltage);
/* Enforcing charge_ceil here prevents race conditions between
* the hook task and the PD task, which could occur if
* charge_ceil is changed while a deferred
* charge_manager_refresh is in process.
* charge_manager_force_ceil's premise as an API is to lower
* the ceil for the current charge port. It doesn't need to find
* a better port, supplier, or PDO. That means most of
* charge_manager_refresh doesn't need to be called.
*/
charge_current = ceil;
charge_ceil[port][CEIL_REQUESTOR_PD] = ceil;
CPRINTS("CL: p%d s%d i%d v%d (forced)", port,
CHARGE_SUPPLIER_PD, charge_current, charge_voltage);
} else {
/*
* Inform charge_manager so it stays in sync with the state
* of the world.
*/
charge_manager_set_ceil(port, CEIL_REQUESTOR_PD, ceil);
}
}
int charge_manager_set_override(int port)
{
int retval = EC_SUCCESS;
CPRINTS("Charge Override: %d", port);
/*
* If attempting to change the override port, then return
* error. Since we may be in the middle of a power swap on
* the original override port, it's too complicated to
* guarantee that the original override port is switched back
* to source.
*/
if (delayed_override_port != OVERRIDE_OFF)
return EC_ERROR_BUSY;
/* Set the override port if it's a sink. */
if (port < 0 || is_sink(port)) {
if (override_port != port) {
override_port = port;
if (charge_manager_is_seeded())
hook_call_deferred(&charge_manager_refresh_data,
0);
}
}
/*
* If the attached device is capable of being a sink, request a
* power swap and set the delayed override for swap completion.
*/
else if (!is_sink(port) && dualrole_capability[port] == CAP_DUALROLE) {
delayed_override_deadline.val =
get_time().val + POWER_SWAP_TIMEOUT;
delayed_override_port = port;
hook_call_deferred(&charge_override_timeout_data,
POWER_SWAP_TIMEOUT);
pd_request_power_swap(port);
/* Can't charge from requested port -- return error. */
} else
retval = EC_ERROR_INVAL;
return retval;
}
int charge_manager_get_override(void)
{
return override_port;
}
int charge_manager_get_active_charge_port(void)
{
return charge_port;
}
int charge_manager_get_selected_charge_port(void)
{
int port, supplier;
charge_manager_get_best_port(&port, &supplier);
return port;
}
int charge_manager_get_charger_current(void)
{
return charge_current;
}
int charge_manager_get_charger_voltage(void)
{
return charge_voltage;
}
enum charge_supplier charge_manager_get_supplier(void)
{
return charge_supplier;
}
void charge_manager_set_supplier(int port, enum charge_supplier supplier)
{
if (charge_supplier != CHARGE_SUPPLIER_NONE ||
charge_port != CHARGE_PORT_NONE) {
return;
}
CPRINTS("Seeding initial charge supplier, port %d, supplier %d", port,
supplier);
charge_port = port;
charge_supplier = supplier;
}
int charge_manager_get_power_limit_uw(void)
{
int current_ma = charge_current;
int voltage_mv = charge_voltage;
if (current_ma == CHARGE_CURRENT_UNINITIALIZED ||
voltage_mv == CHARGE_VOLTAGE_UNINITIALIZED)
return 0;
else
return current_ma * voltage_mv;
}
#if defined(CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT) && \
!defined(CONFIG_USB_PD_TCPMV2)
/* Note: this functionality is a part of the TCPMv2 Device Poicy Manager */
/* Bitmap of ports used as power source */
static volatile uint32_t source_port_bitmap;
BUILD_ASSERT(sizeof(source_port_bitmap) * 8 >= CONFIG_USB_PD_PORT_MAX_COUNT);
static inline int has_other_active_source(int port)
{
return source_port_bitmap & ~BIT(port);
}
static inline int is_active_source(int port)
{
return source_port_bitmap & BIT(port);
}
static int can_supply_max_current(int port)
{
#ifdef CONFIG_USB_PD_MAX_TOTAL_SOURCE_CURRENT
/*
* This guarantees active 3A source continues to supply 3A.
*
* Since redistribution occurs sequentially, younger ports get
* priority. Priority surfaces only when 3A source is released.
* That is, when 3A source is released, the youngest active
* port gets 3A.
*/
int p;
if (!is_active_source(port))
/* Non-active ports don't get 3A */
return 0;
for (p = 0; p < board_get_usb_pd_port_count(); p++) {
if (p == port)
continue;
if (source_port_rp[p] ==
CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT)
return 0;
}
return 1;
#else
return is_active_source(port) && !has_other_active_source(port);
#endif /* CONFIG_USB_PD_MAX_TOTAL_SOURCE_CURRENT */
}
void charge_manager_source_port(int port, int enable)
{
uint32_t prev_bitmap = source_port_bitmap;
int p, rp;
if (enable)
atomic_or((atomic_t *)&source_port_bitmap, 1 << port);
else
atomic_clear_bits((atomic_t *)&source_port_bitmap, 1 << port);
/* No change, exit early. */
if (prev_bitmap == source_port_bitmap)
return;
/* Set port limit according to policy */
for (p = 0; p < board_get_usb_pd_port_count(); p++) {
rp = can_supply_max_current(p) ?
CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT :
CONFIG_USB_PD_PULLUP;
source_port_rp[p] = rp;
#ifdef CONFIG_USB_PD_LOGGING
if (is_connected(p) && !is_sink(p))
charge_manager_save_log(p);
#endif
typec_set_source_current_limit(p, rp);
if (IS_ENABLED(CONFIG_USB_PD_TCPMV2))
typec_select_src_current_limit_rp(p, rp);
else
tcpm_select_rp_value(p, rp);
pd_update_contract(p);
}
}
int charge_manager_get_source_pdo(const uint32_t **src_pdo, const int port)
{
if (can_supply_max_current(port)) {
*src_pdo = pd_src_pdo_max;
return pd_src_pdo_max_cnt;
}
*src_pdo = pd_src_pdo;
return pd_src_pdo_cnt;
}
#endif /* CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT && !CONFIG_USB_PD_TCPMV2 */
static enum ec_status hc_pd_power_info(struct host_cmd_handler_args *args)
{
const struct ec_params_usb_pd_power_info *p = args->params;
struct ec_response_usb_pd_power_info *r = args->response;
int port = p->port;
/* If host is asking for the charging port, set port appropriately */
if (port == PD_POWER_CHARGING_PORT)
port = charge_port;
/*
* Not checking for invalid port here, because it might break existing
* contract with ectool users. The invalid ports will have the response
* voltage, current and power parameters set to 0.
*/
if (port >= CHARGE_PORT_COUNT)
return EC_RES_INVALID_PARAM;
charge_manager_fill_power_info(port, r);
args->response_size = sizeof(*r);
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_USB_PD_POWER_INFO, hc_pd_power_info,
EC_VER_MASK(0));
static enum ec_status hc_charge_port_count(struct host_cmd_handler_args *args)
{
struct ec_response_charge_port_count *resp = args->response;
args->response_size = sizeof(*resp);
resp->port_count = CHARGE_PORT_COUNT;
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_CHARGE_PORT_COUNT, hc_charge_port_count,
EC_VER_MASK(0));
static enum ec_status
hc_charge_port_override(struct host_cmd_handler_args *args)
{
const struct ec_params_charge_port_override *p = args->params;
const int16_t op = p->override_port;
if (!is_valid_override_port(op))
return EC_RES_INVALID_PARAM;
return charge_manager_set_override(op) == EC_SUCCESS ? EC_RES_SUCCESS :
EC_RES_ERROR;
}
DECLARE_HOST_COMMAND(EC_CMD_PD_CHARGE_PORT_OVERRIDE, hc_charge_port_override,
EC_VER_MASK(0));
#if CONFIG_DEDICATED_CHARGE_PORT_COUNT > 0
static enum ec_status
hc_override_dedicated_charger_limit(struct host_cmd_handler_args *args)
{
const struct ec_params_dedicated_charger_limit *p = args->params;
struct charge_port_info ci = {
.current = p->current_lim,
.voltage = p->voltage_lim,
};
/*
* Allow a change only if the dedicated charge port is used. Host needs
* to apply a change every time a dedicated charger is plugged.
*/
if (charge_port != DEDICATED_CHARGE_PORT)
return EC_RES_UNAVAILABLE;
charge_manager_update_charge(CHARGE_SUPPLIER_DEDICATED,
DEDICATED_CHARGE_PORT, &ci);
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_OVERRIDE_DEDICATED_CHARGER_LIMIT,
hc_override_dedicated_charger_limit, EC_VER_MASK(0));
#endif
static int command_charge_port_override(int argc, const char **argv)
{
int port = OVERRIDE_OFF;
int ret = EC_SUCCESS;
char *e;
if (argc >= 2) {
port = strtoi(argv[1], &e, 0);
if (*e || !is_valid_override_port(port))
return EC_ERROR_PARAM1;
ret = charge_manager_set_override(port);
}
ccprintf("Override: %d\n",
(argc >= 2 && ret == EC_SUCCESS) ? port : override_port);
return ret;
}
DECLARE_CONSOLE_COMMAND(
chgoverride, command_charge_port_override, "[port | -1 | -2]",
"Force charging from a given port (-1 = off, -2 = disable charging)");
#ifdef CONFIG_CHARGE_MANAGER_EXTERNAL_POWER_LIMIT
static void charge_manager_set_external_power_limit(int current_lim,
int voltage_lim)
{
int port;
if (current_lim == EC_POWER_LIMIT_NONE)
current_lim = CHARGE_CEIL_NONE;
if (voltage_lim == EC_POWER_LIMIT_NONE)
voltage_lim = CONFIG_USB_PD_MAX_VOLTAGE_MV;
for (port = 0; port < board_get_usb_pd_port_count(); ++port) {
charge_manager_set_ceil(port, CEIL_REQUESTOR_HOST, current_lim);
pd_set_external_voltage_limit(port, voltage_lim);
}
}
/*
* On transition out of S0, disable all external power limits, in case AP
* failed to clear them.
*/
static void charge_manager_external_power_limit_off(void)
{
charge_manager_set_external_power_limit(EC_POWER_LIMIT_NONE,
EC_POWER_LIMIT_NONE);
}
DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, charge_manager_external_power_limit_off,
HOOK_PRIO_DEFAULT);
static enum ec_status
hc_external_power_limit(struct host_cmd_handler_args *args)
{
const struct ec_params_external_power_limit_v1 *p = args->params;
charge_manager_set_external_power_limit(p->current_lim, p->voltage_lim);
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_EXTERNAL_POWER_LIMIT, hc_external_power_limit,
EC_VER_MASK(1));
static int command_external_power_limit(int argc, const char **argv)
{
int max_current;
int max_voltage;
char *e;
if (argc >= 2) {
max_current = strtoi(argv[1], &e, 10);
if (*e)
return EC_ERROR_PARAM1;
} else
max_current = EC_POWER_LIMIT_NONE;
if (argc >= 3) {
max_voltage = strtoi(argv[2], &e, 10);
if (*e)
return EC_ERROR_PARAM1;
} else
max_voltage = EC_POWER_LIMIT_NONE;
charge_manager_set_external_power_limit(max_current, max_voltage);
ccprintf("max req: %dmA %dmV\n", max_current, max_voltage);
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(chglim, command_external_power_limit,
"[max_current (mA)] [max_voltage (mV)]",
"Set max charger current / voltage");
#endif /* CONFIG_CHARGE_MANAGER_EXTERNAL_POWER_LIMIT */
#ifdef CONFIG_CMD_CHARGE_SUPPLIER_INFO
static int charge_supplier_info(int argc, const char **argv)
{
int p, s;
int port_printed;
ccprintf("\n");
ccprintf("Port --Supplier-- Prio -Available Power-\n");
for (p = 0; p < CHARGE_PORT_COUNT; p++) {
port_printed = 0;
for (s = 0; s < CHARGE_SUPPLIER_COUNT; s++) {
if (available_charge[s][p].current == 0 &&
available_charge[s][p].voltage == 0)
continue;
if (charge_manager_get_active_charge_port() == p &&
charge_manager_get_supplier() == s)
ccprintf("*");
else
ccprintf(" ");
if (!port_printed) {
ccprintf("P%d ", p);
port_printed = 1;
} else {
ccprintf(" ");
}
ccprintf("%-10s %4d %5dmA %5dmV\n",
charge_supplier_name[s], supplier_priority[s],
available_charge[s][p].current,
available_charge[s][p].voltage);
}
}
ccprintf("\n");
ccprintf(" %s safe mode\n", left_safe_mode ? "Left" : "In");
ccprintf(" Override port = P%d\n", charge_manager_get_override());
ccprintf("\n");
return 0;
}
DECLARE_CONSOLE_COMMAND(chgsup, charge_supplier_info, NULL,
"print chg supplier info");
#endif
__overridable int board_charge_port_is_sink(int port)
{
return 1;
}
__overridable int board_charge_port_is_connected(int port)
{
return 1;
}
__overridable void
board_fill_source_power_info(int port, struct ec_response_usb_pd_power_info *r)
{
r->meas.voltage_now = 0;
r->meas.voltage_max = 0;
r->meas.current_max = 0;
r->meas.current_lim = 0;
r->max_power = 0;
}
__overridable void board_set_charge_limit(int port, int supplier, int charge_ma,
int max_ma, int charge_mv)
{
#if defined(CONFIG_CHARGER) && defined(CONFIG_BATTERY)
charge_set_input_current_limit(charge_ma, charge_mv);
#endif
}
OSZAR »