mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-10 17:41:54 +00:00
This is a precursor to changing the switch state machine to delay powering on the AP until the charge_state module knows if there's enough power to do so (which it will know when it leaves the INIT state). BUG=chrome-os-partner:17124 BRANCH=link TEST=manual 1. Reboot EC on battery power. See charge state init->discharging 2. Plug in AC. See charge state discharging->reinit (charge state will then transition to some other charging or idle state based on battery level) Change-Id: Ia02cc8b37e9b5e8d6dd8c2fbfdf14e385694b1bf Signed-off-by: Randall Spangler <rspangler@chromium.org> Reviewed-on: https://gerrit.chromium.org/gerrit/44291 Reviewed-by: Bill Richardson <wfrichar@chromium.org>
249 lines
6.1 KiB
C
249 lines
6.1 KiB
C
/* 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.
|
|
*
|
|
* 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 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);
|
|
}
|
|
|
|
/**
|
|
* Decrease the charging voltage one step.
|
|
*/
|
|
static int dec_voltage(struct power_state_context *ctx)
|
|
{
|
|
return set_voltage(ctx, ctx->curr.charging_voltage -
|
|
ctx->charger->voltage_step);
|
|
}
|
|
|
|
/**
|
|
* Increase the charging voltage baseline one step.
|
|
*/
|
|
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.
|
|
*/
|
|
sleep(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_REINIT;
|
|
}
|
|
|
|
/* 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_REINIT;
|
|
}
|
|
|
|
/*
|
|
* 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_REINIT;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
sleep(5);
|
|
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_REINIT;
|
|
}
|
|
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;
|
|
sleep(1);
|
|
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. The target current to desired voltage
|
|
* function is a monotonic function. To simplify the calculation, 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);
|
|
sleep(1);
|
|
return PWR_STATE_UNCHANGE;
|
|
}
|
|
|
|
if (desired_volt < (curr->charging_voltage -
|
|
cinfo->voltage_step)) {
|
|
dec_voltage(ctx);
|
|
sleep(1);
|
|
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;
|
|
}
|