sensor: bq274xx: rewrite the configuration function

Rework the device configuration code. The current code has a bunch of
leftover functions that read data and compute checksums that are never
used, but that break the initialization sequence if removed because
there are also some missing delays in their place.

Redo the initialization code from scratch, this is mainly inspired from
the Linux driver and taking some part from the (somewhat confusing and
incomplete) datasheet.

This drops the dead code and adds the necessary sleeps to guarantee
correct operation.

The device configuration is also now changing the local copy of the data
block, and soft reset is also issued only if the device configuration
has changed, which should only happens if the battery is replaced or
went completely flat. This should also result in a consistent battery
measurement operation across resets.

Link: https://elixir.bootlin.com/linux/latest/source/drivers/power/supply/bq27xxx_battery.c
Link: https://www.ti.com/lit/ug/sluucd5/sluucd5.pdf
Signed-off-by: Fabio Baltieri <fabiobaltieri@google.com>
This commit is contained in:
Fabio Baltieri 2023-07-23 18:23:29 +01:00 committed by Carles Cufí
commit 34bae6c497
2 changed files with 169 additions and 172 deletions

View file

@ -34,8 +34,11 @@ LOG_MODULE_REGISTER(bq274xx, CONFIG_SENSOR_LOG_LEVEL);
/* subclass 64 & 82 needs 5ms delay */
#define BQ274XX_SUBCLASS_DELAY K_MSEC(5)
/* Time to wait for CFGUP bit to be set */
#define BQ274XX_CFGUP_DELAY K_MSEC(50)
/* Time to wait for CFGUP bit to be set, up to 1 second according to the
* technical reference manual, keep some headroom like the Linux driver.
*/
#define BQ274XX_CFGUP_DELAY K_MSEC(25)
#define BQ274XX_CFGUP_MAX_TRIES 100
/* Time to set pin in order to exit shutdown mode */
#define PIN_DELAY_TIME K_MSEC(1)
@ -49,6 +52,9 @@ LOG_MODULE_REGISTER(bq274xx, CONFIG_SENSOR_LOG_LEVEL);
/* Config update mode flag */
#define BQ27XXX_FLAG_CFGUP BIT(4)
/* Subclasses */
#define BQ274XX_SUBCLASS_82 82
static const struct bq274xx_regs bq27421_regs = {
.dm_design_capacity = 10,
.dm_design_energy = 12,
@ -88,7 +94,7 @@ static int bq274xx_ctrl_reg_write(const struct device *dev, uint16_t subcommand)
uint8_t tx_buf[3];
tx_buf[0] = BQ274XX_CMD_CONTROL_LOW;
tx_buf[0] = BQ274XX_CMD_CONTROL;
sys_put_le16(subcommand, &tx_buf[1]);
ret = i2c_write_dt(&config->i2c, tx_buf, sizeof(tx_buf));
@ -100,26 +106,6 @@ static int bq274xx_ctrl_reg_write(const struct device *dev, uint16_t subcommand)
return 0;
}
static int bq274xx_read_data_block(const struct device *dev, uint8_t offset,
uint8_t *data, uint8_t bytes)
{
const struct bq274xx_config *config = dev->config;
uint8_t i2c_data;
int ret;
i2c_data = BQ274XX_EXT_BLKDAT_START + offset;
ret = i2c_burst_read_dt(&config->i2c, i2c_data, data, bytes);
if (ret < 0) {
LOG_ERR("Failed to read block");
return -EIO;
}
k_sleep(BQ274XX_SUBCLASS_DELAY);
return 0;
}
static int bq274xx_get_device_type(const struct device *dev, uint16_t *val)
{
int ret;
@ -130,7 +116,7 @@ static int bq274xx_get_device_type(const struct device *dev, uint16_t *val)
return -EIO;
}
ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_CONTROL_LOW, val);
ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_CONTROL, val);
if (ret < 0) {
LOG_ERR("Unable to read register");
return -EIO;
@ -139,21 +125,142 @@ static int bq274xx_get_device_type(const struct device *dev, uint16_t *val)
return 0;
}
static int bq274xx_read_block(const struct device *dev,
uint8_t subclass,
uint8_t *block, uint8_t num_bytes)
{
const struct bq274xx_config *const config = dev->config;
int ret;
ret = i2c_reg_write_byte_dt(&config->i2c, BQ274XX_EXT_DATA_CLASS, subclass);
if (ret < 0) {
LOG_ERR("Failed to update state subclass");
return -EIO;
}
/* DataBlock(), 0 for the first 32 bytes. */
ret = i2c_reg_write_byte_dt(&config->i2c, BQ274XX_EXT_DATA_BLOCK, 0x00);
if (ret < 0) {
LOG_ERR("Failed to update block offset");
return -EIO;
}
k_sleep(BQ274XX_SUBCLASS_DELAY);
ret = i2c_burst_read_dt(&config->i2c, BQ274XX_EXT_BLKDAT_START, block, num_bytes);
if (ret < 0) {
LOG_ERR("Unable to read block data");
return -EIO;
}
return 0;
}
static int bq274xx_write_block(const struct device *dev,
uint8_t *block, uint8_t num_bytes)
{
const struct bq274xx_config *const config = dev->config;
uint8_t checksum = 0;
int ret;
uint8_t buf[1 + BQ27XXX_DM_SZ];
__ASSERT_NO_MSG(num_bytes <= BQ27XXX_DM_SZ);
buf[0] = BQ274XX_EXT_BLKDAT_START;
memcpy(&buf[1], block, num_bytes);
ret = i2c_write_dt(&config->i2c, buf, 1 + num_bytes);
if (ret < 0) {
LOG_ERR("Unable to write block data");
return -EIO;
}
for (uint8_t i = 0; i < num_bytes; i++) {
checksum += block[i];
}
checksum = 0xff - checksum;
ret = i2c_reg_write_byte_dt(&config->i2c, BQ274XX_EXT_CHECKSUM, checksum);
if (ret < 0) {
LOG_ERR("Failed to update block checksum");
return -EIO;
}
k_sleep(BQ274XX_SUBCLASS_DELAY);
return 0;
}
static void bq274xx_update_block(uint8_t *block,
uint8_t offset, uint16_t val,
bool *block_modified)
{
uint16_t old_val;
old_val = sys_get_be16(&block[offset]);
LOG_DBG("update block: off=%d old=%d new=%d\n", offset, old_val, val);
if (val == old_val) {
return;
}
sys_put_be16(val, &block[offset]);
*block_modified = true;
}
static int bq274xx_mode_cfgupdate(const struct device *dev, bool enabled)
{
uint16_t flags;
uint8_t try;
int ret;
uint16_t val = enabled ? BQ274XX_CTRL_SET_CFGUPDATE : BQ274XX_CTRL_SOFT_RESET;
bool enabled_flag;
ret = bq274xx_ctrl_reg_write(dev, val);
if (ret < 0) {
LOG_ERR("Unable to set device mode to %02x", val);
return -EIO;
}
for (try = 0; try < BQ274XX_CFGUP_MAX_TRIES; try++) {
ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_FLAGS, &flags);
if (ret < 0) {
LOG_ERR("Unable to read flags");
return -EIO;
}
enabled_flag = !!(flags & BQ27XXX_FLAG_CFGUP);
if (enabled_flag == enabled) {
LOG_DBG("CFGUP ready, try %u", try);
break;
}
k_sleep(BQ274XX_CFGUP_DELAY);
}
if (try >= BQ274XX_CFGUP_MAX_TRIES) {
LOG_ERR("Config mode change timeout");
return -EIO;
}
return 0;
}
static int bq274xx_gauge_configure(const struct device *dev)
{
const struct bq274xx_config *const config = dev->config;
struct bq274xx_data *data = dev->data;
const struct bq274xx_regs *regs = data->regs;
int ret;
uint8_t tmp_checksum, checksum_old, checksum_new;
uint16_t flags, designenergy_mwh, taperrate, reg_val;
uint16_t designenergy_mwh, taperrate;
uint8_t block[BQ27XXX_DM_SZ];
uint8_t try;
bool block_modified = false;
designenergy_mwh = (uint32_t)config->design_capacity * 37 / 10; /* x3.7 */
taperrate = config->design_capacity * 10 / config->taper_current;
/* Unseal the battery control register */
ret = bq274xx_ctrl_reg_write(dev, BQ274XX_UNSEAL_KEY_A);
if (ret < 0) {
LOG_ERR("Unable to unseal the battery");
@ -166,30 +273,9 @@ static int bq274xx_gauge_configure(const struct device *dev)
return -EIO;
}
/* Send CFG_UPDATE */
ret = bq274xx_ctrl_reg_write(dev, BQ274XX_CTRL_SET_CFGUPDATE);
ret = bq274xx_mode_cfgupdate(dev, true);
if (ret < 0) {
LOG_ERR("Unable to set CFGUpdate");
return -EIO;
}
/* Step to place the Gauge into CONFIG UPDATE Mode */
try = 100;
do {
ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_FLAGS, &flags);
if (ret < 0) {
LOG_ERR("Unable to read flags");
return -EIO;
}
if (!(flags & BQ27XXX_FLAG_CFGUP)) {
k_sleep(BQ274XX_CFGUP_DELAY);
}
} while (!(flags & BQ27XXX_FLAG_CFGUP) && --try);
if (!try) {
LOG_ERR("Config mode change timeout");
return -EIO;
return ret;
}
ret = i2c_reg_write_byte_dt(&config->i2c, BQ274XX_EXT_DATA_CONTROL, 0x00);
@ -198,97 +284,41 @@ static int bq274xx_gauge_configure(const struct device *dev)
return -EIO;
}
/* Access State subclass */
ret = i2c_reg_write_byte_dt(&config->i2c, BQ274XX_EXT_DATA_CLASS, 0x52);
ret = bq274xx_read_block(dev, BQ274XX_SUBCLASS_82, block, sizeof(block));
if (ret < 0) {
LOG_ERR("Failed to update state subclass");
return -EIO;
return ret;
}
/* Write the block offset */
ret = i2c_reg_write_byte_dt(&config->i2c, BQ274XX_EXT_DATA_BLOCK, 0x00);
bq274xx_update_block(block,
regs->dm_design_capacity, config->design_capacity,
&block_modified);
bq274xx_update_block(block,
regs->dm_design_energy, designenergy_mwh,
&block_modified);
bq274xx_update_block(block,
regs->dm_terminate_voltage, config->terminate_voltage,
&block_modified);
bq274xx_update_block(block,
regs->dm_taper_rate, taperrate,
&block_modified);
if (block_modified) {
LOG_INF("bq274xx: updating fuel gauge parameters");
ret = bq274xx_write_block(dev, block, sizeof(block));
if (ret < 0) {
LOG_ERR("Failed to update block offset");
return -EIO;
return ret;
}
ret = bq274xx_read_data_block(dev, 0, block, sizeof(block));
ret = bq274xx_mode_cfgupdate(dev, false);
if (ret < 0) {
LOG_ERR("Unable to read block data");
return -EIO;
return ret;
}
}
tmp_checksum = 0;
for (uint8_t i = 0; i < ARRAY_SIZE(block); i++) {
tmp_checksum += block[i];
}
tmp_checksum = 255 - tmp_checksum;
/* Read the block checksum */
ret = i2c_reg_read_byte_dt(&config->i2c, BQ274XX_EXT_CHECKSUM, &checksum_old);
ret = bq274xx_ctrl_reg_write(dev, BQ274XX_CTRL_SEALED);
if (ret < 0) {
LOG_ERR("Unable to read block checksum");
return -EIO;
}
reg_val = sys_cpu_to_be16(config->design_capacity);
ret = i2c_burst_write_dt(&config->i2c,
BQ274XX_EXT_BLKDAT(regs->dm_design_capacity),
(uint8_t *)&reg_val, sizeof(reg_val));
if (ret < 0) {
LOG_ERR("Failed to write design capacity");
return -EIO;
}
reg_val = sys_cpu_to_be16(designenergy_mwh);
ret = i2c_burst_write_dt(&config->i2c,
BQ274XX_EXT_BLKDAT(regs->dm_design_energy),
(uint8_t *)&reg_val, sizeof(reg_val));
if (ret < 0) {
LOG_ERR("Failed to write design energy");
return -EIO;
}
reg_val = sys_cpu_to_be16(config->terminate_voltage);
ret = i2c_burst_write_dt(&config->i2c,
BQ274XX_EXT_BLKDAT(regs->dm_terminate_voltage),
(uint8_t *)&reg_val, sizeof(reg_val));
if (ret < 0) {
LOG_ERR("Failed to write terminate voltage");
return -EIO;
}
reg_val = sys_cpu_to_be16(taperrate);
ret = i2c_burst_write_dt(&config->i2c,
BQ274XX_EXT_BLKDAT(regs->dm_taper_rate),
(uint8_t *)&reg_val, sizeof(reg_val));
if (ret < 0) {
LOG_ERR("Failed to write taper rate");
return -EIO;
}
ret = bq274xx_read_data_block(dev, 0, block, sizeof(block));
if (ret < 0) {
LOG_ERR("Unable to read block data");
return -EIO;
}
checksum_new = 0;
for (uint8_t i = 0; i < ARRAY_SIZE(block); i++) {
checksum_new += block[i];
}
checksum_new = 255 - checksum_new;
ret = i2c_reg_write_byte_dt(&config->i2c, BQ274XX_EXT_CHECKSUM, checksum_new);
if (ret < 0) {
LOG_ERR("Failed to update new checksum");
return -EIO;
}
tmp_checksum = 0;
ret = i2c_reg_read_byte_dt(&config->i2c, BQ274XX_EXT_CHECKSUM, &tmp_checksum);
if (ret < 0) {
LOG_ERR("Failed to read checksum");
LOG_ERR("Failed to seal the gauge");
return -EIO;
}
@ -298,38 +328,6 @@ static int bq274xx_gauge_configure(const struct device *dev)
return -EIO;
}
ret = bq274xx_ctrl_reg_write(dev, BQ274XX_CTRL_SOFT_RESET);
if (ret < 0) {
LOG_ERR("Failed to soft reset the gauge");
return -EIO;
}
/* Poll Flags */
try = 100;
do {
ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_FLAGS, &flags);
if (ret < 0) {
LOG_ERR("Unable to read flags");
return -EIO;
}
if (flags & BQ27XXX_FLAG_CFGUP) {
k_sleep(BQ274XX_CFGUP_DELAY);
}
} while ((flags & BQ27XXX_FLAG_CFGUP) & --try);
if (!try) {
LOG_ERR("Config mode change timeout");
return -EIO;
}
/* Seal the gauge */
ret = bq274xx_ctrl_reg_write(dev, BQ274XX_CTRL_SEALED);
if (ret < 0) {
LOG_ERR("Failed to seal the gauge");
return -EIO;
}
data->configured = true;
return 0;

View file

@ -17,8 +17,7 @@
#define BQ27427_DEVICE_ID 0x0427
/*** Standard Commands ***/
#define BQ274XX_CMD_CONTROL_LOW 0x00 /* Control() low register */
#define BQ274XX_CMD_CONTROL_HIGH 0x01 /* Control() high register */
#define BQ274XX_CMD_CONTROL 0x00 /* Control() register */
#define BQ274XX_CMD_TEMP 0x02 /* Temperature() */
#define BQ274XX_CMD_VOLTAGE 0x04 /* Voltage() */
#define BQ274XX_CMD_FLAGS 0x06 /* Flags() */