Files
OpenCellular/common/charge_manager.c
Daisuke Nojiri 443207913b charge_manager: Give dedicated chargers highest priority
This patch lowers the priorities of other chargers so that dedicated
chargers are given the highest priority.

Dedicated chargers are adapters shipped with the device and do nothing
but being the power source. Thus, when they're connected, it should be
always used even if other suppliers offer more power.

BUG=b:38321259
BRANCH=none
TEST=make runtests

Change-Id: Ida862eb31c7f9ede36cade99f53645196b6a3f70
Signed-off-by: Daisuke Nojiri <dnojiri@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/762339
Reviewed-by: Shawn N <shawnn@chromium.org>
2017-11-28 15:44:18 -08:00

1242 lines
35 KiB
C

/* Copyright (c) 2014 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.
*/
#include "adc.h"
#include "atomic.h"
#include "battery.h"
#include "charge_manager.h"
#include "charge_ramp.h"
#include "charger.h"
#include "console.h"
#include "gpio.h"
#include "hooks.h"
#include "host_command.h"
#include "system.h"
#include "tcpm.h"
#include "timer.h"
#include "usb_pd.h"
#include "usb_pd_tcpm.h"
#include "util.h"
#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)
/* Charge supplier priority: lower number indicates higher priority. */
test_mockable 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 CHARGE_MANAGER_BC12
[CHARGE_SUPPLIER_PROPRIETARY] = 2,
[CHARGE_SUPPLIER_BC12_DCP] = 3,
[CHARGE_SUPPLIER_BC12_CDP] = 4,
[CHARGE_SUPPLIER_BC12_SDP] = 5,
[CHARGE_SUPPLIER_OTHER] = 6,
[CHARGE_SUPPLIER_VBUS] = 7
#endif
};
BUILD_ASSERT(ARRAY_SIZE(supplier_priority) == 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 partner port */
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. */
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 override_port = OVERRIDE_OFF;
static int delayed_override_port = OVERRIDE_OFF;
static timestamp_t delayed_override_deadline;
/* 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_COUNT);
static uint8_t source_port_last_rp[CONFIG_USB_PD_PORT_COUNT];
/*
* 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,
};
static int is_pd_port(int port)
{
return 0 <= port && port < CONFIG_USB_PD_PORT_COUNT;
}
static int is_sink(int port)
{
if (!is_pd_port(port))
/* Dedicated port is sink-only */
return 1;
return pd_get_role(port) == PD_ROLE_SINK;
}
#ifndef TEST_BUILD
static int is_connected(int port)
{
if (!is_pd_port(port))
/* Dedicated port is always connected */
return 1;
return pd_is_connected(port);
}
#endif /* !TEST_BUILD */
#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() == SYSTEM_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) {
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))
source_port_last_rp[i] = CONFIG_USB_PD_PULLUP;
}
}
DECLARE_HOOK(HOOK_INIT, charge_manager_init, HOOK_PRIO_CHARGE_MANAGER_INIT);
/**
* Check if the charge manager is seeded.
*
* @return 1 if all ports/suppliers have reported
* with some initial charge, 0 otherwise.
*/
static int charge_manager_is_seeded(void)
{
/* Once we're seeded, we don't need to check again. */
static int is_seeded;
int i, j;
if (is_seeded)
return 1;
for (i = 0; i < CHARGE_SUPPLIER_COUNT; ++i)
for (j = 0; j < CHARGE_PORT_COUNT; ++j)
if (available_charge[i][j].current ==
CHARGE_CURRENT_UNINITIALIZED ||
available_charge[i][j].voltage ==
CHARGE_VOLTAGE_UNINITIALIZED)
return 0;
is_seeded = 1;
return 1;
}
#ifndef TEST_BUILD
/**
* 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)
{
ASSERT(is_pd_port(port));
switch (source_port_last_rp[port]) {
case TYPEC_RP_3A0:
return 3000;
case TYPEC_RP_1A5:
return 1500;
case TYPEC_RP_USB:
default:
return 500;
}
}
/**
* 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)
{
int sup = CHARGE_SUPPLIER_NONE;
int i;
/* Determine supplier information to show. */
if (port == charge_port)
sup = charge_supplier;
else
/* Find highest priority supplier */
for (i = 0; i < CHARGE_SUPPLIER_COUNT; ++i)
if (available_charge[i][port].current > 0 &&
available_charge[i][port].voltage > 0 &&
(sup == CHARGE_SUPPLIER_NONE ||
supplier_priority[i] <
supplier_priority[sup] ||
(supplier_priority[i] ==
supplier_priority[sup] &&
POWER(available_charge[i][port]) >
POWER(available_charge[sup][port]))))
sup = i;
/* Fill in power role */
if (charge_port == port)
r->role = USB_PD_PORT_POWER_SINK;
else if (is_connected(port) && !is_sink(port))
r->role = USB_PD_PORT_POWER_SOURCE;
else if (sup != CHARGE_SUPPLIER_NONE)
r->role = USB_PD_PORT_POWER_SINK_NOT_CHARGING;
else
r->role = USB_PD_PORT_POWER_DISCONNECTED;
/* 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) {
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 {
#if defined(HAS_TASK_CHG_RAMP) || defined(CONFIG_CHARGE_RAMP_HW)
/* Read ramped current if active charging port */
int use_ramp_current = (charge_port == port);
#else
const int use_ramp_current = 0;
#endif
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 CHARGE_MANAGER_BC12
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;
}
r->meas.voltage_max = available_charge[sup][port].voltage;
/*
* Report unknown charger CHARGE_DETECT_DELAY after supplier
* change since PD negotiation may take time.
*/
if (get_time().val < registration_time[port].val +
CHARGE_DETECT_DELAY)
r->type = USB_CHG_TYPE_UNKNOWN;
if (use_ramp_current) {
/* 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.
*/
if (chg_ramp_allowed(sup)) {
r->meas.current_max = chg_ramp_is_stable() ?
r->meas.current_lim :
chg_ramp_max(
sup,
available_charge[sup][port].current);
} else {
r->meas.current_max =
available_charge[sup][port].current;
}
r->max_power =
r->meas.current_max * r->meas.voltage_max;
} else {
r->meas.current_max = r->meas.current_lim =
available_charge[sup][port].current;
r->max_power = POWER(available_charge[sup][port]);
}
/*
* If we are sourcing power, or sinking but not charging, then
* VBUS must be 5V. If we are charging, then read VBUS ADC.
*/
if (r->role == USB_PD_PORT_POWER_SINK_NOT_CHARGING)
r->meas.voltage_now = 5000;
else {
#ifdef CONFIG_USB_PD_VBUS_MEASURE_CHARGER
r->meas.voltage_now = charger_get_vbus_voltage(port);
#else
if (ADC_VBUS >= 0)
r->meas.voltage_now =
adc_read_channel(ADC_VBUS);
else
/* No VBUS ADC channel - voltage is unknown */
r->meas.voltage_now = 0;
#endif
}
}
}
#endif /* TEST_BUILD */
#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;
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;
}
/**
* Select the 'best' charge port, as defined by the supplier heirarchy and the
* ability of the port to provide power.
*
* @param new_port Pointer to the best charge port by definition.
* @param new_supplier Pointer to the best charge supplier by definition.
*/
static void charge_manager_get_best_charge_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;
/* Skip port selection on OVERRIDE_DONT_CHARGE. */
if (override_port != OVERRIDE_DONT_CHARGE) {
/*
* Charge supplier selection logic:
* 1. Prefer higher priority supply.
* 2. Prefer higher power over lower in case priority is tied.
* 3. 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 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 =
POWER(available_charge[i][j]);
/* Select if no supplier chosen yet. */
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;
}
}
}
*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;
/* Hunt for an acceptable charge port */
while (1) {
charge_manager_get_best_charge_port(&new_port, &new_supplier);
if (!left_safe_mode && new_port == CHARGE_PORT_NONE)
return;
/*
* If the port or supplier changed, make an attempt to switch to
* the port. We will re-set the active port on a supplier change
* to give the board-level function another chance to reject
* the port, for example, if the port has become a charge
* source.
*/
if ((active_charge_port_initialized &&
new_port == charge_port &&
new_supplier == charge_supplier) ||
board_set_active_charge_port(new_port) == EC_SUCCESS)
break;
/* 'Dont charge' request must be accepted. */
ASSERT(new_port != CHARGE_PORT_NONE);
/*
* Zero 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;
}
active_charge_port_initialized = 1;
/*
* Clear override if it wasn't selected as the 'best' port -- 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) {
new_charge_current = 0;
new_charge_current_uncapped = 0;
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_supplier))
new_charge_current_uncapped =
chg_ramp_max(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;
}
/* 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_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);
}
/*
* 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 < CONFIG_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))
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)
/* notify host of power info change */
pd_send_host_event(PD_EVENT_POWER_CHANGE);
}
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,
struct charge_port_info *charge)
{
int i;
int clear_override = 0;
/* 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)
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
#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 dts = !!(max_ma & TYPEC_CURRENT_DTS_MASK);
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
charge_manager_update_charge(dts ? CHARGE_SUPPLIER_TYPEC_DTS :
CHARGE_SUPPLIER_TYPEC,
port, &charge);
/*
* Zero TYPEC / TYPEC-DTS when zero'ing the other, since they are
* mutually exclusive and DTS status of port partner will no longer
* be reflected on disconnect.
*/
if (max_ma == 0 || supply_voltage == 0)
charge_manager_update_charge(dts ? CHARGE_SUPPLIER_TYPEC :
CHARGE_SUPPLIER_TYPEC_DTS,
port, &charge);
}
void charge_manager_update_charge(int supplier,
int port,
struct charge_port_info *charge)
{
charge_manager_make_change(CHANGE_CHARGE, supplier, port, charge);
}
void charge_manager_update_dualrole(int port, enum dualrole_capabilities cap)
{
/* 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
void charge_manager_leave_safe_mode(void)
{
if (left_safe_mode)
return;
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 (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);
}
}
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);
/*
* Now 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_charger_current(void)
{
return charge_current;
}
int charge_manager_get_charger_voltage(void)
{
return charge_voltage;
}
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;
}
#ifdef CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT
void charge_manager_source_port(int port, int enable)
{
uint32_t prev_bitmap = source_port_bitmap;
int p;
if (enable)
atomic_or(&source_port_bitmap, 1 << port);
else
atomic_clear(&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 < CONFIG_USB_PD_PORT_COUNT; p++) {
/*
* if we are the only active source port or there is none,
* advertise all the available power.
*/
int rp = (source_port_bitmap & ~(1 << p)) ? CONFIG_USB_PD_PULLUP
: CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT;
source_port_last_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);
tcpm_select_rp_value(p, rp);
pd_update_contract(p);
}
}
int charge_manager_get_source_pdo(const uint32_t **src_pdo, const int port)
{
/* Are there any other connected sinks? */
if (source_port_bitmap & ~(1 << port)) {
*src_pdo = pd_src_pdo;
return pd_src_pdo_cnt;
}
/*
* If not, send the maximum current since we're sourcing on only one
* port.
*/
*src_pdo = pd_src_pdo_max;
return pd_src_pdo_max_cnt;
}
#endif /* CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT */
#ifndef TEST_BUILD
static int 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;
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));
#endif /* TEST_BUILD */
static int hc_charge_port_override(struct host_cmd_handler_args *args)
{
const struct ec_params_charge_port_override *p = args->params;
const int16_t override_port = p->override_port;
if (override_port < OVERRIDE_DONT_CHARGE ||
override_port >= CHARGE_PORT_COUNT)
return EC_RES_INVALID_PARAM;
return charge_manager_set_override(override_port) == 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 int 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, char **argv)
{
int port = OVERRIDE_OFF;
int ret = EC_SUCCESS;
char *e;
if (argc >= 2) {
port = strtoi(argv[1], &e, 0);
if (*e || port < OVERRIDE_DONT_CHARGE ||
port >= CHARGE_PORT_COUNT)
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 = PD_MAX_VOLTAGE_MV;
for (port = 0; port < CONFIG_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 int 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, 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, char **argv)
{
ccprintf("port=%d, type=%d, cur=%dmA, vtg=%dmV, lsm=%d\n",
charge_manager_get_active_charge_port(),
charge_supplier,
charge_current,
charge_voltage,
left_safe_mode);
return 0;
}
DECLARE_CONSOLE_COMMAND(chgsup, charge_supplier_info,
NULL, "print chg supplier info");
#endif