389 lines
8.9 KiB
C
389 lines
8.9 KiB
C
|
/* Copyright 2018 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.
|
||
|
*
|
||
|
* TI bq25703 battery charger driver.
|
||
|
*/
|
||
|
|
||
|
#include "battery_smart.h"
|
||
|
#include "bq25703.h"
|
||
|
#include "charge_ramp.h"
|
||
|
#include "charger.h"
|
||
|
#include "common.h"
|
||
|
#include "console.h"
|
||
|
#include "hooks.h"
|
||
|
#include "i2c.h"
|
||
|
#include "timer.h"
|
||
|
|
||
|
/* Sense resistor configurations and macros */
|
||
|
#define DEFAULT_SENSE_RESISTOR 10
|
||
|
|
||
|
#define INPUT_RESISTOR_RATIO \
|
||
|
((CONFIG_CHARGER_SENSE_RESISTOR_AC) / DEFAULT_SENSE_RESISTOR)
|
||
|
#define REG_TO_INPUT_CURRENT(REG) ((REG + 1) * 50 / INPUT_RESISTOR_RATIO)
|
||
|
#define INPUT_CURRENT_TO_REG(CUR) (((CUR) * INPUT_RESISTOR_RATIO / 50) - 1)
|
||
|
|
||
|
#define CHARGING_RESISTOR_RATIO \
|
||
|
((CONFIG_CHARGER_SENSE_RESISTOR) / DEFAULT_SENSE_RESISTOR)
|
||
|
#define REG_TO_CHARGING_CURRENT(REG) ((REG) / CHARGING_RESISTOR_RATIO)
|
||
|
#define CHARGING_CURRENT_TO_REG(CUR) ((CUR) * CHARGING_RESISTOR_RATIO)
|
||
|
|
||
|
|
||
|
/* Console output macros */
|
||
|
#define CPRINTF(format, args...) cprintf(CC_CHARGER, format, ## args)
|
||
|
|
||
|
/* Charger parameters */
|
||
|
static const struct charger_info bq25703_charger_info = {
|
||
|
.name = "bq25703",
|
||
|
.voltage_max = 19200,
|
||
|
.voltage_min = 1024,
|
||
|
.voltage_step = 16,
|
||
|
.current_max = 8128 / CHARGING_RESISTOR_RATIO,
|
||
|
.current_min = 64 / CHARGING_RESISTOR_RATIO,
|
||
|
.current_step = 64 / CHARGING_RESISTOR_RATIO,
|
||
|
.input_current_max = 6400 / INPUT_RESISTOR_RATIO,
|
||
|
.input_current_min = 50 / INPUT_RESISTOR_RATIO,
|
||
|
.input_current_step = 50 / INPUT_RESISTOR_RATIO,
|
||
|
};
|
||
|
|
||
|
static inline int raw_read8(int offset, int *value)
|
||
|
{
|
||
|
return i2c_read8(I2C_PORT_CHARGER, BQ25703_I2C_ADDR1, offset, value);
|
||
|
}
|
||
|
|
||
|
static inline int raw_write8(int offset, int value)
|
||
|
{
|
||
|
return i2c_write8(I2C_PORT_CHARGER, BQ25703_I2C_ADDR1, offset, value);
|
||
|
}
|
||
|
|
||
|
static inline int raw_read16(int offset, int *value)
|
||
|
{
|
||
|
return i2c_read16(I2C_PORT_CHARGER, BQ25703_I2C_ADDR1, offset, value);
|
||
|
}
|
||
|
|
||
|
static inline int raw_write16(int offset, int value)
|
||
|
{
|
||
|
return i2c_write16(I2C_PORT_CHARGER, BQ25703_I2C_ADDR1, offset, value);
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_CHARGE_RAMP_HW
|
||
|
static int bq25703_get_low_power_mode(int *mode)
|
||
|
{
|
||
|
int rv;
|
||
|
int reg;
|
||
|
|
||
|
rv = raw_read16(BQ25703_REG_CHARGE_OPTION_0, ®);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
|
||
|
*mode = !!(reg & BQ25703_CHARGE_OPTION_0_LOW_POWER_MODE);
|
||
|
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
static int bq25703_set_low_power_mode(int enable)
|
||
|
{
|
||
|
int rv;
|
||
|
int reg;
|
||
|
|
||
|
rv = raw_read16(BQ25703_REG_CHARGE_OPTION_0, ®);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
|
||
|
if (enable)
|
||
|
reg |= BQ25703_CHARGE_OPTION_0_LOW_POWER_MODE;
|
||
|
else
|
||
|
reg &= ~BQ25703_CHARGE_OPTION_0_LOW_POWER_MODE;
|
||
|
|
||
|
rv = raw_write16(BQ25703_REG_CHARGE_OPTION_0, reg);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* Charger interfaces */
|
||
|
|
||
|
const struct charger_info *charger_get_info(void)
|
||
|
{
|
||
|
return &bq25703_charger_info;
|
||
|
}
|
||
|
|
||
|
int charger_post_init(void)
|
||
|
{
|
||
|
/*
|
||
|
* Note: bq25703 power on reset state is:
|
||
|
* watch dog timer = 175 sec
|
||
|
* input current limit = ~1/2 maximum setting
|
||
|
* charging voltage = 0 mV
|
||
|
* charging current = 0 mA
|
||
|
* discharge on AC = disabled
|
||
|
*/
|
||
|
|
||
|
/* Set charger input current limit */
|
||
|
return charger_set_input_current(CONFIG_CHARGER_INPUT_CURRENT);
|
||
|
}
|
||
|
|
||
|
int charger_get_status(int *status)
|
||
|
{
|
||
|
int rv;
|
||
|
int option;
|
||
|
|
||
|
rv = charger_get_option(&option);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
|
||
|
/* Default status */
|
||
|
*status = CHARGER_LEVEL_2;
|
||
|
|
||
|
if (option & BQ25703_CHARGE_OPTION_0_CHRG_INHIBIT)
|
||
|
*status |= CHARGER_CHARGE_INHIBITED;
|
||
|
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
int charger_set_mode(int mode)
|
||
|
{
|
||
|
int rv;
|
||
|
int option;
|
||
|
|
||
|
rv = charger_get_option(&option);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
|
||
|
if (mode & CHARGER_CHARGE_INHIBITED)
|
||
|
option |= BQ25703_CHARGE_OPTION_0_CHRG_INHIBIT;
|
||
|
else
|
||
|
option &= ~BQ25703_CHARGE_OPTION_0_CHRG_INHIBIT;
|
||
|
|
||
|
return charger_set_option(option);
|
||
|
}
|
||
|
|
||
|
int charger_enable_otg_power(int enabled)
|
||
|
{
|
||
|
/* This is controlled with the EN_OTG pin. Support not added yet. */
|
||
|
return EC_ERROR_UNIMPLEMENTED;
|
||
|
}
|
||
|
|
||
|
int charger_set_otg_current_voltage(int output_current, int output_voltage)
|
||
|
{
|
||
|
/* Add when needed. */
|
||
|
return EC_ERROR_UNIMPLEMENTED;
|
||
|
}
|
||
|
|
||
|
int charger_is_sourcing_otg_power(int port)
|
||
|
{
|
||
|
/* Add when needed. */
|
||
|
return EC_ERROR_UNIMPLEMENTED;
|
||
|
}
|
||
|
|
||
|
int charger_get_current(int *current)
|
||
|
{
|
||
|
int rv, reg;
|
||
|
|
||
|
rv = raw_read16(BQ25703_REG_CHARGE_CURRENT, ®);
|
||
|
if (!rv)
|
||
|
*current = REG_TO_CHARGING_CURRENT(reg);
|
||
|
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
int charger_set_current(int current)
|
||
|
{
|
||
|
return raw_write16(BQ25703_REG_CHARGE_CURRENT,
|
||
|
CHARGING_CURRENT_TO_REG(current));
|
||
|
}
|
||
|
|
||
|
/* Get/set charge voltage limit in mV */
|
||
|
int charger_get_voltage(int *voltage)
|
||
|
{
|
||
|
return raw_read16(BQ25703_REG_MAX_CHARGE_VOLTAGE, voltage);
|
||
|
}
|
||
|
int charger_set_voltage(int voltage)
|
||
|
{
|
||
|
return raw_write16(BQ25703_REG_MAX_CHARGE_VOLTAGE, voltage);
|
||
|
}
|
||
|
|
||
|
/* Discharge battery when on AC power. */
|
||
|
int charger_discharge_on_ac(int enable)
|
||
|
{
|
||
|
int rv, option;
|
||
|
|
||
|
rv = charger_get_option(&option);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
|
||
|
if (enable)
|
||
|
option |= BQ25703_CHARGE_OPTION_0_EN_LEARN;
|
||
|
else
|
||
|
option &= ~BQ25703_CHARGE_OPTION_0_EN_LEARN;
|
||
|
|
||
|
return charger_set_option(option);
|
||
|
}
|
||
|
|
||
|
int charger_set_input_current(int input_current)
|
||
|
{
|
||
|
return raw_write8(BQ25703_REG_IIN_HOST,
|
||
|
INPUT_CURRENT_TO_REG(input_current));
|
||
|
}
|
||
|
|
||
|
int charger_get_input_current(int *input_current)
|
||
|
{
|
||
|
int rv, reg;
|
||
|
|
||
|
/*
|
||
|
* IIN_DPM register reflects the actual input current limit programmed
|
||
|
* in the register, either from host or from ICO. After ICO, the
|
||
|
* current limit used by DPM regulation may differ from the IIN_HOST
|
||
|
* register settings.
|
||
|
*/
|
||
|
rv = raw_read8(BQ25703_REG_IIN_DPM, ®);
|
||
|
if (!rv)
|
||
|
*input_current = REG_TO_INPUT_CURRENT(reg);
|
||
|
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
int charger_manufacturer_id(int *id)
|
||
|
{
|
||
|
return raw_read8(BQ25703_REG_MANUFACTURER_ID, id);
|
||
|
}
|
||
|
int charger_device_id(int *id)
|
||
|
{
|
||
|
return raw_read8(BQ25703_REG_DEVICE_ADDRESS, id);
|
||
|
}
|
||
|
|
||
|
int charger_get_option(int *option)
|
||
|
{
|
||
|
/* There are 4 option registers, but we only need the first for now. */
|
||
|
return raw_read16(BQ25703_REG_CHARGE_OPTION_0, option);
|
||
|
}
|
||
|
|
||
|
int charger_set_option(int option)
|
||
|
{
|
||
|
/* There are 4 option registers, but we only need the first for now. */
|
||
|
return raw_write16(BQ25703_REG_CHARGE_OPTION_0, option);
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_CHARGE_RAMP_HW
|
||
|
|
||
|
static void bq25703_chg_ramp_handle(void)
|
||
|
{
|
||
|
int ramp_curr;
|
||
|
|
||
|
/*
|
||
|
* Once the charge ramp is stable write back the stable ramp
|
||
|
* current to input current register.
|
||
|
*/
|
||
|
if (chg_ramp_is_stable()) {
|
||
|
ramp_curr = chg_ramp_get_current_limit();
|
||
|
if (ramp_curr && !charger_set_input_current(ramp_curr))
|
||
|
CPRINTF("stable ramp current=%d\n", ramp_curr);
|
||
|
}
|
||
|
}
|
||
|
DECLARE_DEFERRED(bq25703_chg_ramp_handle);
|
||
|
|
||
|
int charger_set_hw_ramp(int enable)
|
||
|
{
|
||
|
int option3_reg, option2_reg, rv;
|
||
|
|
||
|
rv = raw_read16(BQ25703_REG_CHARGE_OPTION_3, &option3_reg);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
rv = raw_read16(BQ25703_REG_CHARGE_OPTION_2, &option2_reg);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
|
||
|
if (enable) {
|
||
|
/* Set InputVoltage register to BC1.2 minimum ramp voltage */
|
||
|
rv = raw_write16(BQ25703_REG_INPUT_VOLTAGE,
|
||
|
BQ25703_BC12_MIN_VOLTAGE_MV);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
|
||
|
/* Enable ICO algorithm */
|
||
|
option3_reg |= BQ25703_CHARGE_OPTION_3_EN_ICO_MODE;
|
||
|
|
||
|
/* 0b: Input current limit is set by BQ25703_REG_IIN_HOST */
|
||
|
option2_reg &= ~BQ25703_CHARGE_OPTION_2_EN_EXTILIM;
|
||
|
|
||
|
/* Charge ramp may take up to 2s to settle down */
|
||
|
hook_call_deferred(&bq25703_chg_ramp_handle_data, (4 * SECOND));
|
||
|
} else {
|
||
|
/* Disable ICO algorithm */
|
||
|
option3_reg &= ~BQ25703_CHARGE_OPTION_3_EN_ICO_MODE;
|
||
|
|
||
|
/*
|
||
|
* 1b: Input current limit is set by the lower value of
|
||
|
* ILIM_HIZ pin and BQ25703_REG_IIN_HOST
|
||
|
*/
|
||
|
option2_reg |= BQ25703_CHARGE_OPTION_2_EN_EXTILIM;
|
||
|
}
|
||
|
|
||
|
rv = raw_write16(BQ25703_REG_CHARGE_OPTION_2, option2_reg);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
return raw_write16(BQ25703_REG_CHARGE_OPTION_3, option3_reg);
|
||
|
}
|
||
|
|
||
|
int chg_ramp_is_stable(void)
|
||
|
{
|
||
|
int reg;
|
||
|
|
||
|
if (raw_read16(BQ25703_REG_CHARGER_STATUS, ®))
|
||
|
return 0;
|
||
|
|
||
|
return reg & BQ25703_CHARGE_STATUS_ICO_DONE;
|
||
|
}
|
||
|
|
||
|
int chg_ramp_get_current_limit(void)
|
||
|
{
|
||
|
int reg;
|
||
|
int mode;
|
||
|
int tries_left = 8;
|
||
|
|
||
|
/* Save current mode to restore same state after ADC read */
|
||
|
if (bq25703_get_low_power_mode(&mode))
|
||
|
goto error;
|
||
|
|
||
|
/* Exit low power mode so ADC conversion takes typical time */
|
||
|
if (bq25703_set_low_power_mode(0))
|
||
|
goto error;
|
||
|
|
||
|
/* Turn on the ADC for one reading */
|
||
|
reg = BQ25703_ADC_OPTION_ADC_START | BQ25703_ADC_OPTION_EN_ADC_IIN;
|
||
|
if (raw_write16(BQ25703_REG_ADC_OPTION, reg))
|
||
|
goto error;
|
||
|
|
||
|
/*
|
||
|
* Wait until the ADC operation completes. The spec says typical
|
||
|
* conversion time is 10 msec. If low power mode isn't exited first,
|
||
|
* then the conversion time jumps to ~60 msec.
|
||
|
*/
|
||
|
do {
|
||
|
msleep(2);
|
||
|
raw_read16(BQ25703_REG_ADC_OPTION, ®);
|
||
|
} while (--tries_left && (reg & BQ25703_ADC_OPTION_ADC_START));
|
||
|
|
||
|
/* ADC reading attempt complete, go back to low power mode */
|
||
|
if (bq25703_set_low_power_mode(mode))
|
||
|
goto error;
|
||
|
|
||
|
/* Could not complete read */
|
||
|
if (reg & BQ25703_ADC_OPTION_ADC_START)
|
||
|
goto error;
|
||
|
|
||
|
/* Read ADC value */
|
||
|
if (raw_read8(BQ25703_REG_ADC_IIN, ®))
|
||
|
goto error;
|
||
|
|
||
|
/* LSB => 50mA */
|
||
|
return reg * BQ25703_ADC_IIN_STEP_MA;
|
||
|
|
||
|
error:
|
||
|
CPRINTF("Could not read input current limit ADC!\n");
|
||
|
return 0;
|
||
|
}
|
||
|
#endif /* CONFIG_CHARGE_RAMP_HW */
|