mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-09 17:11:42 +00:00
Adding trickle charging mode to precharge batteries with voltage lower than minimal design value. This CL adds control to charger voltage to track battery input current change. To prevent battery from deeply discharging, this CL preserves 3% of the design capacity. Minor bug fixes include error state check and charger control logic. Signed-off-by: Rong Chang <rongchang@chromium.org> BUG=chrome-os-partner:8660,8661 TEST=manual Plug AC power, the power adapter led should be 'yellow'. On the EC serial console, type 'battery' and 'charger' commands. Battery input current should staid close to its desired current. A deeply discharged battery (5.5V) should be revived to a healthy state after 30 minutes ~ 4 hours. Change-Id: Ibaa2396c6b751639d98db32f5919b1e8ec700e40
235 lines
6.1 KiB
C
235 lines
6.1 KiB
C
/* Copyright (c) 2012 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.
|
|
*
|
|
* Battery charging task and state machine.
|
|
*/
|
|
|
|
#include "battery.h"
|
|
#include "battery_pack.h"
|
|
#include "charge_state.h"
|
|
#include "charger.h"
|
|
#include "smart_battery.h"
|
|
#include "timer.h"
|
|
#include "uart.h"
|
|
#include "util.h"
|
|
|
|
/* Buffer size for charging resistance calculation */
|
|
#define LOG_BUFFER_SIZE 16
|
|
|
|
static int log_index;
|
|
static short log_volt[LOG_BUFFER_SIZE];
|
|
static short log_curr[LOG_BUFFER_SIZE];
|
|
static int baseline_voltage;
|
|
static int kicking_count;
|
|
|
|
static inline int time_after(timestamp_t now, timestamp_t orig, uint64_t usec)
|
|
{
|
|
return (now.val > (orig.val + usec));
|
|
}
|
|
|
|
static inline void reset_data_log(void)
|
|
{
|
|
log_index = 0;
|
|
}
|
|
|
|
static inline void trickle_charging_init(void)
|
|
{
|
|
baseline_voltage = 0;
|
|
kicking_count = 0;
|
|
reset_data_log();
|
|
}
|
|
|
|
/* Adjust charging voltage with voltage value range checking.
|
|
* Reset data log and charger watchdog timer.
|
|
*/
|
|
static int set_voltage(struct power_state_context *ctx, int voltage)
|
|
{
|
|
if (voltage <= ctx->curr.batt.desired_voltage && voltage > 0) {
|
|
charger_set_voltage(voltage);
|
|
charger_get_voltage(&ctx->curr.charging_voltage);
|
|
ctx->charger_update_time = get_time();
|
|
reset_data_log();
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* Increase/decrease the charging voltage one step */
|
|
static int inc_voltage(struct power_state_context *ctx)
|
|
{
|
|
return set_voltage(ctx, ctx->curr.charging_voltage +
|
|
ctx->charger->voltage_step);
|
|
}
|
|
|
|
static int dec_voltage(struct power_state_context *ctx)
|
|
{
|
|
return set_voltage(ctx, ctx->curr.charging_voltage -
|
|
ctx->charger->voltage_step);
|
|
}
|
|
|
|
/* Bump up the charging voltage baseline to one step higher. */
|
|
static enum power_state go_next_level(struct power_state_context *ctx)
|
|
{
|
|
if (inc_voltage(ctx))
|
|
return PWR_STATE_ERROR;
|
|
|
|
/* Battery chemical reaction lags behind the charging voltage
|
|
* change. Delay the charging state machine 2 seconds.
|
|
*/
|
|
usleep(SECOND * 2);
|
|
charger_get_voltage(&baseline_voltage);
|
|
|
|
return PWR_STATE_UNCHANGE;
|
|
}
|
|
|
|
/* Trickle charging handler
|
|
* - check trickle charging timeout
|
|
* - new state: INIT
|
|
* - exit condition: when desired_current reaches current_min
|
|
* - try to charge larger current when battery voltage reaches
|
|
* 105% of voltage_min
|
|
*/
|
|
enum power_state trickle_charge(struct power_state_context *ctx)
|
|
{
|
|
int sum_volt, sum_curr;
|
|
int desired_volt, desired_curr;
|
|
|
|
struct power_state_data *curr = &ctx->curr;
|
|
struct batt_params *batt = &curr->batt;
|
|
const struct charger_info *cinfo = ctx->charger;
|
|
const struct battery_info *binfo = ctx->battery;
|
|
|
|
/* Clear trickle charging duration on AC change */
|
|
if (curr->ac != ctx->prev.ac) {
|
|
ctx->trickle_charging_time.val = 0;
|
|
if (!curr->ac)
|
|
return PWR_STATE_INIT;
|
|
}
|
|
|
|
/* Start timer */
|
|
if (ctx->trickle_charging_time.val == 0) {
|
|
trickle_charging_init();
|
|
ctx->trickle_charging_time = get_time();
|
|
}
|
|
|
|
/* Check charger reset */
|
|
if (curr->charging_voltage == 0 || curr->charging_current == 0) {
|
|
ctx->trickle_charging_time.val = 0;
|
|
return PWR_STATE_INIT;
|
|
}
|
|
|
|
/* 4 hours is *long* enough to pre-charge a large battery (8000mAh)
|
|
* using minimal current (5mAh).
|
|
*/
|
|
if (time_after(curr->ts, ctx->trickle_charging_time, HOUR * 4))
|
|
return PWR_STATE_ERROR;
|
|
|
|
if (curr->error & F_BATTERY_MASK)
|
|
return PWR_STATE_UNCHANGE;
|
|
|
|
/* End of pre-charge condition. Battery desired a current higher
|
|
* than the minimal charging cap.
|
|
*/
|
|
if (batt->desired_current > cinfo->current_min) {
|
|
trickle_charging_init();
|
|
ctx->trickle_charging_time.val = 0;
|
|
return PWR_STATE_INIT;
|
|
}
|
|
|
|
/* If the trickle charging current drops to zero, raise charging
|
|
* voltage baseline to next level.
|
|
*/
|
|
if (batt->current == 0)
|
|
return go_next_level(ctx);
|
|
|
|
/* When the battery voltage reaches normal charging value (105% min),
|
|
* try kicking the current up and see if it starts normal charging.
|
|
*/
|
|
if (kicking_count < 5 &&
|
|
batt->voltage > (binfo->voltage_min * 105 / 100)) {
|
|
kicking_count++;
|
|
charger_set_voltage(batt->desired_voltage);
|
|
usleep(5 * SECOND);
|
|
desired_curr = 0;
|
|
battery_desired_current(&desired_curr);
|
|
if (desired_curr >= cinfo->current_min) {
|
|
/* Exit trickle charging state */
|
|
trickle_charging_init();
|
|
ctx->trickle_charging_time.val = 0;
|
|
return PWR_STATE_INIT;
|
|
}
|
|
charger_set_voltage(curr->charging_voltage);
|
|
ctx->charger_update_time = get_time();
|
|
|
|
reset_data_log();
|
|
return PWR_STATE_UNCHANGE;
|
|
}
|
|
|
|
/* Over current protection. Decrease charging voltage and baseline
|
|
* voltage.
|
|
*/
|
|
if (batt->current > binfo->precharge_current) {
|
|
dec_voltage(ctx);
|
|
if (baseline_voltage > ctx->curr.charging_voltage)
|
|
baseline_voltage = ctx->curr.charging_voltage;
|
|
usleep(SECOND);
|
|
reset_data_log();
|
|
return PWR_STATE_UNCHANGE;
|
|
}
|
|
|
|
/* Voltage and current data acquisition. */
|
|
if (log_index < LOG_BUFFER_SIZE) {
|
|
log_volt[log_index] = batt->voltage;
|
|
log_curr[log_index] = batt->current;
|
|
log_index++;
|
|
return PWR_STATE_UNCHANGE;
|
|
}
|
|
|
|
sum_volt = 0;
|
|
sum_curr = 0;
|
|
for (log_index = 0; log_index < LOG_BUFFER_SIZE; log_index++) {
|
|
sum_volt += log_volt[log_index];
|
|
sum_curr += log_curr[log_index];
|
|
}
|
|
|
|
reset_data_log();
|
|
|
|
/* Estimate desired_voltage:
|
|
* Although the target current to desired voltage function is a
|
|
* monotonic function. To simplify the calculation, following
|
|
* code use linear estimation when the current delta is small.
|
|
*
|
|
* V_desired = I_target * ( avg(dV_batt) / avg(I_batt) ) +
|
|
* V_batt
|
|
*/
|
|
desired_volt = (1 + batt->desired_current) *
|
|
(curr->charging_voltage * LOG_BUFFER_SIZE - sum_volt) /
|
|
sum_curr + batt->voltage;
|
|
|
|
if (desired_volt > baseline_voltage) {
|
|
if (desired_volt > curr->charging_voltage) {
|
|
inc_voltage(ctx);
|
|
usleep(SECOND);
|
|
return PWR_STATE_UNCHANGE;
|
|
}
|
|
|
|
if (desired_volt < (curr->charging_voltage -
|
|
cinfo->voltage_step)) {
|
|
dec_voltage(ctx);
|
|
usleep(SECOND);
|
|
return PWR_STATE_UNCHANGE;
|
|
}
|
|
}
|
|
|
|
/* Update charger watchdog periodically */
|
|
if (time_after(curr->ts, ctx->charger_update_time,
|
|
CHARGER_UPDATE_PERIOD)) {
|
|
charger_set_current(curr->charging_current);
|
|
ctx->charger_update_time = get_time();
|
|
}
|
|
|
|
return PWR_STATE_UNCHANGE;
|
|
}
|
|
|