blob: f7e0516e7087ba06b6b353e7960d52448ef4d798 [file]
/* Copyright (c) 2013 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.
*/
/*
* Falco adapters can support "charger hybrid turbo boost" mode and other
* buzzwords. The limits vary depending on each adapter's power rating, so we
* need to watch for changes and adjust the limits and high-current thresholds
* accordingly. If we go over, the AP needs to throttle itself. The EC's
* charging state logic isn't affected, just the AP's P-State. We try to save
* PROCHOT as a last resort.
*/
#include <limits.h> /* part of the compiler */
#include "adc.h"
#include "battery_smart.h"
#include "charge_state.h"
#include "charger.h"
#include "chipset.h"
#include "common.h"
#include "console.h"
#include "driver/charger/bq24738.h"
#include "extpower.h"
#include "extpower_falco.h"
#include "hooks.h"
#include "host_command.h"
#include "throttle_ap.h"
#include "util.h"
/* Console output macros */
#define CPUTS(outstr) cputs(CC_CHARGER, outstr)
#define CPRINTS(format, args...) cprints(CC_CHARGER, format, ## args)
/* Values for our supported adapters */
static const char * const ad_name[] = {
"unknown",
"45W",
"65W",
"90W"
};
BUILD_ASSERT(ARRAY_SIZE(ad_name) == NUM_ADAPTER_TYPES);
test_export_static
struct adapter_id_vals ad_id_vals[] = {
/* mV low, mV high */
{INT_MIN, INT_MAX}, /* anything = ADAPTER_UNKNOWN */
{434, 554}, /* ADAPTER_45W */
{561, 717}, /* ADAPTER_65W */
{725, 925} /* ADAPTER_90W */
};
BUILD_ASSERT(ARRAY_SIZE(ad_id_vals) == NUM_ADAPTER_TYPES);
test_export_static
int ad_input_current[][NUM_AC_TURBO_STATES] = {
/*
* Current limits in mA for each adapter, for turbo off and turbo on.
* Values are in hex to avoid roundoff, because the BQ24738 Input
* Current Register masks off bits 6-0.
*
* Note that this is very specific to the combinations of adapters and
* BQ24738 charger chip on Falco.
*/
{0x0a00, 0x0a00}, /* ADAPTER_UNKNOWN ~ 2.5 A */
{0x0600, 0x0800}, /* ADAPTER_45W ~ 1.5-2.0 A */
{0x0a00, 0x0c00}, /* ADAPTER_65W ~ 2.5-3.0 A */
{0x0f00, 0x1100} /* ADAPTER_90W ~ 3.8-4.3 A */
};
BUILD_ASSERT(ARRAY_SIZE(ad_input_current) == NUM_ADAPTER_TYPES);
test_export_static
struct adapter_limits ad_limits[][NUM_AC_TURBO_STATES][NUM_AC_THRESHOLDS] = {
/* ADAPTER_UNKNOWN - treat as 65W, no turbo */
{
/* Turbo off */
{
{ 3080, 2730, 16, 80, },
{ 3280, 2930, 1, 80, },
},
/* Turbo on - unused, except for testing */
{
{ 3080, 2730, 16, 80, },
{ 3280, 2930, 1, 80, },
}
},
/* ADAPTER_45W */
{
/* Turbo off */
{
{ 2050, 1700, 16, 80, },
{ 2260, 1910, 1, 80, },
},
/* Turbo on */
{
{ 2310, 1960, 16, 80, },
{ 2560, 2210, 1, 80, },
}
},
/* ADAPTER_65W */
{
/* Turbo off */
{
{ 3080, 2730, 16, 80, },
{ 3280, 2930, 1, 80, },
},
/* Turbo on */
{
{ 3330, 2980, 16, 80, },
{ 3590, 3240, 1, 80, },
}
},
/* ADAPTER_90W */
{
/* Turbo off */
{
{ 4360, 4010, 16, 80, },
{ 4560, 4210, 1, 80, },
},
/* Turbo on */
{
{ 4620, 4270, 16, 80, },
{ 4870, 4520, 1, 80, },
}
}
};
BUILD_ASSERT(ARRAY_SIZE(ad_limits) == NUM_ADAPTER_TYPES);
/* The battery current limits are independent of Turbo or adapter rating.
* hi_val and lo_val are DISCHARGE current in mA.
*/
test_export_static
struct adapter_limits batt_limits[] = {
{ 7500, 7000, 16, 50, },
{ 8000, 7500, 1, 50, },
};
BUILD_ASSERT(ARRAY_SIZE(batt_limits) == NUM_BATT_THRESHOLDS);
static int last_mv;
static enum adapter_type identify_adapter(void)
{
int i;
last_mv = adc_read_channel(ADC_AC_ADAPTER_ID_VOLTAGE);
/* ADAPTER_UNKNOWN matches everything, so search backwards */
for (i = NUM_ADAPTER_TYPES - 1; i >= 0; i--)
if (last_mv >= ad_id_vals[i].lo && last_mv <= ad_id_vals[i].hi)
return i;
return ADAPTER_UNKNOWN; /* should never get here */
}
test_export_static enum adapter_type ac_adapter;
static void ac_change_callback(void)
{
if (extpower_is_present()) {
ac_adapter = identify_adapter();
CPRINTS("AC Adapter is %s (%dmv)",
ad_name[ac_adapter], last_mv);
} else {
ac_adapter = ADAPTER_UNKNOWN;
CPRINTS("AC Adapter is not present");
/* Charger unavailable. Clear local flags */
}
}
DECLARE_HOOK(HOOK_AC_CHANGE, ac_change_callback, HOOK_PRIO_DEFAULT);
test_export_static int ac_turbo = -1;
static void set_turbo(int on)
{
int tmp, r;
if (ac_turbo != on)
CPRINTS("turbo mode => %d", on);
/* Set/clear turbo mode in charger */
r = charger_get_option(&tmp);
if (r != EC_SUCCESS)
goto bad;
if (on)
tmp |= OPTION_BOOST_MODE_ENABLE;
else
tmp &= ~OPTION_BOOST_MODE_ENABLE;
r = charger_set_option(tmp);
if (r != EC_SUCCESS)
goto bad;
/* Set allowed Io based on adapter. The charger will sometimes change
* this setting all by itself due to inrush current limiting, so we
* can't assume it stays where we put it. */
r = charger_set_input_current(ad_input_current[ac_adapter][on]);
if (r != EC_SUCCESS)
goto bad;
ac_turbo = on;
return;
bad:
CPRINTS("ERROR: can't talk to charger: %d", r);
}
/* We need to OR all the possible reasons to throttle in order to decide
* whether it should happen or not. Use one bit per reason.
*/
#define BATT_REASON_OFFSET 0
#define AC_REASON_OFFSET NUM_BATT_THRESHOLDS
BUILD_ASSERT(NUM_BATT_THRESHOLDS + NUM_AC_THRESHOLDS < 32);
test_export_static uint32_t ap_is_throttled;
static void set_throttle(int on, int whosays)
{
if (on)
ap_is_throttled |= (1 << whosays);
else
ap_is_throttled &= ~(1 << whosays);
throttle_ap(ap_is_throttled ? THROTTLE_ON : THROTTLE_OFF,
THROTTLE_SOFT, THROTTLE_SRC_POWER);
}
test_export_static
void check_threshold(int current, struct adapter_limits *lim, int whoami)
{
if (lim->triggered) {
/* watching for current to drop */
if (current < lim->lo_val) {
if (++lim->count >= lim->lo_cnt) {
set_throttle(0, whoami);
lim->count = 0;
lim->triggered = 0;
}
} else {
lim->count = 0;
}
} else {
/* watching for current to rise */
if (current > lim->hi_val) {
if (++lim->count >= lim->hi_cnt) {
set_throttle(1, whoami);
lim->count = 0;
lim->triggered = 1;
}
} else {
lim->count = 0;
}
}
}
test_export_static
void watch_battery_closely(struct charge_state_context *ctx)
{
int i;
int current = ctx->curr.batt.current;
/* NB: The values in batt_limits[] indicate DISCHARGE current (mA).
* However, the value returned from battery_current() is CHARGE
* current: postive for charging and negative for discharging.
*
* Turbo mode can discharge the battery even while connected to the
* charger. The spec says not to turn throttling off until the battery
* drain has been below the threshold for 5 seconds. That means we
* still need to check while on AC, or else just plugging the adapter
* in and out would mess up that 5-second timeout. Since the threshold
* logic uses signed numbers to compare the limits, everything Just
* Works.
*/
/* Check limits against DISCHARGE current, not CHARGE current! */
for (i = 0; i < NUM_BATT_THRESHOLDS; i++)
check_threshold(-current, &batt_limits[i], /* invert sign! */
i + BATT_REASON_OFFSET);
}
void watch_adapter_closely(struct charge_state_context *ctx)
{
int current, i;
/* We always watch the battery current drain, even when on AC. */
watch_battery_closely(ctx);
/* We can only talk to the charger if we're on AC. If there are no
* errors and we recognize the adapter, enable Turbo at 15% charge,
* disable it at 10% to provide hysteresis. */
if (extpower_is_present()) {
if (ctx->curr.error ||
ctx->curr.batt.state_of_charge < 10 ||
ac_adapter == ADAPTER_UNKNOWN) {
set_turbo(0);
} else if (ctx->curr.batt.state_of_charge > 15) {
set_turbo(1);
}
} else {
/* If we're not on AC, we can't monitor the current,
* so watch for its return.
*/
ac_turbo = -1;
}
/* If the AP is off, we won't need to throttle it. */
if (chipset_in_state(CHIPSET_STATE_ANY_OFF |
CHIPSET_STATE_SUSPEND))
return;
/* Check all the thresholds. */
current = adc_read_channel(ADC_CH_CHARGER_CURRENT);
for (i = 0; i < NUM_AC_THRESHOLDS; i++)
check_threshold(current, &ad_limits[ac_adapter][ac_turbo][i],
i + AC_REASON_OFFSET);
}
static int command_adapter(int argc, char **argv)
{
enum adapter_type v = identify_adapter();
ccprintf("Adapter %s (%dmv), turbo %d, ap_is_throttled 0x%08x\n",
ad_name[v], last_mv, ac_turbo, ap_is_throttled);
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(adapter, command_adapter,
NULL,
"Display AC adapter information",
NULL);