Files
OpenCellular/common/charge_manager.c
Alec Berg 6dce2424d9 charge_manager: modify PD power HC to give more info on current
Change the EC_CMD_USB_PD_POWER_INFO host command to report the input
current limit and the max current theoretically possible given the
charger. The input current limit field is useful for logging purposes
and the max current field is useful to give to powerd to determine
if we have a low power charger connected.

The max current is determined by checking if the charge supplier is
allowed to ramp. If the charge supplier is allowed to ramp and has
not completed ramping yet, then max current is the max current that
we are allowed to ramp up to. Once the ramp has completed, then max
current is the stable charging current. If the charge supplier is not
allowed to ramp, then max current is simply the max current as
registered with charge_manager. The point here is to keep the max as
high as possible until we know for sure it is lower to avoid showing
the user the low power notification until we know for sure.

This CL also adds a new charger type, USB_CHG_TYPE_UNKNOWN. For a short
period after a charger is plugged in, the supplier type may change and
PD negotiation is still in process, and therefore we tell the host we
have an unknown charger type. This allows powerd to show the charging
icon, but delay determining if this is a low power charger until we
know for sure.

BUG=chrome-os-partner:38548
BRANCH=samus
TEST=tested with zinger, a DCP, an SDP, and a proprietary charger.
tested that low power notification never pops up with zinger, even
if you purposely wedge charge circuit with "charger voltage 7000" on
EC console. tested that the other chargers all pop up low power
notification once the supplier changes from UNKNOWN to the real
supplier. used "ectool --name=cros_pd usbpdpower" to check current
values.

Change-Id: If8a9a1799504cc2a13238f4e6ec917d25d972b22
Signed-off-by: Alec Berg <alecaberg@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/265066
Reviewed-by: Sameer Nanda <snanda@chromium.org>
2015-05-01 03:44:19 +00:00

781 lines
22 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 "charge_manager.h"
#include "charge_ramp.h"
#include "console.h"
#include "gpio.h"
#include "hooks.h"
#include "host_command.h"
#include "system.h"
#include "timer.h"
#include "usb_pd.h"
#include "usb_pd_config.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)
/* Keep track of available charge for each charge port. */
static struct charge_port_info available_charge[CHARGE_SUPPLIER_COUNT]
[PD_PORT_COUNT];
/* Keep track of when the supplier on each port is registered. */
static timestamp_t registration_time[PD_PORT_COUNT];
/*
* Charge ceiling for ports. This can be set to temporarily limit the charge
* pulled from a port, without influencing the port selection logic.
*/
static int charge_ceil[PD_PORT_COUNT];
/* Dual-role capability of attached partner port */
static enum dualrole_capabilities dualrole_capability[PD_PORT_COUNT];
#ifdef CONFIG_USB_PD_LOGGING
/* Mark port as dirty when making changes, for later logging */
static int save_log[PD_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;
enum charge_manager_change_type {
CHANGE_CHARGE,
CHANGE_DUALROLE,
};
/**
* 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 < PD_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;
}
charge_ceil[i] = CHARGE_CEIL_NONE;
dualrole_capability[i] = CAP_UNKNOWN;
}
}
DECLARE_HOOK(HOOK_INIT, charge_manager_init, HOOK_PRIO_DEFAULT-1);
/**
* Returns 1 if all ports + suppliers have reported in 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 < PD_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_CHARGE_MANAGER
/**
* Fills passed power_info structure with current info about the passed port.
*/
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 (pd_is_connected(port) && pd_get_role(port) == PD_ROLE_SOURCE)
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 = 0;
r->max_power = 0;
} else {
#ifdef HAS_TASK_CHG_RAMP
/* 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:
r->type = USB_CHG_TYPE_C;
break;
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;
default:
r->type = USB_CHG_TYPE_OTHER;
}
r->meas.voltage_max = available_charge[sup][port].voltage;
if (use_ramp_current) {
/*
* If charge_ramp has not detected charger yet,
* then charger type is unknown.
*/
if (!chg_ramp_is_detected())
r->type = USB_CHG_TYPE_UNKNOWN;
/* 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 (board_is_ramp_allowed(sup)) {
r->meas.current_max = chg_ramp_is_stable() ?
r->meas.current_lim :
board_get_ramp_current_limit(
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
r->meas.voltage_now = adc_read_channel(ADC_VBUS);
}
}
#endif /* TEST_CHARGE_MANAGER */
#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 (port < 0 || port >= PD_PORT_COUNT)
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 */
/**
* Perform cleanup operations on an override port, when switching to a
* different port. This involves switching the port from sink to source,
* if applicable.
*/
static void charge_manager_cleanup_override_port(int port)
{
if (port < 0 || port >= PD_PORT_COUNT)
return;
if (dualrole_capability[port] == CAP_DUALROLE &&
pd_get_role(port) == PD_ROLE_SINK)
pd_request_power_swap(port);
}
/**
* Select the 'best' charge port, as defined by the supplier heirarchy and the
* ability of the port to provide power.
*/
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 < PD_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;
/*
* Don't charge from a dual-role port unless
* it is our override port.
*/
if (dualrole_capability[j] != CAP_DEDICATED &&
override_port != j)
continue;
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)
{
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;
/* Hunt for an acceptable charge port */
while (1) {
charge_manager_get_best_charge_port(&new_port, &new_supplier);
/*
* 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 ((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;
}
/*
* 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) {
charge_manager_cleanup_override_port(override_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;
/* Enforce port charge ceiling. */
if (charge_ceil[new_port] != CHARGE_CEIL_NONE)
new_charge_current = MIN(charge_ceil[new_port],
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]);
#else
board_set_charge_limit(new_charge_current);
#endif
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;
/* Signal new power request on old port if we're switching away. */
if (charge_port != new_port && charge_port != CHARGE_PORT_NONE)
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 < 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 (updated_new_port != CHARGE_PORT_NONE)
pd_set_new_power_request(updated_new_port);
if (updated_old_port != CHARGE_PORT_NONE)
pd_set_new_power_request(updated_old_port);
}
DECLARE_DEFERRED(charge_manager_refresh);
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
*/
if (dualrole_capability[port] != CAP_DEDICATED)
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 dedicated charger is plugged */
if (clear_override && override_port != port &&
dualrole_capability[port] == CAP_DEDICATED) {
charge_manager_cleanup_override_port(override_port);
override_port = OVERRIDE_OFF;
if (delayed_override_port != OVERRIDE_OFF) {
charge_manager_cleanup_override_port(
delayed_override_port);
delayed_override_port = OVERRIDE_OFF;
hook_call_deferred(
board_charge_manager_override_timeout,
-1);
}
}
if (change == CHANGE_CHARGE) {
available_charge[supplier][port].current = charge->current;
available_charge[supplier][port].voltage = charge->voltage;
registration_time[port] = get_time();
/*
* 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 &&
pd_get_role(delayed_override_port) == PD_ROLE_SINK &&
get_time().val < delayed_override_deadline.val)
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, 0);
}
/**
* Update available charge for a given port / supplier.
*
* @param supplier Charge supplier to update.
* @param port Charge port to update.
* @param charge Charge port current / voltage.
*/
void charge_manager_update_charge(int supplier,
int port,
struct charge_port_info *charge)
{
ASSERT(supplier >= 0 && supplier < CHARGE_SUPPLIER_COUNT);
ASSERT(port >= 0 && port < PD_PORT_COUNT);
ASSERT(charge != NULL);
charge_manager_make_change(CHANGE_CHARGE, supplier, port, charge);
}
/**
* Notify charge_manager of a partner dualrole capability change.
*
* @param port Charge port which changed.
* @param cap New port capability.
*/
void charge_manager_update_dualrole(int port, enum dualrole_capabilities cap)
{
ASSERT(port >= 0 && port < PD_PORT_COUNT);
/*
* We have no way of determining the charger dualrole capability in
* locked RO, so just assume we always have a dedicated charger.
*/
if (system_get_image_copy() == SYSTEM_IMAGE_RO && system_is_locked())
cap = CAP_DEDICATED;
/* Ignore when capability is unchanged */
if (cap != dualrole_capability[port]) {
dualrole_capability[port] = cap;
charge_manager_make_change(CHANGE_DUALROLE, 0, port, NULL);
}
}
/**
* Update charge ceiling for a given port.
*
* @param port Charge port to update.
* @param ceil Charge ceiling (mA).
*/
void charge_manager_set_ceil(int port, int ceil)
{
ASSERT(port >= 0 && port < PD_PORT_COUNT);
if (charge_ceil[port] != ceil) {
charge_ceil[port] = ceil;
if (port == charge_port && charge_manager_is_seeded())
hook_call_deferred(charge_manager_refresh, 0);
}
}
/**
* Select an 'override port', a port which is always the preferred charge port.
* Returns EC_SUCCESS on success, ec_error_list status on failure.
*
* @param port Charge port to select as override, or
* OVERRIDE_OFF to select no override port,
* or OVERRIDE_DONT_CHARGE to specifc that no
* charge port should be selected.
*/
int charge_manager_set_override(int port)
{
int retval = EC_SUCCESS;
ASSERT(port >= OVERRIDE_DONT_CHARGE && port < PD_PORT_COUNT);
CPRINTS("Charge Override: %d", port);
/* Supersede any pending delayed overrides. */
if (delayed_override_port != OVERRIDE_OFF) {
if (delayed_override_port != port)
charge_manager_cleanup_override_port(
delayed_override_port);
delayed_override_port = OVERRIDE_OFF;
hook_call_deferred(
board_charge_manager_override_timeout, -1);
}
/* Set the override port if it's a sink. */
if (port < 0 || pd_get_role(port) == PD_ROLE_SINK) {
if (override_port != port) {
charge_manager_cleanup_override_port(override_port);
override_port = port;
if (charge_manager_is_seeded())
hook_call_deferred(charge_manager_refresh, 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 (pd_get_role(port) != PD_ROLE_SINK &&
dualrole_capability[port] == CAP_DUALROLE) {
delayed_override_deadline.val = get_time().val +
POWER_SWAP_TIMEOUT;
delayed_override_port = port;
hook_call_deferred(
board_charge_manager_override_timeout,
POWER_SWAP_TIMEOUT);
pd_request_power_swap(port);
/* Can't charge from requested port -- return error. */
} else
retval = EC_ERROR_INVAL;
return retval;
}
/**
* Get the override port. OVERRIDE_OFF if no override port.
* OVERRIDE_DONT_CHARGE if override is set for no port.
*
* @return override port
*/
int charge_manager_get_override(void)
{
return override_port;
}
int charge_manager_get_active_charge_port(void)
{
return charge_port;
}
#ifndef TEST_CHARGE_MANAGER
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_CHARGE_MANAGER */
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 >= PD_PORT_COUNT)
return EC_RES_INVALID_PARAM;
return charge_manager_set_override(override_port);
}
DECLARE_HOST_COMMAND(EC_CMD_PD_CHARGE_PORT_OVERRIDE,
hc_charge_port_override,
EC_VER_MASK(0));
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 >= PD_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)",
NULL);