From 43dfbdb3481cd01a498c4e37325bd63729092bbe Mon Sep 17 00:00:00 2001 From: macman88 Date: Fri, 28 Feb 2020 18:17:27 -0600 Subject: [PATCH] Battery monitor support (#404) * Adding support for BQ769x0 Battery Monitor IC (Work In Progress) * Additional changes to support BQ769x0 * Store cell count and chip type when setting up * Added shutdown, limits, charge/discharge switch, and clear faults operations * Added support for current measurement; some cleanup * Updated temperature reporting. Fixed negative current reporting. * When setting safety limits, update limit structure with actual values used. * Added note on battery limit structure * Updates to BQ769x0. Re-ordered fault reporting, added fault cache, added ordered fault clearing --- drivers/power/Kconfig | 28 + drivers/power/Make.defs | 26 + drivers/power/battery_monitor.c | 352 ++++ drivers/power/bq769x0.c | 2245 +++++++++++++++++++++++++ include/crc8.h | 11 + include/nuttx/power/battery_ioctl.h | 8 + include/nuttx/power/battery_monitor.h | 431 +++++ include/nuttx/power/bq769x0.h | 284 ++++ libs/libc/misc/lib_crc8ccitt.c | 36 +- 9 files changed, 3409 insertions(+), 12 deletions(-) create mode 100644 drivers/power/battery_monitor.c create mode 100644 drivers/power/bq769x0.c create mode 100644 include/nuttx/power/battery_monitor.h create mode 100644 include/nuttx/power/bq769x0.h diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 8246ca4aaf2..0eb6b2eb298 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -459,5 +459,33 @@ config I2C_BQ2429X config I2C_MAX1704X bool default y if MAX1704X + +config BATTERY_MONITOR + bool "Battery Monitor/Management support" + default n + +config BQ769X0 + bool "BQ769X0 Battery monitor support" + default n + select I2C + select I2C_BQ769X0 + depends on BATTERY_MONITOR + ---help--- + The BQ76920/BQ76930/BQ76940 provide monitoring, balancing, and + protection features for up to 15 cells in series. + +config I2C_BQ769X0 + bool + default y if BQ769X0 + +if BQ769X0 + config DEBUG_BQ769X0 + bool "BQ769X0 debug features" + default n + + config BQ769X0_USE_INTERNAL_TS + bool "Use internal temperature sensor on BQ769X0" + default n +endif endif # POWER diff --git a/drivers/power/Make.defs b/drivers/power/Make.defs index 25cd82483af..928088445db 100644 --- a/drivers/power/Make.defs +++ b/drivers/power/Make.defs @@ -166,6 +166,32 @@ POWER_CFLAGS := ${shell $(INCDIR) $(INCDIROPT) "$(CC)" $(TOPDIR)$(DELIM)drivers$ endif +# Add battery monitor drivers + +ifeq ($(CONFIG_BATTERY_MONITOR),y) + +CSRCS += battery_monitor.c + +# Add I2C-based battery monitor drivers + +ifeq ($(CONFIG_I2C),y) + +# Add the BQ769x0 I2C-based battery monitor driver + +ifeq ($(CONFIG_I2C_BQ769X0),y) +CSRCS += bq769x0.c +endif + +endif + +# Include battery suport in the build + +POWER_DEPPATH := --dep-path power +POWER_VPATH := :power +POWER_CFLAGS := ${shell $(INCDIR) $(INCDIROPT) "$(CC)" $(TOPDIR)$(DELIM)drivers$(DELIM)power} + +endif + # Include power management in the build DEPPATH += $(POWER_DEPPATH) diff --git a/drivers/power/battery_monitor.c b/drivers/power/battery_monitor.c new file mode 100644 index 00000000000..22489e36904 --- /dev/null +++ b/drivers/power/battery_monitor.c @@ -0,0 +1,352 @@ +/**************************************************************************** + * drivers/power/battery_monitor.c + * Upper-half, character driver for battery manager & monitor ICs. + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include +#include +#include + +/* This driver requires: + * + * CONFIG_BATTERY_MONITOR - Upper half battery driver support + */ + +#if defined(CONFIG_BATTERY_MONITOR) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Character driver methods */ + +static int bat_monitor_open(FAR struct file *filep); +static int bat_monitor_close(FAR struct file *filep); +static ssize_t bat_monitor_read(FAR struct file *filep, FAR char *buffer, + size_t buflen); +static ssize_t bat_monitor_write(FAR struct file *filep, + FAR const char *buffer, size_t buflen); +static int bat_monitor_ioctl(FAR struct file *filep, int cmd, + unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations g_batteryops = +{ + bat_monitor_open, + bat_monitor_close, + bat_monitor_read, + bat_monitor_write, + NULL, + bat_monitor_ioctl, + NULL +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: bat_monitor_open + * + * Description: + * This function is called whenever the battery device is opened. + * + ****************************************************************************/ + +static int bat_monitor_open(FAR struct file *filep) +{ + return OK; +} + +/**************************************************************************** + * Name: bat_monitor_close + * + * Description: + * This routine is called when the battery device is closed. + * + ****************************************************************************/ + +static int bat_monitor_close(FAR struct file *filep) +{ + return OK; +} + +/**************************************************************************** + * Name: bat_monitor_read + ****************************************************************************/ + +static ssize_t bat_monitor_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + /* Return nothing read */ + + return 0; +} + +/**************************************************************************** + * Name: bat_monitor_write + ****************************************************************************/ + +static ssize_t bat_monitor_write(FAR struct file *filep, + FAR const char *buffer, size_t buflen) +{ + /* Return nothing written */ + + return 0; +} + +/**************************************************************************** + * Name: bat_monitor_ioctl + ****************************************************************************/ + +static int bat_monitor_ioctl(FAR struct file *filep, int cmd, + unsigned long arg) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct battery_monitor_dev_s *dev = inode->i_private; + int ret; + + /* Enforce mutually exclusive access to the battery driver */ + + ret = nxsem_wait(&dev->batsem); + if (ret < 0) + { + return ret; /* Probably -EINTR */ + } + + /* Process the IOCTL command */ + + ret = -EINVAL; /* Assume a bad argument */ + switch (cmd) + { + case BATIOC_STATE: + { + FAR int *ptr = (FAR int *)((uintptr_t)arg); + if (ptr) + { + ret = dev->ops->state(dev, ptr); + } + } + break; + + case BATIOC_HEALTH: + { + FAR int *ptr = (FAR int *)((uintptr_t)arg); + if (ptr) + { + ret = dev->ops->health(dev, ptr); + } + } + break; + + case BATIOC_ONLINE: + { + FAR bool *ptr = (FAR bool *)((uintptr_t)arg); + if (ptr) + { + ret = dev->ops->online(dev, ptr); + } + } + break; + + case BATIOC_VOLTAGE: + { + FAR int *voltsp = (FAR int *)((uintptr_t)arg); + if (voltsp) + { + ret = dev->ops->voltage(dev, voltsp); + } + } + break; + + case BATIOC_CURRENT: + { + FAR struct battery_monitor_current_s *current = + (FAR struct battery_monitor_current_s *)((uintptr_t)arg); + if (current) + { + ret = dev->ops->current(dev, current); + } + } + break; + + case BATIOC_OPERATE: + { + FAR int *ptr = (FAR int *)((uintptr_t)arg); + if (ptr) + { + ret = dev->ops->operate(dev, (uintptr_t)arg); + } + } + break; + + case BATIOC_CELLVOLTAGE: + { + FAR struct battery_monitor_voltage_s *ptr = + (FAR struct battery_monitor_voltage_s *)((uintptr_t)arg); + if (ptr) + { + ret = dev->ops->cell_voltage(dev, ptr); + } + } + break; + + case BATIOC_BALANCE: + { + FAR struct battery_monitor_balance_s *ptr = + (FAR struct battery_monitor_balance_s *)((uintptr_t)arg); + if (ptr) + { + ret = dev->ops->balance(dev, ptr); + } + } + break; + + case BATIOC_SHUTDOWN: + { + /* Options are not currently used by shutdown */ + + ret = dev->ops->shutdown(dev, (uintptr_t)arg); + } + break; + + case BATIOC_SETLIMITS: + { + FAR struct battery_monitor_limits_s *ptr = + (FAR struct battery_monitor_limits_s *)((uintptr_t)arg); + if (ptr) + { + ret = dev->ops->setlimits(dev, ptr); + } + } + break; + + case BATIOC_CHGDSG: + { + FAR struct battery_monitor_switches_s *ptr = + (FAR struct battery_monitor_switches_s *)((uintptr_t)arg); + if (ptr) + { + ret = dev->ops->chgdsg(dev, ptr); + } + } + break; + + case BATIOC_CLEARFAULTS: + { + /* Options are not currently used by clear faults operation */ + + ret = dev->ops->clearfaults(dev, (uintptr_t)arg); + } + break; + + case BATIOC_TEMPERATURE: + { + FAR struct battery_monitor_temperature_s *ptr = + (FAR struct battery_monitor_temperature_s *)((uintptr_t)arg); + if (ptr) + { + ret = dev->ops->temperature(dev, ptr); + } + } + break; + + case BATIOC_COULOMBS: + { + FAR int *coulombp = (FAR int *)((uintptr_t)arg); + if (coulombp) + { + ret = dev->ops->coulombs(dev, coulombp); + } + } + break; + + default: + _err("ERROR: Unrecognized cmd: %d\n", cmd); + ret = -ENOTTY; + break; + } + + nxsem_post(&dev->batsem); + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: battery_monitor_register + * + * Description: + * Register a lower half battery driver with the common, upper-half + * battery driver. + * + * Input Parameters: + * devpath - The location in the pseudo-filesystem to create the driver. + * Recommended standard is "/dev/bat0", "/dev/bat1", etc. + * dev - An instance of the battery state structure . + * + * Returned Value: + * Zero on success or a negated errno value on failure. + * + ****************************************************************************/ + +int battery_monitor_register(FAR const char *devpath, + FAR struct battery_monitor_dev_s *dev) +{ + int ret; + + /* Initialize the semaphore */ + + nxsem_init(&dev->batsem, 0, 1); + + /* Register the character driver */ + + ret = register_driver(devpath, &g_batteryops, 0555, dev); + if (ret < 0) + { + _err("ERROR: Failed to register driver: %d\n", ret); + } + + return ret; +} +#endif /* CONFIG_BATTERY_MONITOR */ diff --git a/drivers/power/bq769x0.c b/drivers/power/bq769x0.c new file mode 100644 index 00000000000..f3123fde8b3 --- /dev/null +++ b/drivers/power/bq769x0.c @@ -0,0 +1,2245 @@ +/**************************************************************************** + * drivers/power/bq769x0.c + * Lower half driver for BQ769x0 battery monitor + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/* The bq76920/bq76930/bq76940 battery monitor ICs provide voltage, current, + * and temperature monitoring of up to 15-series cells. These ICs also + * provide Coulomb counting for state-of-charge measurement, balance drivers + * for all cells, and drivers for external cell protection switches. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +/* This driver requires: + * + * CONFIG_BATTERY_MONITOR- Upper half battery driver support + * CONFIG_I2C - I2C support + * CONFIG_I2C_BQ769X0 - And the driver must be explicitly selected. + */ + +#if defined(CONFIG_BATTERY_MONITOR) && defined(CONFIG_I2C) && \ + defined(CONFIG_I2C_BQ769X0) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Helpers ******************************************************************/ + +#ifndef MIN +# define MIN(a,b) (a < b ? a : b) +#endif +#ifndef MAX +# define MAX(a,b) (a > b ? a : b) +#endif + +/* The CRC function expects to see address bytes as they appear on the wire */ + +#define WR_ADDR(a) ((a) << 1) +#define RD_ADDR(a) (((a) << 1) | 1) + +/* Debug ********************************************************************/ + +#ifdef CONFIG_DEBUG_BQ769X0 +# define baterr _err +# define batreg _err +# define batinfo _info +#else +# ifdef CONFIG_CPP_HAVE_VARARGS +# define baterr(x...) +# define batreg(x...) +# define batinfo(x...) +# else +# define baterr(void) +# define batreg(void) +# define batinfo(void) +# endif +#endif + +/**************************************************************************** + * Private + ****************************************************************************/ + +struct bq769x0_dev_s +{ + /* The common part of the battery driver visible to the upper-half driver */ + + FAR const struct battery_monitor_operations_s *ops; /* Battery operations */ + sem_t batsem; /* Enforce mutually exclusive access */ + + /* Data fields specific to the lower half BQ769x0 driver follow */ + + FAR struct i2c_master_s *i2c; /* I2C interface */ + uint8_t addr; /* I2C address */ + uint8_t chip; /* Chip Type (e.g. CHIP_76920) */ + uint8_t cellcount; /* Number of cells attached to chip */ + uint8_t fault_cache; /* Cache of last-read fault bits */ + uint32_t frequency; /* I2C frequency */ + uint32_t gain; /* ADC gain value in uV */ + uint32_t offset; /* ADC offset value in uV */ + uint32_t sense_r; /* Current sense resistor, in uOhm */ + const uint8_t *mapping; /* Pointer to cell mapping table */ + bool crc; /* True if the device has CRC enabled */ +}; + +/* Cell mapping tables + * Some channels are not used depending on how many cells are connected + * to the BQ769X0. These tables map cell number (array index) to physical + * cell channel (array value). See TI datasheet for cell connections table. + */ + +static const uint8_t bq76920_3cell_mapping[] = + { + 0, 1, 4 + }; +static const uint8_t bq76920_4cell_mapping[] = + { + 0, 1, 2, 4 + }; +static const uint8_t bq76920_5cell_mapping[] = + { + 0, 1, 2, 3, 4 + }; +static const uint8_t *bq76920_cell_mapping[] = + { + bq76920_3cell_mapping, + bq76920_4cell_mapping, + bq76920_5cell_mapping + }; + +static const uint8_t bq76930_6cell_mapping[] = +{ + 0, 1, 4, 5, 6, 9 +}; +static const uint8_t bq76930_7cell_mapping[] = +{ + 0, 1, 2, 4, 5, 6, 9 +}; +static const uint8_t bq76930_8cell_mapping[] = +{ + 0, 1, 2, 4, 5, 6, 7, 9 +}; +static const uint8_t bq76930_9cell_mapping[] = +{ + 0, 1, 2, 3, 4, 5, 6, 7, 9 +}; +static const uint8_t bq76930_10cell_mapping[] = +{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 +}; +static const uint8_t *bq76930_cell_mapping[] = + { + bq76930_6cell_mapping, + bq76930_7cell_mapping, + bq76930_8cell_mapping, + bq76930_9cell_mapping, + bq76930_10cell_mapping + }; + +static const uint8_t bq76940_9cell_mapping[] = + { + 0, 1, 4, 5, 6, 9, 10, 11, 14 + }; +static const uint8_t bq76940_10cell_mapping[] = + { + 0, 1, 2, 4, 5, 6, 9, 10, 11, 14 + }; +static const uint8_t bq76940_11cell_mapping[] = + { + 0, 1, 2, 4, 5, 6, 7, 9, 10, 11, 14 + }; +static const uint8_t bq76940_12cell_mapping[] = + { + 0, 1, 2, 4, 5, 6, 7, 9, 10, 11, 12, 14 + }; +static const uint8_t bq76940_13cell_mapping[] = + { + 0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 14 + }; +static const uint8_t bq76940_14cell_mapping[] = + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14 + }; +static const uint8_t bq76940_15cell_mapping[] = + + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 + }; +static const uint8_t *bq76940_cell_mapping[] = + { + bq76940_9cell_mapping, + bq76940_10cell_mapping, + bq76940_11cell_mapping, + bq76940_12cell_mapping, + bq76940_13cell_mapping, + bq76940_14cell_mapping, + bq76940_15cell_mapping, + }; + +/* Current sense limit mapping tables + * Maps a voltage threshold (in mV, array value) + * to a register field value (array index) + * The last values in each list are somewhat arbitrary upper bounds - + * The algorithm rounds down when selecting a register value + */ + +static const uint8_t ocd_t_rsns_0_limits[] = + { + 8, 11, 14, 17, 19, 22, 25, 28, 31, 33, 36, 39, 42, 44, 47, 50, 53 + }; +static const uint8_t ocd_t_rsns_1_limits[] = + { + 17, 22, 28, 33, 39, 44, 50, 56, 61, 67, 72, 78, 83, 89, 94, 100, 106 + }; +static const uint8_t scd_t_rsns_0_limits[] = + { + 22, 33, 44, 56, 67, 78, 89, 100, 105 + }; +static const uint8_t scd_t_rsns_1_limits[] = + { + 44, 67, 89, 111, 133, 155, 178, 200, 210 + }; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* I2C support functions */ + +static int bq769x0_getreg8(FAR struct bq769x0_dev_s *priv, uint8_t regaddr, + FAR uint8_t *regval); +static int bq769x0_putreg8(FAR struct bq769x0_dev_s *priv, uint8_t regaddr, + uint8_t regval); +static int bq769x0_getreg16(FAR struct bq769x0_dev_s *priv, uint8_t regaddr, + FAR uint16_t *regval); +static int bq769x0_getnreg16(FAR struct bq769x0_dev_s *priv, uint8_t regaddr, + FAR uint16_t *regvals, unsigned int count); + +/* Device functions */ + +static inline int bq769x0_getreport(FAR struct bq769x0_dev_s *priv, + FAR uint8_t *report); +static inline int bq769x0_getvolt(FAR struct bq769x0_dev_s *priv, + FAR int *volts); +static inline int bq769x0_getcurrent(FAR struct bq769x0_dev_s *priv, + FAR struct battery_monitor_current_s *current); +static inline int bq769x0_getcellvolt(FAR struct bq769x0_dev_s *priv, + FAR struct battery_monitor_voltage_s *voltages); +static inline int bq769x0_gettemperature(FAR struct bq769x0_dev_s *priv, + FAR struct battery_monitor_temperature_s *temps); +static inline int bq769x0_setbalance(FAR struct bq769x0_dev_s *priv, + FAR struct battery_monitor_balance_s *bal); +static inline int bq769x0_doshutdown(FAR struct bq769x0_dev_s *priv); +static inline int bq769x0_setlimits(FAR struct bq769x0_dev_s *priv, + FAR struct battery_monitor_limits_s *limits); +static inline int bq769x0_setchgdsg(FAR struct bq769x0_dev_s *priv, + FAR struct battery_monitor_switches_s *sw); +static inline int bq769x0_clear_chipfaults(FAR struct bq769x0_dev_s *priv, + uint8_t faults); +static inline int bq769x0_updategain(FAR struct bq769x0_dev_s *priv); +static int bq769x0_chip_cellcount(FAR struct bq769x0_dev_s *priv); + +/* Battery driver lower half methods */ + +static int bq769x0_state(struct battery_monitor_dev_s *dev, int *status); +static int bq769x0_health(struct battery_monitor_dev_s *dev, int *health); +static int bq769x0_online(struct battery_monitor_dev_s *dev, bool *status); +static int bq769x0_voltage(struct battery_monitor_dev_s *dev, int *value); +static int bq769x0_cellvoltage(struct battery_monitor_dev_s *dev, + struct battery_monitor_voltage_s *cellv); +static int bq769x0_current(struct battery_monitor_dev_s *dev, + struct battery_monitor_current_s *current); +static int bq769x0_soc(struct battery_monitor_dev_s *dev, b16_t *value); +static int bq769x0_coulombs(struct battery_monitor_dev_s *dev, + int *coulombs); +static int bq769x0_temp(struct battery_monitor_dev_s *dev, + struct battery_monitor_temperature_s *temps); +static int bq769x0_balance(struct battery_monitor_dev_s *dev, + struct battery_monitor_balance_s *bal); +static int bq769x0_shutdown(struct battery_monitor_dev_s *dev, + uintptr_t param); +static int bq769x0_limits(struct battery_monitor_dev_s *dev, + struct battery_monitor_limits_s *limits); +static int bq769x0_chgdsg(struct battery_monitor_dev_s *dev, + struct battery_monitor_switches_s *sw); +static int bq769x0_clearfaults(struct battery_monitor_dev_s *dev, + uintptr_t param); +static int bq769x0_operate(struct battery_monitor_dev_s *dev, + uintptr_t param); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct battery_monitor_operations_s g_bq769x0ops = +{ + bq769x0_state, + bq769x0_health, + bq769x0_online, + bq769x0_voltage, + bq769x0_cellvoltage, + bq769x0_current, + bq769x0_soc, + bq769x0_coulombs, + bq769x0_temp, + bq769x0_balance, + bq769x0_shutdown, + bq769x0_limits, + bq769x0_chgdsg, + bq769x0_clearfaults, + bq769x0_operate, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: bq769x0_getreg8 + * + * Description: + * Read a 8-bit value from a BQ769x0 register. + * + * START ACK ACK + * REPEATED-START ACK Data0 NO-ACK STOP + * + ****************************************************************************/ + +static int bq769x0_getreg8(FAR struct bq769x0_dev_s *priv, uint8_t regaddr, + FAR uint8_t *regval) +{ + struct i2c_config_s config; + uint8_t val[2]; + int ret; + int datalen; + uint8_t sl_addr; + uint8_t crc; + + /* Set up the I2C configuration */ + + config.frequency = priv->frequency; + config.address = priv->addr; + config.addrlen = 7; + + /* Write the register address */ + + ret = i2c_write(priv->i2c, &config, ®addr, 1); + if (ret < 0) + { + baterr("ERROR: i2c_write failed: %d\n", ret); + return ret; + } + + /* Our expected data length varies depending on whether or not a CRC is used */ + + if (priv->crc) + { + datalen = 2; + } + else + { + datalen = 1; + } + + /* Restart and read 8-bits from the register */ + + ret = i2c_read(priv->i2c, &config, val, datalen); + if (ret < 0) + { + baterr("ERROR: i2c_read failed: %d\n", ret); + return ret; + } + + /* If CRC is used, verify that it is correct */ + + if (priv->crc) + { + sl_addr = RD_ADDR(priv->addr); + crc = crc8ccittpart(&sl_addr, 1, 0); + crc = crc8ccittpart(val, 1, crc); + if (crc != val[1]) + { + baterr("ERROR: CRC mismatch: Got %02x, Expected %02x\n", val[1], + crc); + return ERROR; + } + } + + /* Copy 8-bit value to be returned */ + + *regval = val[0]; + return OK; +} + +/**************************************************************************** + * Name: bq769x0_putreg8 + * + * Description: + * Write a 8-bit value to a BQ769x0 register. + * + * START ACK ACK Data0 ACK STOP + * + ****************************************************************************/ + +static int bq769x0_putreg8(FAR struct bq769x0_dev_s *priv, uint8_t regaddr, + uint8_t regval) +{ + struct i2c_config_s config; + uint8_t buffer[3]; + int datalen; + uint8_t sl_addr; + uint8_t crc; + + /* Set up the I2C configuration */ + + config.frequency = priv->frequency; + config.address = priv->addr; + config.addrlen = 7; + + batreg("addr: %02x regval: %02x\n", regaddr, regval); + + /* Set up a 3 byte message to send */ + + buffer[0] = regaddr; + buffer[1] = regval; + + /* Our expected data length varies depending on whether or not a CRC is used */ + + if (priv->crc) + { + datalen = 3; + sl_addr = WR_ADDR(priv->addr); + crc = crc8ccittpart(&sl_addr, 1, 0); + crc = crc8ccittpart(buffer, 2, crc); + buffer[2] = crc; + batreg("write crc: %02x\n", crc); + } + else + { + datalen = 2; + } + + /* Write the register address followed by the data (no RESTART) */ + + return i2c_write(priv->i2c, &config, buffer, datalen); +} + +/**************************************************************************** + * Name: bq769x0_getreg16 + * + * Description: + * Read a 16-bit value from a BQ769x0 register pair. + * + * START ACK ACK + * REPEATED-START ACK Data0 ACK Data1 NO-ACK STOP + * + ****************************************************************************/ + +static int bq769x0_getreg16(FAR struct bq769x0_dev_s *priv, uint8_t regaddr, + FAR uint16_t *regval) +{ + return bq769x0_getnreg16(priv, regaddr, regval, 1); +} + +/**************************************************************************** + * Name: bq769x0_getnreg16 + * + * Description: + * Read an array of 16-bit values from BQ769x0 register pairs. + * + * START ACK ACK + * REPEATED-START ACK Data0 ACK Data1 NO-ACK STOP + * + * count is number of 16-bit words to read + * + ****************************************************************************/ + +static int bq769x0_getnreg16(FAR struct bq769x0_dev_s *priv, uint8_t regaddr, + FAR uint16_t *regvals, unsigned int count) +{ + struct i2c_config_s config; + uint8_t tmp_val[(4 * 22)]; /* Maximum of 22 registers per read with CRC */ + int ret; + int datalen; + int byte_count; + uint8_t sl_addr; + uint8_t crc; + int i; + + /* Make sure specified number of registers will fit in our buffer. + * If not, limit read to the available buffer size + */ + + if (priv->crc) + { + if (count >= (sizeof(tmp_val) / 4)) + { + count = sizeof(tmp_val) / 4; + } + } + else + { + if (count >= (sizeof(tmp_val) / 2)) + { + count = sizeof(tmp_val) / 2; + } + } + + /* Set up the I2C configuration */ + + config.frequency = priv->frequency; + config.address = priv->addr; + config.addrlen = 7; + + /* Write the register address */ + + ret = i2c_write(priv->i2c, &config, ®addr, 1); + if (ret < 0) + { + baterr("ERROR: i2c_write failed: %d\n", ret); + return ret; + } + + byte_count = 2 * count; + + /* Our expected I2C data length varies depending on whether or not a CRC is used */ + + if (priv->crc) + { + /* When reading multiple bytes, there is 1 CRC byte per data byte */ + + datalen = (4 * count); + } + else + { + datalen = byte_count; + } + + /* Restart and read 16-bits from the register */ + + ret = i2c_read(priv->i2c, &config, tmp_val, datalen); + if (ret < 0) + { + baterr("ERROR: i2c_read failed: %d\n", ret); + return ret; + } + + /* If CRC is used, verify that it is correct + * We only include the address with the first data byte. + * After that, we compare the CRC of each byte with its following byte + */ + + if (priv->crc) + { + sl_addr = RD_ADDR(priv->addr); + crc = crc8ccittpart(&sl_addr, 1, 0); + for (i = 0; i < byte_count; i += 2) + { + crc = crc8ccittpart(&tmp_val[i], 1, crc); + if (crc != tmp_val[i + 1]) + { + baterr("ERROR: CRC mismatch: Got %02x, Expected %02x\n", + tmp_val[2], crc); + return ERROR; + } + + crc = 0; + } + + /* Copy 16-bit values to be returned, skipping CRC bytes*/ + + for (i = 0; i < datalen; i += 4) + { + *regvals = (uint16_t)tmp_val[i] << 8 | (uint16_t)tmp_val[i + 2]; + regvals += 1; + } + } + else + { + /* Copy 16-bit values to be returned */ + + for (i = 0; i < datalen; i += 2) + { + *regvals = (uint16_t)tmp_val[i] << 8 | (uint16_t)tmp_val[i + 1]; + regvals += 1; + } + } + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_getreport + * + * Description: + * Read the BQ769X0 SYS_STAT register + * + ****************************************************************************/ + +static inline int bq769x0_getreport(FAR struct bq769x0_dev_s *priv, + uint8_t *report) +{ + uint8_t regval = 0; + int ret; + + ret = bq769x0_getreg8(priv, BQ769X0_REG_SYS_STAT, ®val); + if (ret == OK) + { + *report = regval; + } + + return ret; +} + +/**************************************************************************** + * Name: bq769x0_updategain + * + * Description: + * Updates the local copies of the BQ769x0 ADC gain registers. + * These are used when converting ADC values to actual voltages. + * + ****************************************************************************/ + +static inline int bq769x0_updategain(FAR struct bq769x0_dev_s *priv) +{ + int ret; + uint8_t gainreg1; + uint8_t gainreg2; + uint8_t gain; + int8_t offset; + + /* Read current register values */ + + ret = bq769x0_getreg8(priv, BQ769X0_REG_ADCGAIN1, &gainreg1); + if (ret < 0) + { + baterr("ERROR: Error reading from BQ769X0! Error = %d\n", ret); + return ret; + } + + ret = bq769x0_getreg8(priv, BQ769X0_REG_ADCGAIN2, &gainreg2); + if (ret < 0) + { + baterr("ERROR: Error reading from BQ769X0! Error = %d\n", ret); + return ret; + } + + ret = bq769x0_getreg8(priv, BQ769X0_REG_ADCOFFSET, (uint8_t *)&offset); + if (ret < 0) + { + baterr("ERROR: Error reading from BQ769X0! Error = %d\n", ret); + return ret; + } + + /* Calculate actual gain & offset values + * gainreg1 contains gain bits 4-3 + * gainreg2 contains gain bits 2-0 + */ + + gainreg1 &= BQ769X0_ADCGAIN1_MASK; + gainreg2 &= BQ769X0_ADCGAIN2_MASK; + gain = (gainreg1 << 1) | (gainreg2 >> 5); + + priv->gain = gain + BQ769X0_BASE_GAIN; + priv->offset = offset * 1000; /* Convert mV to uV */ + + batinfo("Battery monitor gain: %d uV/LSB, offset: %d uV.\n", priv->gain, + priv->offset); + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_do_shutdown + * + * Description: + * Put the device into a low-power SHIP mode. + * External hardware may be required to wake the device up from this state. + * + ****************************************************************************/ + +static inline int bq769x0_doshutdown(FAR struct bq769x0_dev_s *priv) +{ + int ret; + uint8_t regval; + + /* Read current register value */ + + ret = bq769x0_getreg8(priv, BQ769X0_REG_SYS_CTRL1, ®val); + if (ret < 0) + { + baterr("ERROR: Error reading from BQ769X0! Error = %d\n", ret); + return ret; + } + + /* Mask off the writeable bits */ + + regval &= BQ769X0_SYS_CTRL1_WRITE_MASK; + + /* Set SHUT_A and SHUT_B to 0 */ + + regval &= ~BQ769X0_SYS_CTRL1_SHUTDOWN_MASK; + + /* Write the shutdown sequence */ + + ret = bq769x0_putreg8(priv, BQ769X0_REG_SYS_CTRL1, regval); + if (ret < 0) + { + baterr("ERROR: Error reading from BQ769X0! Error = %d\n", ret); + return ret; + } + + /* Set SHUT_A to 0 and SHUT_B to 1 */ + + regval |= BQ769X0_SHUT_B; + + ret = bq769x0_putreg8(priv, BQ769X0_REG_SYS_CTRL1, regval); + if (ret < 0) + { + baterr("ERROR: Error writing to BQ769X0! Error = %d\n", ret); + return ret; + } + + /* Set SHUT_A to 1 and SHUT_B to 0 */ + + regval &= ~BQ769X0_SYS_CTRL1_SHUTDOWN_MASK; + regval |= BQ769X0_SHUT_A; + + ret = bq769x0_putreg8(priv, BQ769X0_REG_SYS_CTRL1, regval); + if (ret < 0) + { + baterr("ERROR: Error writing to BQ769X0! Error = %d\n", ret); + return ret; + } + + batinfo("Device should now be in ship mode\n"); + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_setlimits + * + * Description: + * Set the safety cutoff limits of the device. + * + ****************************************************************************/ + +static inline int bq769x0_setlimits(FAR struct bq769x0_dev_s *priv, + struct battery_monitor_limits_s *limits) +{ + int ret; + int i; + uint8_t regval; + uint32_t tripval; + bool rsns_0_scd_found; + bool rsns_1_scd_found; + bool rsns_0_ocd_found; + bool rsns_1_ocd_found; + + uint8_t rsns_0_scd_idx; + uint8_t rsns_1_scd_idx; + uint8_t rsns_0_ocd_idx; + uint8_t rsns_1_ocd_idx; + + regval = 0; + + /* The OCD (Over current in discharge) and SCD + * (Short circuit in discharge) registers are both + * affected by the RSNS bit. We ideally want to find + * a mapping that satisfies both registers for the provided values + * using only a single RSNS value. + */ + + /* Compute overcurrent voltage trip point based on provided + * current trip point. + */ + + tripval = limits->overcurrent_limit * priv->sense_r; + + /* result is in milli-amps * micro-ohms + * e.g. 20A * 5 milli-ohms = 20000 * 5000 = 100000000 + * Divide by 1000000 to get millivolts + */ + + tripval /= 1000000UL; + + batinfo("Overcurrent trip voltage is %d mV\n", tripval); + + /* Now look up overcurrent limit value in the OCD_T + * lookup tables. Check both RSNS = 0 and RSNS = 1 + * so we can make a decision about which one to use. + * Note that limits lower than the minimum will be set to the + * minimum value. + */ + + rsns_0_ocd_found = false; + rsns_1_ocd_found = false; + rsns_0_ocd_idx = 0; + rsns_1_ocd_idx = 0; + for (i = 1; i < sizeof(ocd_t_rsns_0_limits) / + sizeof(ocd_t_rsns_0_limits[0]); i += 1) + { + if (tripval <= ocd_t_rsns_0_limits[i]) + { + rsns_0_ocd_idx = i - 1; /* round down */ + rsns_0_ocd_found = true; + break; + } + } + + for (i = 1; i < sizeof(ocd_t_rsns_1_limits) / + sizeof(ocd_t_rsns_1_limits[0]); i += 1) + { + if (tripval <= ocd_t_rsns_1_limits[i]) + { + rsns_1_ocd_idx = i - 1; /* round down */ + rsns_1_ocd_found = true; + break; + } + } + + if (!rsns_0_ocd_found && !rsns_1_ocd_found) + { + baterr("ERROR: Failed to find suitable value for OCD_T\n"); + return -EINVAL; + } + + /* Compute short circuit voltage trip point based on provided + * current trip point + */ + + tripval = limits->shortcircuit_limit * priv->sense_r; + + /* result is in milli-amps * micro-ohms + * e.g. 20A * 5 milli-ohms = 20000 * 5000 = 100000000 + * Divide by 1000000 to get millivolts + */ + + tripval /= 1000000UL; + batinfo("Short circuit trip voltage is %d mV\n", tripval); + + /* Now look up the short circuit limit value in the SCD_T + * lookup tables. Check both RSNS = 0 and RSNS = 1 + * so we can make a decision about which one to use. + * Note that limits lower than the minimum will be set to the + * minimum value. + */ + + rsns_0_scd_found = false; + rsns_1_scd_found = false; + rsns_0_scd_idx = 0; + rsns_1_scd_idx = 0; + + /* Don't look at the first element since we're rounding down anyway */ + + for (i = 1; i < sizeof(scd_t_rsns_0_limits) / + sizeof(scd_t_rsns_0_limits[0]); i += 1) + { + if (tripval < scd_t_rsns_0_limits[i]) + { + rsns_0_scd_idx = i - 1; /* round down */ + rsns_0_scd_found = true; + break; + } + } + + for (i = 1; i < sizeof(scd_t_rsns_1_limits) / + sizeof(scd_t_rsns_1_limits[0]); i += 1) + { + if (tripval < scd_t_rsns_1_limits[i]) + { + rsns_1_scd_idx = i - 1; /* round down */ + rsns_1_scd_found = true; + break; + } + } + + if (!rsns_0_scd_found && !rsns_1_scd_found) + { + baterr("ERROR: Failed to find suitable value for SCD_T\n"); + return -EINVAL; + } + + /* Now let's figure out RSNS. + * We prefer RSNS = 0 if available, because it gives us finer-grained + * control over the actual trip voltage + */ + + if (rsns_0_ocd_found && rsns_0_scd_found) + { + batinfo("Using RSNS = 0\n"); + batinfo("Using SCD_T %x\n", rsns_0_scd_idx); + regval |= (rsns_0_scd_idx << BQ769X0_SCD_THRESH_SHIFT) & + BQ769X0_SCD_THRESH_MASK; + } + else if (rsns_1_ocd_found && rsns_1_scd_found) + { + batinfo("Using RSNS = 1\n"); + batinfo("Using SCD_T %x\n", rsns_1_scd_idx); + regval |= BQ769X0_RSNS; + regval |= (rsns_1_scd_idx << BQ769X0_SCD_THRESH_SHIFT) & + BQ769X0_SCD_THRESH_MASK; + } + else + { + /* Not possible to meet both trip points with a single RSNS value + * For now, let's call that an error. + */ + + limits->overcurrent_limit = 0; + limits->shortcircuit_limit = 0; + baterr("ERROR: OCD_T and SCD_T could not agree on RSNS.\n"); + return -EINVAL; + } + + /* Configure short circuit delay and threshold. + * Always round down if we are less than the + * next highest value. + * Throw an error if we are out of bounds + * (+/- an arbitrarily-chosen buffer) + */ + + if (limits->shortcircuit_delay < 68) + { + limits->shortcircuit_delay = 0; + baterr("ERROR: Short circuit delay is too short\n"); + return -EINVAL; + } + else if (limits->shortcircuit_delay < 100) + { + limits->shortcircuit_delay = 70; + regval |= BQ769X0_SCD_DELAY_70US; + batinfo("Short circuit delay set to 70uS\n"); + } + else if (limits->shortcircuit_delay < 200) + { + limits->shortcircuit_delay = 100; + regval |= BQ769X0_SCD_DELAY_100US; + batinfo("Short circuit delay set to 100uS\n"); + } + else if (limits->shortcircuit_delay < 400) + { + limits->shortcircuit_delay = 200; + regval |= BQ769X0_SCD_DELAY_200US; + batinfo("Short circuit delay set to 200uS\n"); + } + else if (limits->shortcircuit_delay < 410) + { + limits->shortcircuit_delay = 400; + regval |= BQ769X0_SCD_DELAY_400US; + batinfo("Short circuit delay set to 400uS\n"); + } + else + { + limits->shortcircuit_delay = 0; + baterr("ERROR: Short circuit delay is too long\n"); + return -EINVAL; + } + + ret = bq769x0_putreg8(priv, BQ769X0_REG_PROTECT1, regval); + if (ret < 0) + { + baterr("ERROR: Error writing to BQ769X0! Error = %d\n", ret); + return ret; + } + + /* Configure overcurrent delay and threshold + * Always round down if we are less than the + * next highest value. + * Throw an error if we are out of bounds + * (+/- an arbitrarily-chosen buffer) + */ + + regval = 0; + if (limits->overcurrent_delay < (7 * USEC_PER_MSEC)) + { + limits->overcurrent_delay = 0; + baterr("ERROR: Overcurrent delay is too short\n"); + return -EINVAL; + } + else if (limits->overcurrent_delay < (20 * USEC_PER_MSEC)) + { + limits->overcurrent_delay = 8 * USEC_PER_MSEC; + regval |= BQ769X0_OCD_DELAY_8MS; + batinfo("Overcurrent delay set to 8mS\n"); + } + else if (limits->overcurrent_delay < (40 * USEC_PER_MSEC)) + { + limits->overcurrent_delay = 20 * USEC_PER_MSEC; + regval |= BQ769X0_OCD_DELAY_20MS; + batinfo("Overcurrent delay set to 20mS\n"); + } + else if (limits->overcurrent_delay < (80 * USEC_PER_MSEC)) + { + limits->overcurrent_delay = 40 * USEC_PER_MSEC; + regval |= BQ769X0_OCD_DELAY_40MS; + batinfo("Overcurrent delay set to 40mS\n"); + } + else if (limits->overcurrent_delay < (160 * USEC_PER_MSEC)) + { + limits->overcurrent_delay = 80 * USEC_PER_MSEC; + regval |= BQ769X0_OCD_DELAY_80MS; + batinfo("Overcurrent delay set to 80mS\n"); + } + else if (limits->overcurrent_delay < (320 * USEC_PER_MSEC)) + { + limits->overcurrent_delay = 160 * USEC_PER_MSEC; + regval |= BQ769X0_OCD_DELAY_160MS; + batinfo("Overcurrent delay set to 160mS\n"); + } + else if (limits->overcurrent_delay < (640 * USEC_PER_MSEC)) + { + limits->overcurrent_delay = 320 * USEC_PER_MSEC; + regval |= BQ769X0_OCD_DELAY_320MS; + batinfo("Overcurrent delay set to 320mS\n"); + } + else if (limits->overcurrent_delay < (1280 * USEC_PER_MSEC)) + { + limits->overcurrent_delay = 640 * USEC_PER_MSEC; + regval |= BQ769X0_OCD_DELAY_640MS; + batinfo("Overcurrent delay set to 640mS\n"); + } + else if (limits->overcurrent_delay < 1300 * USEC_PER_MSEC) + { + limits->overcurrent_delay = 1280 * USEC_PER_MSEC; + regval |= BQ769X0_OCD_DELAY_1280MS; + batinfo("Overcurrent delay set to 1280mS\n"); + } + else + { + limits->overcurrent_delay = 0; + baterr("ERROR: Overcurrent delay is too long\n"); + return -EINVAL; + } + + /* If neither rsns_0 or rsns_1 work, we would have + * errored out before this point. + */ + + if (rsns_0_ocd_found) + { + batinfo("Using OCD_T %x\n", rsns_0_ocd_idx); + regval |= (rsns_0_ocd_idx << BQ769X0_OCD_THRESH_SHIFT) & + BQ769X0_OCD_THRESH_MASK; + } + else if (rsns_1_ocd_found) + { + batinfo("Using OCD_T %x\n", rsns_1_ocd_idx); + regval |= (rsns_1_ocd_idx << BQ769X0_OCD_THRESH_SHIFT) & + BQ769X0_OCD_THRESH_MASK; + } + + ret = bq769x0_putreg8(priv, BQ769X0_REG_PROTECT2, regval); + if (ret < 0) + { + baterr("ERROR: Error writing to BQ769X0! Error = %d\n", ret); + return ret; + } + + /* Configure overvoltage and undervoltage delays + * Throw an error if we are out of bounds + * (+/- an arbitrarily-chosen buffer) + */ + + regval = 0; + if (limits->overvoltage_delay < (1 * USEC_PER_SEC)) + { + limits->overvoltage_delay = 0; + baterr("ERROR: overvoltage delay is too short\n"); + return -EINVAL; + } + else if (limits->overvoltage_delay < (2 * USEC_PER_SEC)) + { + limits->overvoltage_delay = 1 * USEC_PER_SEC; + regval |= BQ769X0_OV_DELAY_1S; + batinfo("Overvoltage delay set to 1S\n"); + } + else if (limits->overvoltage_delay < (4 * USEC_PER_SEC)) + { + limits->overvoltage_delay = 2 * USEC_PER_SEC; + regval |= BQ769X0_OV_DELAY_2S; + batinfo("Overvoltage delay set to 2S\n"); + } + else if (limits->overvoltage_delay < (8 * USEC_PER_SEC)) + { + limits->overvoltage_delay = 4 * USEC_PER_SEC; + regval |= BQ769X0_OV_DELAY_4S; + batinfo("Overvoltage delay set to 4S\n"); + } + else if (limits->overvoltage_delay < (10 * USEC_PER_SEC)) + { + limits->overvoltage_delay = 8 * USEC_PER_SEC; + regval |= BQ769X0_OV_DELAY_8S; + batinfo("Overvoltage delay set to 8S\n"); + } + else + { + limits->overvoltage_delay = 0; + baterr("ERROR: overvoltage delay is too long\n"); + return -EINVAL; + } + + if (limits->undervoltage_delay < (1 * USEC_PER_SEC)) + { + limits->undervoltage_delay = 0; + baterr("ERROR: undervoltage delay is too short\n"); + return -EINVAL; + } + else if (limits->undervoltage_delay < (4 * USEC_PER_SEC)) + { + limits->undervoltage_delay = 1 * USEC_PER_SEC; + regval |= BQ769X0_UV_DELAY_1S; + batinfo("Undervoltage delay set to 1S\n"); + } + else if (limits->undervoltage_delay < (8 * USEC_PER_SEC)) + { + limits->undervoltage_delay = 4 * USEC_PER_SEC; + regval |= BQ769X0_UV_DELAY_4S; + batinfo("Undervoltage delay set to 4S\n"); + } + else if (limits->undervoltage_delay < (16 * USEC_PER_SEC)) + { + limits->undervoltage_delay = 8 * USEC_PER_SEC; + regval |= BQ769X0_UV_DELAY_8S; + batinfo("Undervoltage delay set to 8S\n"); + } + else if (limits->undervoltage_delay < (20 * USEC_PER_SEC)) + { + limits->undervoltage_delay = 16 * USEC_PER_SEC; + regval |= BQ769X0_UV_DELAY_16S; + batinfo("Undervoltage delay set to 16S\n"); + } + else + { + limits->undervoltage_delay = 0; + baterr("ERROR: undervoltage delay is too long\n"); + return -EINVAL; + } + + ret = bq769x0_putreg8(priv, BQ769X0_REG_PROTECT3, regval); + if (ret < 0) + { + baterr("ERROR: Error writing to BQ769X0! Error = %d\n", ret); + return ret; + } + + /* Calculate OV_TRIP register value based on provided limit. + * Note that the register format limits the trip range to + * approximately 3.15V to 4.7V + */ + + tripval = (limits->overvoltage_limit - priv->offset) / priv->gain; + tripval >>= 4; + regval = (uint8_t)(tripval & 0xff); + ret = bq769x0_putreg8(priv, BQ769X0_REG_OV_TRIP, regval); + if (ret < 0) + { + baterr("ERROR: Error writing to BQ769X0! Error = %d\n", ret); + return ret; + } + + /* Calculate UV_TRIP register value based on provided limit. + * Note that the register format limits the trip range to + * approximately 1.58V to 3.1V + */ + + tripval = (limits->undervoltage_limit - priv->offset) / priv->gain; + tripval >>= 4; + regval = (uint8_t)(tripval & 0xff); + ret = bq769x0_putreg8(priv, BQ769X0_REG_UV_TRIP, regval); + if (ret < 0) + { + baterr("ERROR: Error writing to BQ769X0! Error = %d\n", ret); + return ret; + } + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_setlimits + * + * Description: + * Set the device's charge/discharge switches + * + ****************************************************************************/ + +static inline int bq769x0_setchgdsg(FAR struct bq769x0_dev_s *priv, + struct battery_monitor_switches_s *sw) +{ + int ret; + uint8_t regval; + + /* Read current register value */ + + ret = bq769x0_getreg8(priv, BQ769X0_REG_SYS_CTRL2, ®val); + if (ret < 0) + { + baterr("ERROR: Error reading from BQ769X0! Error = %d\n", ret); + return ret; + } + + /* Mask off the writeable bits */ + + regval &= BQ769X0_SYS_CTRL2_WRITE_MASK; + + /* Set CHG_ON and DSG_ON */ + + regval &= ~BQ769X0_SYS_CTRL2_CHGDSG_MASK; + if (sw->charge) + { + regval |= BQ769X0_CHG_ON; + batinfo("Turned on charge switch\n"); + } + + if (sw->discharge) + { + regval |= BQ769X0_DSG_ON; + batinfo("Turned on discharge switch\n"); + } + + /* Write the new register value */ + + ret = bq769x0_putreg8(priv, BQ769X0_REG_SYS_CTRL2, regval); + if (ret < 0) + { + baterr("ERROR: Error writing to BQ769X0! Error = %d\n", ret); + return ret; + } + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_clear_chip_faults + * + * Description: + * Clear the specified chip faults + * + ****************************************************************************/ + +static inline int bq769x0_clear_chipfaults(FAR struct bq769x0_dev_s *priv, + uint8_t faults) +{ + int ret; + + batinfo("Clearing battery faults: %02x\n", faults); + + /* Write the new register value */ + + ret = bq769x0_putreg8(priv, BQ769X0_REG_SYS_STAT, faults); + + if (ret < 0) + { + baterr("ERROR: Error writing to BQ769X0! Error = %d\n", ret); + return ret; + } + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_state + * + * Description: + * Return the current battery state + * + ****************************************************************************/ + +static int bq769x0_state(struct battery_monitor_dev_s *dev, int *status) +{ + FAR struct bq769x0_dev_s *priv = (FAR struct bq769x0_dev_s *)dev; + uint8_t regval = 0; + int ret; + + ret = bq769x0_getreport(priv, ®val); + + if (ret < 0) + { + *status = BATTERY_UNKNOWN; + return ret; + } + + if (regval & BQ769X0_FAULT_MASK) + { + *status = BATTERY_FAULT; + } + else + { + *status = BATTERY_IDLE; + } + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_health + * + * Description: + * Return the current battery health state + * + * Note: if more than one fault happened the user will need to + * clear the fault and call this ioctl again to read a new fault, + * repeat until receive a BATTERY_HEALTH_GOOD. + * + ****************************************************************************/ + +static int bq769x0_health(struct battery_monitor_dev_s *dev, int *health) +{ + FAR struct bq769x0_dev_s *priv = (FAR struct bq769x0_dev_s *)dev; + uint8_t regval = 0; + int ret; + + ret = bq769x0_getreport(priv, ®val); + + if (ret < 0) + { + *health = BATTERY_HEALTH_UNKNOWN; + return ret; + } + + priv->fault_cache = regval; + + if (regval & BQ769X0_DEVICE_XREADY) + { + *health = BATTERY_HEALTH_WD_TMR_EXP; + } + else if (regval & BQ769X0_SCD) + { + *health = BATTERY_HEALTH_SHORT_CIRCUIT; + } + else if (regval & BQ769X0_OCD) + { + *health = BATTERY_HEALTH_OVERCURRENT; + } + else if (regval & BQ769X0_OV) + { + *health = BATTERY_HEALTH_OVERVOLTAGE; + } + else if (regval & BQ769X0_UV) + { + *health = BATTERY_HEALTH_UNDERVOLTAGE; + } + else if (regval & BQ769X0_OVRD_ALERT) + { + *health = BATTERY_HEALTH_UNSPEC_FAIL; + } + else + { + *health = BATTERY_HEALTH_GOOD; + } + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_online + * + * Description: + * Return true if the battery is online + * + ****************************************************************************/ + +static int bq769x0_online(struct battery_monitor_dev_s *dev, bool *status) +{ + /* There is no concept of online/offline in this driver */ + + *status = true; + return OK; +} + +/**************************************************************************** + * Name: bq769x0_getvolt + * + * Description: + * Gets the battery stack voltage in uV. + * + ****************************************************************************/ + +static inline int bq769x0_getvolt(FAR struct bq769x0_dev_s *priv, int *volts) +{ + uint16_t regval; + int ret; + + ret = bq769x0_getreg16(priv, BQ769X0_REG_BAT_HI, ®val); + if (ret < 0) + { + baterr("ERROR: Error reading voltage from BQ769X0! Error = %d\n", ret); + return ret; + } + + /* Voltage is returned from the chip in units of uV/LSB + * An offset also needs to be added. + * Reading the two bytes in a single operation guarantees atomic access. + * The pack voltage is divided by 4 in order to fit in a 16-bit register + * Multiply gain by 4, and offset by number of channels on the chip, since + * it is cumulative. See TI appnote SLUUB41. + */ + + *volts = ((uint32_t) regval * priv->gain * 4) + + (priv->offset * bq769x0_chip_cellcount(priv)); + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_getvolt + * + * Description: + * Gets one or more battery cell voltages from the monitor. + * + ****************************************************************************/ + +static inline int bq769x0_getcellvolt(FAR struct bq769x0_dev_s *priv, + struct battery_monitor_voltage_s *voltages) +{ + uint16_t regvals[BQ769X0_MAX_CELLS]; + int ret; + int i; + int cellsread; + + if (voltages) + { + /* Check how many cells were requested. If more than available, + * overwrite with the number available. + */ + + if (voltages->cell_count > priv->cellcount) + { + voltages->cell_count = priv->cellcount; + } + } + + /* Due to gaps in cell voltages when the whole stack is not filled, + * We'll read the maximum number of cells supported by the chip + * and discard what we don't need. + */ + + cellsread = bq769x0_chip_cellcount(priv); + ret = bq769x0_getnreg16(priv, BQ769X0_REG_VC1_HI, regvals, cellsread); + if (ret < 0) + { + baterr("ERROR: Error reading from BQ769X0! Error = %d\n", ret); + return ret; + } + + for (i = 0; i < voltages->cell_count; i += 1) + { + /* Voltage is returned from the chip in units of uV/LSB + * An offset also needs to be added. + * We use the mapping table to determine mapping between cell number + * and ADC channel + */ + + voltages->cell_voltages[i] = ((uint32_t) regvals[priv->mapping[i]] * + priv->gain) + priv->offset; + } + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_gettemperature + * + * Description: + * Gets the voltage(s) at the temperature sensor input(s) of the chip + * It is up to the user to convert these voltage values into temperature + * values, as many types of temperature sensors exist. + * + ****************************************************************************/ + +static inline int bq769x0_gettemperature(FAR struct bq769x0_dev_s *priv, + struct battery_monitor_temperature_s *temps) +{ + int chip_sensors; + int ret; + int i; + uint16_t regvals[3]; + + /* The number of temperature registers varies depending on the + * chip variant + */ + + switch (priv->chip) + { + case CHIP_BQ76920: + chip_sensors = BQ76920_TEMP_COUNT; + break; + case CHIP_BQ76930: + chip_sensors = BQ76930_TEMP_COUNT; + break; + default: + case CHIP_BQ76940: + chip_sensors = BQ76940_TEMP_COUNT; + break; + } + + /* Read the number of sensors requested or available, whichever is smaller + * We replace the requested count with the number of channels actually read + */ + + temps->sensor_count = MIN(chip_sensors, temps->sensor_count); + ret = bq769x0_getnreg16(priv, BQ769X0_REG_TS1_HI, regvals, + temps->sensor_count); + if (ret < 0) + { + baterr("ERROR: Error reading from BQ769X0! Error = %d\n", ret); + return ret; + } + + /* Convert temp sensor ADC values to microvolts */ + + for (i = 0; i < temps->sensor_count; i += 1) + { + temps->temperatures[i] = ((uint32_t) regvals[i] * priv->gain) + + priv->offset; + } + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_chip_cellcount + * + * Description: + * Returns the number of cell channels on the specified device + * + ****************************************************************************/ + +static int bq769x0_chip_cellcount(FAR struct bq769x0_dev_s *priv) +{ + switch (priv->chip) + { + case CHIP_BQ76920: + return BQ76920_MAX_CELL_COUNT; + break; + + case CHIP_BQ76930: + return BQ76930_MAX_CELL_COUNT; + break; + + default: + case CHIP_BQ76940: + return BQ76940_MAX_CELL_COUNT; + break; + } +} + +/**************************************************************************** + * Name: bq769x0_getcurrent + * + * Description: + * Gets the value of the battery current as measured by the BQ769X0 + * + ****************************************************************************/ + +static inline int bq769x0_getcurrent(FAR struct bq769x0_dev_s *priv, + FAR struct battery_monitor_current_s *current) +{ + /* The BQ769X0's "coulomb counter" reports average current over a 250ms + * period. This can be integrated by the user application to measure + * amp-hours. + */ + + int i; + uint8_t regval; + int16_t ccval; + int32_t ccvolts; + int32_t ccamps; + int ret; + + /* Poll SYS_STAT register until a new Coulomb counter value is ready + * or until we time out + */ + + for (i = 0; i < 6; i += 1) + { + ret = bq769x0_getreg8(priv, BQ769X0_REG_SYS_STAT, ®val); + if (ret < 0) + { + baterr("ERROR: Failed to read BQ769X0 Status! Error = %d\n", ret); + return ret; + } + + if (regval & BQ769X0_CC_READY) + { + /* Clear the CC_ready flag */ + + ret = bq769x0_putreg8(priv, BQ769X0_REG_SYS_STAT, + BQ769X0_CC_READY); + if (ret < 0) + { + baterr("ERROR: Error writing to BQ769X0! Error = %d\n", ret); + return ret; + } + + /* Get the CC register data (a signed value) */ + + ret = bq769x0_getreg16(priv, BQ769X0_REG_CC_HI, + (uint16_t *)&ccval); + if (ret < 0) + { + baterr("ERROR: Error reading from BQ769X0! Error = %d\n", ret); + return ret; + } + + batinfo("Coulomb counter raw value: %d\n", ccval); + + /* Convert coulomb counter to real units + * Multiply by 4 for some extra resolution + */ + + ccvolts = (int32_t)ccval * (int32_t)BQ769X0_CC_SCALE * (int32_t)4; + + /* ccvolts is nV, sense_r is uOhm. Result is in mA + * convert to uA, and don't forget to divide the 4 back out + */ + + ccamps = ccvolts / ((int32_t)priv->sense_r); + ccamps *= (int32_t)1000; + ccamps /= (int32_t)4; + current->current = ccamps; + + /* Acquisition time is constant with this device */ + + current->time = (BQ769X0_CC_TIME * USEC_PER_MSEC); + return OK; + } + + /* Sample is not complete, wait and try again */ + + usleep(BQ769X0_CC_POLL_INTERVAL * USEC_PER_MSEC); + } + + /* CC value didn't become available in the expected amount of time */ + + return -ETIMEDOUT; +} + +/**************************************************************************** + * Name: bq769x0_setbalance + * + * Description: + * Sets the values of the BQ769X0 balance switches + * + ****************************************************************************/ + +static inline int bq769x0_setbalance(FAR struct bq769x0_dev_s *priv, + struct battery_monitor_balance_s *bal) +{ + int i; + int j; + int ret; + uint8_t regval; + uint16_t balancebits; + + bool currentbit; + bool lastbit; + int currentindex; + + /* Check how many balance switches were requested. If more than available, + * overwrite with the number available. + */ + + if (bal->balance_count > priv->cellcount) + { + bal->balance_count = priv->cellcount; + } + + /* Scan through the input and look for adjacent cells in each group. + * This is not allowed by the chip, so we will remove them. + * At the same time, copy the balance inputs into a single bit field. + * This allows us to get cell remapping out of the way. + * We will never have more than 15 cells, so we can store the + * result in a 16-bit int. + */ + + balancebits = 0; + + for (i = 0; i < BQ769X0_BAL_REG_COUNT; i += 1) + { + lastbit = false; + for (j = 0; j < BQ769X0_BAL_BITS_PER_REG; j += 1) + { + currentindex = i * BQ769X0_BAL_BITS_PER_REG + j; + if (currentindex >= bal->balance_count) + { + break; + } + + currentbit = bal->balance[currentindex]; + if (currentbit && lastbit) + { + bal->balance[currentindex] = false; + batinfo("Skipping cell %d because balance is set and previous " + "cell balance is set\n", currentindex); + } + else + { + balancebits |= (currentbit ? 1 : 0) << + priv->mapping[currentindex]; + batinfo("Setting cell balance %d to %d\n", currentindex, + currentbit); + batinfo("Balance bits are %02x\n", balancebits); + } + + lastbit = currentbit; + } + } + + /* Now split the result into 3 groups of 5 and send*/ + + for (i = 0; i < BQ769X0_BAL_REG_COUNT; i += 1) + { + regval = (balancebits >> (i * 5)) & BQ769X0_CELLBAL_MASK; + ret = bq769x0_putreg8(priv, BQ769X0_REG_CELLBAL1 + i, regval); + if (ret < 0) + { + baterr("ERROR: Error writing to BQ769X0! Error = %d\n", ret); + return ret; + } + } + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_voltage + * + * Description: + * Get the pack voltage + * + ****************************************************************************/ + +static int bq769x0_voltage(struct battery_monitor_dev_s *dev, int *value) +{ + FAR struct bq769x0_dev_s *priv = (FAR struct bq769x0_dev_s *)dev; + int ret; + + /* Get pack voltage from battery monitor */ + + ret = bq769x0_getvolt(priv, value); + if (ret < 0) + { + baterr("ERROR: Error getting voltage from BQ769X0! Error = %d\n", ret); + return ret; + } + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_voltage + * + * Description: + * Get 1 or more cell voltages + * + ****************************************************************************/ + +static int bq769x0_cellvoltage(struct battery_monitor_dev_s *dev, + struct battery_monitor_voltage_s *cellv) +{ + FAR struct bq769x0_dev_s *priv = (FAR struct bq769x0_dev_s *)dev; + int ret; + + /* Get cell voltages from battery monitor */ + + ret = bq769x0_getcellvolt(priv, cellv); + if (ret < 0) + { + baterr("ERROR: Error getting voltage from BQ769X0! Error = %d\n", ret); + return ret; + } + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_current + * + * Description: + * Get the pack current + * + ****************************************************************************/ + +static int bq769x0_current(struct battery_monitor_dev_s *dev, + struct battery_monitor_current_s *current) +{ + FAR struct bq769x0_dev_s *priv = (FAR struct bq769x0_dev_s *)dev; + int ret; + + /* Get current from battery monitor */ + + ret = bq769x0_getcurrent(priv, current); + if (ret < 0) + { + baterr("ERROR: Error getting current from BQ769X0! Error = %d\n", ret); + return ret; + } + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_soc + * + * Description: + * Get the pack state of charge (in percent) + * + ****************************************************************************/ + +static int bq769x0_soc(struct battery_monitor_dev_s *dev, b16_t *value) +{ + /* The BQ769X0 does not support directly reporting pack state of charge. + * You should be able to come up with a state-of-charge value by knowing an + * initial value and looking at the Coulomb counter. This is out of scope + * for this driver, though. + */ + + return -ENOSYS; +} + +/**************************************************************************** + * Name: bq769x0_coulombs + * + * Description: + * Get the raw value of the coulomb counter + * + ****************************************************************************/ + +static int bq769x0_coulombs(struct battery_monitor_dev_s *dev, int *coulombs) +{ + /* The data from the coulomb counter on this part can be accessed via + * the "get current" command. + */ + + return -ENOSYS; +} + +/**************************************************************************** + * Name: bq769x0_temp + * + * Description: + * Get the pack temperature(s) + * + ****************************************************************************/ + +static int bq769x0_temp(struct battery_monitor_dev_s *dev, + struct battery_monitor_temperature_s *temps) +{ + FAR struct bq769x0_dev_s *priv = (FAR struct bq769x0_dev_s *)dev; + int ret; + + ret = bq769x0_gettemperature(priv, temps); + if (ret < 0) + { + baterr("ERROR: Error getting temperature from BQ769X0! Error = %d\n", + ret); + return ret; + } + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_balance + * + * Description: + * Set the specified cell balance switches + * + ****************************************************************************/ + +static int bq769x0_balance(struct battery_monitor_dev_s *dev, + struct battery_monitor_balance_s *bal) +{ + FAR struct bq769x0_dev_s *priv = (FAR struct bq769x0_dev_s *)dev; + int ret; + + ret = bq769x0_setbalance(priv, bal); + if (ret < 0) + { + baterr("ERROR: Error getting temperature from BQ769X0! Error = %d\n", + ret); + return ret; + } + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_shutdown + * + * Description: + * Put the battery in a low-power state + * + ****************************************************************************/ + +static int bq769x0_shutdown(struct battery_monitor_dev_s *dev, + uintptr_t param) +{ + FAR struct bq769x0_dev_s *priv = (FAR struct bq769x0_dev_s *)dev; + int ret; + + ret = bq769x0_doshutdown(priv); + if (ret < 0) + { + baterr("ERROR: Error putting BQ769X0 into low-power state! Error = %d\n", + ret); + return ret; + } + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_limits + * + * Description: + * Set the built-in safety limit values for the battery + * + ****************************************************************************/ + +static int bq769x0_limits(struct battery_monitor_dev_s *dev, + struct battery_monitor_limits_s *limits) +{ + FAR struct bq769x0_dev_s *priv = (FAR struct bq769x0_dev_s *)dev; + int ret; + + ret = bq769x0_setlimits(priv, limits); + if (ret < 0) + { + baterr("ERROR: Error updating BQ769X0 safety limits! Error = %d\n", + ret); + return ret; + } + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_chgdsg + * + * Description: + * Set the battery charge/discharge switches in order to + * accept/provide current + * + ****************************************************************************/ + +static int bq769x0_chgdsg(struct battery_monitor_dev_s *dev, + struct battery_monitor_switches_s *sw) +{ + FAR struct bq769x0_dev_s *priv = (FAR struct bq769x0_dev_s *)dev; + int ret; + + ret = bq769x0_setchgdsg(priv, sw); + if (ret < 0) + { + baterr("ERROR: Error setting BQ769X0 switches! Error = %d\n", ret); + return ret; + } + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_clearfaults + * + * Description: + * Clear the battery monitor faults one at a time in order of priority + * Uses the most recent fault register read in order to avoid + * race conditions. + * + ****************************************************************************/ + +static int bq769x0_clearfaults(struct battery_monitor_dev_s *dev, + uintptr_t param) +{ + FAR struct bq769x0_dev_s *priv = (FAR struct bq769x0_dev_s *)dev; + int ret; + uint8_t faults = priv->fault_cache; + uint8_t to_clear = 0; + + if (faults & BQ769X0_DEVICE_XREADY) + { + to_clear = BQ769X0_DEVICE_XREADY; + } + else if (faults & BQ769X0_SCD) + { + to_clear = BQ769X0_SCD; + } + else if (faults & BQ769X0_OCD) + { + to_clear = BQ769X0_OCD; + } + else if (faults & BQ769X0_OV) + { + to_clear = BQ769X0_OV; + } + else if (faults & BQ769X0_UV) + { + to_clear = BQ769X0_UV; + } + else if (faults & BQ769X0_OVRD_ALERT) + { + to_clear = BQ769X0_OVRD_ALERT; + } + + if (to_clear) + { + ret = bq769x0_clear_chipfaults(priv, to_clear); + if (ret < 0) + { + baterr("ERROR: Error clearing faults! Error = %d\n", ret); + return ret; + } + } + + return OK; +} + +/**************************************************************************** + * Name: bq769x0_operate + * + * Description: + * Do miscellaneous battery ioctl() + * + ****************************************************************************/ + +static int bq769x0_operate(struct battery_monitor_dev_s *dev, + uintptr_t param) +{ + return -ENOSYS; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: bq769x0_initialize + * + * Description: + * Initialize the BQ769x0 battery driver and return an instance of the + * lower_half interface that may be used with battery_monitor_register(); + * + * This driver requires: + * + * CONFIG_BATTERY_MONITOR - Upper half battery driver support + * CONFIG_I2C - I2C support + * CONFIG_I2C_BQ769X0 - And the driver must be explictly selected. + * + * Input Parameters: + * i2c - An instance of the I2C interface to use to communicate with + * the BQ769x0 + * addr - The I2C address of the BQ769X0 (Can be 0x08 or 0x18). + * frequency - The I2C frequency + * crc - True if the device has CRC enabled (see TI datasheet) + * cellcount - The number of battery cells attached to the BQ769X0. The + * mapping of the cells changes based on count - see datasheet. + * chip - The chip type (either CHIP_BQ76920, CHIP_BQ76930, or + * CHIP_BQ76940). This is used to map cell numbers when the + * full capacity of the chip is not used. See the TI datasheet + * for cell wiring information. + * sense_r - The value of the current sense resistor, in micro ohms. + * This value is used to calculate reported current, and when + * setting overcurrent thresholds. + * + * Returned Value: + * A pointer to the initialized lower-half driver instance. A NULL pointer + * is returned on a failure to initialize the BQ769x0 lower half. + * + ****************************************************************************/ + +FAR struct battery_monitor_dev_s * + bq769x0_initialize(FAR struct i2c_master_s *i2c, uint8_t addr, + uint32_t frequency, bool crc, uint8_t cellcount, + uint8_t chip, uint32_t sense_r) +{ + FAR struct bq769x0_dev_s *priv; + int ret; + uint8_t regval; + + /* Initialize the BQ769x0 device structure */ + + priv = (FAR struct bq769x0_dev_s *)kmm_zalloc(sizeof(struct bq769x0_dev_s)); + if (priv) + { + /* Initialize the BQ769x0 device structure */ + + nxsem_init(&priv->batsem, 0, 1); + priv->ops = &g_bq769x0ops; + priv->i2c = i2c; + priv->addr = addr; + priv->frequency = frequency; + priv->crc = crc; + priv->chip = chip; + priv->cellcount = cellcount; + priv->sense_r = sense_r; + priv->fault_cache = 0; + + /* Sanity check the device setup and assign cell mapping table */ + + switch (chip) + { + case CHIP_BQ76920: + if (cellcount < BQ76920_MIN_CELL_COUNT || + cellcount > BQ76920_MAX_CELL_COUNT) + { + berr("ERROR: Invalid number of cells (%d) for BQ76920\n", + cellcount); + kmm_free(priv); + return NULL; + } + else + { + priv->mapping = bq76920_cell_mapping[cellcount - + BQ76920_MIN_CELL_COUNT]; + } + break; + + case CHIP_BQ76930: + if (cellcount < BQ76930_MIN_CELL_COUNT || + cellcount > BQ76930_MAX_CELL_COUNT) + { + berr("ERROR: Invalid number of cells (%d) for BQ76930\n", + cellcount); + kmm_free(priv); + return NULL; + } + else + { + priv->mapping = bq76930_cell_mapping[cellcount - + BQ76930_MIN_CELL_COUNT]; + } + break; + + case CHIP_BQ76940: + if (cellcount < BQ76940_MIN_CELL_COUNT || + cellcount > BQ76940_MAX_CELL_COUNT) + { + berr("ERROR: Invalid number of cells (%d) for BQ76940\n", + cellcount); + kmm_free(priv); + return NULL; + } + else + { + priv->mapping = bq76940_cell_mapping[cellcount - + BQ76940_MIN_CELL_COUNT]; + } + break; + + default: + berr("ERROR: Unrecognized chip type: %d\n", chip); + kmm_free(priv); + return NULL; + break; + } + + /* Configure the BQ769x0 + * Set default CC_CFG register (required per datasheet) + */ + + ret = bq769x0_putreg8(priv, BQ769X0_REG_CC_CFG, + BQ769X0_CC_CFG_DEFAULT_VAL); + if (ret < 0) + { + baterr("ERROR: Failed to configure the BQ769x0: %d\n", ret); + kmm_free(priv); + return NULL; + } + + /* Set up DELAY_DIS, CC_EN, and CC_ONESHOT bits, + * making sure not to modify existing DSG_ON/CHG_ON state + */ + + ret = bq769x0_getreg8(priv, BQ769X0_REG_SYS_CTRL2, ®val); + if (ret < 0) + { + baterr("ERROR: Failed to configure the BQ769x0: %d\n", ret); + kmm_free(priv); + return NULL; + } + + /* Keep the existing DSG_ON/CHG_ON bits and set CC_EN */ + + regval &= BQ769X0_SYS_CTRL2_CHGDSG_MASK; + regval |= BQ769X0_CC_EN; + + ret = bq769x0_putreg8(priv, BQ769X0_REG_SYS_CTRL2, regval); + if (ret < 0) + { + baterr("ERROR: Failed to configure the BQ769x0: %d\n", ret); + kmm_free(priv); + return NULL; + } + + /* Set ADC_EN and TEMP_SEL bit */ + + regval = BQ769X0_ADC_EN; +#ifndef CONFIG_BQ769X0_USE_INTERNAL_TS + regval |= BQ769X0_TEMP_SEL; +#endif + ret = bq769x0_putreg8(priv, BQ769X0_REG_SYS_CTRL1, regval); + if (ret < 0) + { + baterr("ERROR: Failed to configure the BQ769x0: %d\n", ret); + kmm_free(priv); + return NULL; + } + + /* Pull the factory-calibrated gain and offset values from the chip. */ + + ret = bq769x0_updategain(priv); + if (ret < 0) + { + baterr("ERROR: Failed to get gain/offset values from the BQ769x0: " + "%d\n", ret); + kmm_free(priv); + return NULL; + } + } + + return (FAR struct battery_monitor_dev_s *)priv; +} + +#endif /* CONFIG_BATTERY_MONITOR && CONFIG_I2C && CONFIG_I2C_BQ769X0 */ diff --git a/include/crc8.h b/include/crc8.h index 568d1deaa6f..19caefeeded 100644 --- a/include/crc8.h +++ b/include/crc8.h @@ -88,6 +88,17 @@ uint8_t crc8(FAR const uint8_t *src, size_t len); uint8_t crc8ccitt(FAR const uint8_t *src, size_t len); +/**************************************************************************** + * Name: crc8part + * + * Description: + * Continue CRC calculation on a part of the buffer using the polynomial + * x^8+x^2+x^1+1 (aka "0x07" poly). + * + ****************************************************************************/ + +uint8_t crc8ccittpart(FAR const uint8_t *src, size_t len, uint8_t crc8val); + #undef EXTERN #ifdef __cplusplus } diff --git a/include/nuttx/power/battery_ioctl.h b/include/nuttx/power/battery_ioctl.h index bf61c093c32..f55d6ad1f4b 100644 --- a/include/nuttx/power/battery_ioctl.h +++ b/include/nuttx/power/battery_ioctl.h @@ -61,6 +61,14 @@ #define BATIOC_INPUT_CURRENT _BATIOC(0x0006) #define BATIOC_CAPACITY _BATIOC(0x0007) #define BATIOC_OPERATE _BATIOC(0x0008) +#define BATIOC_CELLVOLTAGE _BATIOC(0x0009) +#define BATIOC_TEMPERATURE _BATIOC(0x000A) +#define BATIOC_BALANCE _BATIOC(0x000B) +#define BATIOC_SHUTDOWN _BATIOC(0x000C) +#define BATIOC_SETLIMITS _BATIOC(0x000D) +#define BATIOC_CHGDSG _BATIOC(0x000E) +#define BATIOC_CLEARFAULTS _BATIOC(0x000F) +#define BATIOC_COULOMBS _BATIOC(0x0010) /**************************************************************************** * Public Types diff --git a/include/nuttx/power/battery_monitor.h b/include/nuttx/power/battery_monitor.h new file mode 100644 index 00000000000..e032d78eafa --- /dev/null +++ b/include/nuttx/power/battery_monitor.h @@ -0,0 +1,431 @@ +/**************************************************************************** + * include/nuttx/power/battery_monitor.h + * NuttX Battery battery manager & monitor interfaces + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_POWER_BATTERY_MONITOR_H +#define __INCLUDE_NUTTX_POWER_BATTERY_MONITOR_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include + +#ifdef CONFIG_BATTERY_MONITOR + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +/* CONFIG_BATTERY_MONITOR - Upper half battery monitor driver support + * + * Specific, lower-half drivers will have other configuration requirements + * such as: + * + * CONFIG_I2C - I2C support *may* be needed + * CONFIG_I2C_BQ769X0 - The BQ769X0 driver must be explicitly selected. + */ + +/* IOCTL Commands ***********************************************************/ + +/* The upper-half battery monitor driver provides a character driver + * "wrapper" around the lower-half battery monitor driver that does all of + * the real work. Since there is no real data transfer to/or from a battery, + * all of the driver interaction is through IOCTL commands. The IOCTL + * commands supported by the upper-half driver simply provide calls into the + * lower half as summarized below: + * + * BATIOC_STATE - Return the current state of the battery (see + * enum battery_monitor_status_e). + * Input value: A pointer to type int. + * BATIOC_HEALTH - Return the current health of the battery (see + * enum battery_monitor_health_e). + * Input value: A pointer to type int. + * BATIOC_ONLINE - Return 1 if the battery is online; 0 if offline. + * Input value: A pointer to type bool. + * BATIOC_VOLTAGE - Return the current battery pack voltage in microvolts. + * Input value: A pointer to type int. + * BATIOC_CELLVOLTAGE - Return the voltage of all cells in microvolts. + * Input value: A pointer to type battery_monitor_voltage_s. + * BATIOC_CURRENT - Return the current battery pack current in microamps. + * The returned data includes duration over which measurement was done, if + * provided by the hardware. + * Input value: A pointer to type battery_monitor_current_s. + * BATIOC_SOC - Return the state-of-charge of the battery, in percent. + * Input value: A pointer to type b16_t. + * BATIOC_COULOMBS - Return the value of the monitor's coulomb counter, + * if provided by the hardware + * Input value: A pointer to type int. + * BATIOC_TEMPERATURE - Return the value of any temperature sensors attached + * to the monitor, in microvolts. + * Input value: A pointer to type battery_monitor_temperature_s. + * BATIOC_BALANCE - Set the monitor's battery balancing switches. + * Input value: A pointer to type battery_monitor_balance_s. + * BATIOC_SHUTDOWN - Put the device into low-power shutdown mode. + * Input value: None. + * BATIOC_SETLIMITS - Set the device's safety limits for voltage, current, + * etc. + * Input value: A pointer to type battery_monitor_limits_s. + * BATIOC_CHGDSG - Set the device's charge and discharge switches. + * Input value: A pointer to type battery_monitor_switches_s. + * BATIOC_CLEARFAULTS - Clear the device's most recent fault. + * Input value: None. + * BATIOC_OPERATE - Perform miscellaneous, device-specific charger operation. + * Input value: An uintptr_t that can hold a pointer to struct + * batio_operate_msg_s. + */ + +/* Special input values for BATIOC_INPUT_CURRENT that may optionally + * be supported by lower-half driver: + */ + +#define BATTERY_INPUT_CURRENT_EXT_LIM (-1) /* External input current limit */ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* Battery status */ + +enum battery_monitor_status_e +{ + BATTERY_UNKNOWN = 0, /* Battery state is not known */ + BATTERY_FAULT, /* Charger reported a fault, get health for more info */ + BATTERY_IDLE, /* Not full, not charging, not discharging */ + BATTERY_FULL, /* Full, not discharging */ + BATTERY_CHARGING, /* Not full, charging */ + BATTERY_DISCHARGING /* Probably not full, discharging */ +}; + +/* Battery Health status */ + +enum battery_monitor_health_e +{ + BATTERY_HEALTH_UNKNOWN = 0, /* Battery health state is not known */ + BATTERY_HEALTH_GOOD, /* Battery is in good condition */ + BATTERY_HEALTH_DEAD, /* Battery is dead, nothing we can do */ + BATTERY_HEALTH_OVERHEAT, /* Battery is over recommended temperature */ + BATTERY_HEALTH_OVERVOLTAGE, /* Battery voltage is over recommended level */ + BATTERY_HEALTH_UNDERVOLTAGE, /* Battery monitor reported an unspecified failure */ + BATTERY_HEALTH_OVERCURRENT, /* Battery monitor reported an overcurrent event */ + BATTERY_HEALTH_SHORT_CIRCUIT, /* Battery monitor reported a short circuit event */ + BATTERY_HEALTH_UNSPEC_FAIL, /* Battery monitor reported an unspecified failure */ + BATTERY_HEALTH_COLD, /* Battery is under recommended temperature */ + BATTERY_HEALTH_WD_TMR_EXP, /* Battery WatchDog Timer Expired */ + BATTERY_HEALTH_DISCONNECTED /* Battery is not connected */ +}; + +struct battery_monitor_voltage_s +{ + /* Before call, contains the desired number of cells to read. + * After call, contains the actual number of cells read. + */ + + int cell_count; + + /* Pointer to array where cell voltages should be stored. + * MUST contain at least cell_count elements. + * Cell voltages are stored in microvolts (uV) + * Cell voltages in this array should be ordered according to the + * physical layout of cells in the system. Driver should rearrange + * voltage values as necssary to present the user with a contiguous + * list of cell voltages in the expected order. + */ + + uint32_t *cell_voltages; +}; + +struct battery_monitor_temperature_s +{ + /* Before call, contains the desired number of temperature sensors to read. + * After call, contains the actual number of temperature sensors read. + */ + + int sensor_count; + + /* Pointer to array where temperature values should be stored. + * MUST contain at least sensor_count elements. + * Temperature values are stored in microvolts (uV) + * It is up to the application to convert these to + * actual temperature values, as the system could have + * any number of different types of sensors hooked up, + * each of which have a different conversion between voltage + * and current. + */ + + uint32_t *temperatures; +}; + +struct battery_monitor_balance_s +{ + int balance_count; + + /* Pointer to array where balance switch values should be stored. + * MUST contain at least balance_count elements. + * Balance switch is turned on if true, off if false + * Array indices for balance switches should correspond to those + * for cell voltage. The driver must rearrange the balance values as + * necessary to make this happen. + */ + + bool *balance; +}; + +struct battery_monitor_limits_s +{ + /* The driver may overwrite any value in this structure to indicate the + * actual value that was set if the exact requested value was not + * available. + * + * All voltage limits are per-cell. + */ + + uint32_t overvoltage_limit; /* Overvoltage trip threshold, in uV */ + uint32_t undervoltage_limit; /* Undervoltage trip threshold, in uV */ + uint32_t overcurrent_limit; /* Overcurrent trip threshold, in mA */ + uint32_t shortcircuit_limit; /* Short circuit current limit, in mA */ + uint32_t overvoltage_delay; /* Delay before overvoltage trips, in uS */ + uint32_t undervoltage_delay; /* Delay before undervoltage trips, in uS */ + uint32_t overcurrent_delay; /* Delay before overcurrent trips, in uS */ + uint32_t shortcircuit_delay; /* Delay before short circuit trips, in uS */ +}; + +struct battery_monitor_switches_s +{ + /* Sets the state of the CHARGE switch, which allows the + * battery to accept current. + */ + + bool charge; + + /* Sets the state of the DISCHARGE switch, which allows the + * battery to supply current. + */ + + bool discharge; +}; + +struct battery_monitor_current_s +{ + /* The value of current measured by the monitor IC, in uA */ + + int32_t current; + + /* The time over which the above current was measured, + * in uS. The application may request a specific time interval + * by filling in this field when calling the BATIOC_CURRENT function, + * and the driver may or may not choose to honor it. The driver + * will always overwrite this field with the actual measurement time. + * A value of 0 means that instantaneous current should be measured. + */ + + uint32_t time; +}; + + /* This structure defines the lower half battery interface */ + +struct battery_monitor_dev_s; + +struct battery_monitor_operations_s +{ + /* Return the current battery state (see enum battery_monitor_status_e) */ + + int (*state)(struct battery_monitor_dev_s *dev, int *status); + + /* Return the current battery health (see enum battery_monitor_health_e) */ + + int (*health)(struct battery_monitor_dev_s *dev, int *health); + + /* Return true if the battery is online */ + + int (*online)(struct battery_monitor_dev_s *dev, bool *status); + + /* Get the battery pack voltage */ + + int (*voltage)(struct battery_monitor_dev_s *dev, int *value); + + /* Get the battery cell voltages */ + + int (*cell_voltage)(struct battery_monitor_dev_s *dev, + struct battery_monitor_voltage_s *cellv); + + /* Get the battery pack current */ + + int (*current)(struct battery_monitor_dev_s *dev, + struct battery_monitor_current_s *current); + + /* Get the battery pack state of charge */ + + int (*soc)(struct battery_monitor_dev_s *dev, b16_t *value); + + /* Get the battery pack Couloumb count value*/ + + int (*coulombs)(struct battery_monitor_dev_s *dev, int *value); + + /* Read battery pack temperature sensor(s) */ + + int (*temperature)(struct battery_monitor_dev_s *dev, + struct battery_monitor_temperature_s *temps); + + /* Set balance switches on battery cells */ + + int (*balance)(struct battery_monitor_dev_s *dev, + struct battery_monitor_balance_s *bal); + + /* Put monitor device into low-power shutdown mode */ + + int (*shutdown)(struct battery_monitor_dev_s *dev, uintptr_t param); + + /* Configure safety limits for the device */ + + int (*setlimits)(struct battery_monitor_dev_s *dev, + struct battery_monitor_limits_s *limits); + + /* Set the state of charge/discharge switches to allow battery to source/sink power */ + + int (*chgdsg)(struct battery_monitor_dev_s *dev, + struct battery_monitor_switches_s *sw); + + /* Clear battery monitor faults */ + + int (*clearfaults)(struct battery_monitor_dev_s *dev, uintptr_t param); + + /* Do device specific operation */ + + int (*operate)(struct battery_monitor_dev_s *dev, uintptr_t param); +}; + +/* This structure defines the battery driver state structure */ + +struct battery_monitor_dev_s +{ + /* Fields required by the upper-half driver */ + + FAR const struct battery_monitor_operations_s *ops; /* Battery operations */ + sem_t batsem; /* Enforce mutually exclusive access */ + + /* Data fields specific to the lower-half driver may follow */ +}; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifndef __ASSEMBLY__ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: battery_monitor_register + * + * Description: + * Register a lower half battery driver with the common, upper-half + * battery driver. + * + * Input Parameters: + * devpath - The location in the pseudo-filesystem to create the driver. + * Recommended standard is "/dev/bat0", "/dev/bat1", etc. + * dev - An instance of the battery state structure . + * + * Returned Value: + * Zero on success or a negated errno value on failure. + * + ****************************************************************************/ + +int battery_monitor_register(FAR const char *devpath, + FAR struct battery_monitor_dev_s *dev); + +/**************************************************************************** + * Name: BQ769X0_initialize + * + * Description: + * Initialize the BQ769X0 battery driver and return an instance of the + * lower-half interface that may be used with battery_monitor_register(). + * + * This is for: + * BQ7692000XXX + * BQ7693000XXX + * bq7694000XXX + * + * This driver requires: + * + * CONFIG_BATTERY_MONITOR - Upper half battery monitor driver support + * CONFIG_I2C - I2C support + * CONFIG_I2C_BQ769X0 - And the driver must be explictly selected. + * + * Input Parameters: + * i2c - An instance of the I2C interface to use to communicate with + * the BQ769X0 + * addr - The I2C address of the BQ769X0 (Can be 0x08 or 0x18). + * frequency - The I2C frequency + * crc - True if the device has CRC enabled (see TI datasheet) + * cellcount - The number of battery cells attached to the BQ769X0. The + * mapping of the cells changes based on count - see datasheet. + * chip - The chip type (either CHIP_BQ76920, CHIP_BQ76930, or + * CHIP_BQ76940). This is used to map cell numbers when the + * full capacity of the chip is not used. See the TI datasheet + * for cell wiring information. + * sense_r - The value of the current sense resistor, in micro ohms. + * This value is used to calculate reported current, and when + * setting overcurrent thresholds. + * Returned Value: + * A pointer to the initialized battery driver instance. A NULL pointer + * is returned on a failure to initialize the BQ769X0 lower half. + * + ****************************************************************************/ + +#if defined(CONFIG_I2C) && defined(CONFIG_I2C_BQ769X0) + +struct i2c_master_s; +FAR struct battery_monitor_dev_s *bq769x0_initialize(FAR struct i2c_master_s *i2c, + uint8_t addr, + uint32_t frequency, + bool crc, + uint8_t cellcount, + uint8_t chip, + uint32_t sense_r); +#endif + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __ASSEMBLY__ */ +#endif /* CONFIG_BATTERY_MONITOR */ +#endif /* __INCLUDE_NUTTX_POWER_BATTERY_H */ diff --git a/include/nuttx/power/bq769x0.h b/include/nuttx/power/bq769x0.h new file mode 100644 index 00000000000..0917f7771c1 --- /dev/null +++ b/include/nuttx/power/bq769x0.h @@ -0,0 +1,284 @@ +/**************************************************************************** + * drivers/power/bq769x0.h + * Lower half driver for 769x0 battery monitor + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __DRIVERS_POWER_BQ769X0_H +#define __DRIVERS_POWER_BQ769X0_H + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Chip variant definitions */ + +#define CHIP_BQ76920 0 +#define CHIP_BQ76930 1 +#define CHIP_BQ76940 2 + +/* Auxiliary Definitions */ + +#define BQ769X0_BASE_GAIN 365 /* uV/LSB */ +#define BQ769X0_MAX_CELLS 15 /* No more than 15 cells per chip*/ +#define BQ769X0_BAL_REG_COUNT 3 /* CELBAL1, CELBAL2, CELBAL3 */ +#define BQ769X0_BAL_BITS_PER_REG 5 +#define BQ76920_MIN_CELL_COUNT 3 +#define BQ76920_MAX_CELL_COUNT 5 +#define BQ76930_MIN_CELL_COUNT 6 +#define BQ76930_MAX_CELL_COUNT 10 +#define BQ76940_MIN_CELL_COUNT 9 +#define BQ76940_MAX_CELL_COUNT 15 +#define BQ76920_TEMP_COUNT 1 +#define BQ76930_TEMP_COUNT 2 +#define BQ76940_TEMP_COUNT 3 +#define BQ769X0_CC_CFG_DEFAULT_VAL 0x19 /* Per Datasheet */ +#define BQ769X0_CELLBAL_MASK 0x1f /* 5 LSBs in each register */ +#define BQ769X0_CC_TIME 250 /* milliseconds per CC sample */ +#define BQ769X0_CC_POLL_INTERVAL 50 /* milliseconds (arbitrary) */ +#define BQ769X0_CC_SCALE 8440 /* nanovolts / LSB */ + +/* BQ769x0 Register Definitions *********************************************/ + +/* System Control & Configuration Registers */ + +#define BQ769X0_REG_SYS_STAT 0x00 +#define BQ769X0_REG_CELLBAL1 0x01 +#define BQ769X0_REG_CELLBAL2 0x02 /* Only on BQ76930 and BQ76940 */ +#define BQ769X0_REG_CELLBAL3 0x03 /* Only on BQ76940 */ +#define BQ769X0_REG_SYS_CTRL1 0x04 +#define BQ769X0_REG_SYS_CTRL2 0x05 +#define BQ769X0_REG_PROTECT1 0x06 +#define BQ769X0_REG_PROTECT2 0x07 +#define BQ769X0_REG_PROTECT3 0x08 +#define BQ769X0_REG_OV_TRIP 0x09 +#define BQ769X0_REG_UV_TRIP 0x0a +#define BQ769X0_REG_CC_CFG 0x0b + +/* Cell Voltage Registers + * BQ76920 can use VC1 - VC5 + * BQ76930 can use VC1 - VC10 + * BQ76940 can use VC1 - VC15 + */ + +#define BQ769X0_REG_VC1_HI 0x0c +#define BQ769X0_REG_VC1_LO 0x0d +#define BQ769X0_REG_VC2_HI 0x0e +#define BQ769X0_REG_VC2_LO 0x0f +#define BQ769X0_REG_VC3_HI 0x10 +#define BQ769X0_REG_VC3_LO 0x11 +#define BQ769X0_REG_VC4_HI 0x12 +#define BQ769X0_REG_VC4_LO 0x13 +#define BQ769X0_REG_VC5_HI 0x14 +#define BQ769X0_REG_VC5_LO 0x15 +#define BQ769X0_REG_VC6_HI 0x16 +#define BQ769X0_REG_VC6_LO 0x17 +#define BQ769X0_REG_VC7_HI 0x18 +#define BQ769X0_REG_VC7_LO 0x19 +#define BQ769X0_REG_VC8_HI 0x1a +#define BQ769X0_REG_VC8_LO 0x1b +#define BQ769X0_REG_VC9_HI 0x1c +#define BQ769X0_REG_VC9_LO 0x1d +#define BQ769X0_REG_VC10_HI 0x1e +#define BQ769X0_REG_VC10_LO 0x1f +#define BQ769X0_REG_VC11_HI 0x20 +#define BQ769X0_REG_VC11_LO 0x21 +#define BQ769X0_REG_VC12_HI 0x22 +#define BQ769X0_REG_VC12_LO 0x23 +#define BQ769X0_REG_VC13_HI 0x24 +#define BQ769X0_REG_VC13_LO 0x25 +#define BQ769X0_REG_VC14_HI 0x26 +#define BQ769X0_REG_VC14_LO 0x27 +#define BQ769X0_REG_VC15_HI 0x28 +#define BQ769X0_REG_VC15_LO 0x29 + +/* System Voltage, Temperature, and Current Registers */ + +#define BQ769X0_REG_BAT_HI 0x2a +#define BQ769X0_REG_BAT_LO 0x2b +#define BQ769X0_REG_TS1_HI 0x2c +#define BQ769X0_REG_TS1_LO 0x2d +#define BQ769X0_REG_TS2_HI 0x2e +#define BQ769X0_REG_TS2_LO 0x2f +#define BQ769X0_REG_TS3_HI 0x30 +#define BQ769X0_REG_TS3_LO 0x31 +#define BQ769X0_REG_CC_HI 0x32 +#define BQ769X0_REG_CC_LO 0x33 + +/* ADC Calibration Registers */ + +#define BQ769X0_REG_ADCGAIN1 0x50 +#define BQ769X0_REG_ADCOFFSET 0x51 +#define BQ769X0_REG_ADCGAIN2 0x59 + +/* REG_SYS_STAT */ + +#define BQ769X0_CC_READY (1 << 7) /* Updated Coulomb counter reading is available */ + /* Bit 6: Reserved */ +#define BQ769X0_DEVICE_XREADY (1 << 5) /* Internal chip fault detected */ +#define BQ769X0_OVRD_ALERT (1 << 4) /* External pull-up detected on ALERT pin */ +#define BQ769X0_UV (1 << 3) /* Undervoltage fault */ +#define BQ769X0_OV (1 << 2) /* Overvoltage fault */ +#define BQ769X0_SCD (1 << 1) /* Short circuit in discharge fault */ +#define BQ769X0_OCD (1 << 0) /* Over current in discharge fault */ +#define BQ769X0_FAULT_MASK (0x3f) /* Bottom 6 bits */ + +/* REG_SYS_CTRL1 */ + +#define BQ769X0_LOAD_PRESENT (1 << 7) /* CHG pin is higher than VLOAD_DETECT, external load is present */ + /* Bit 6: Reserved */ + /* Bit 5: Reserved */ +#define BQ769X0_ADC_EN (1 << 4) /* Enable voltage, temperature measurement and OV protection */ +#define BQ769X0_TEMP_SEL (1 << 3) /* 0 = use onboard temp sensors, 1 = use external temp sensors */ + /* Bit 2: Reserved */ +#define BQ769X0_SHUT_A (1 << 1) /* Shutdown command, bit A */ +#define BQ769X0_SHUT_B (1 << 0) /* Shutdown command, bit B */ + +#define BQ769X0_SYS_CTRL1_WRITE_MASK (0x1b) /* Bits 0, 1, 3, and 4 are writeable */ +#define BQ769X0_SYS_CTRL1_SHUTDOWN_MASK (0x03) /* Bits 0 and 1 for shutdown */ + +/* REG_SYS_CTRL2 */ + +#define BQ769X0_DELAY_DIS (1 << 7) /* 1 = Disable protection delays for testing */ +#define BQ769X0_CC_EN (1 << 6) /* 1 = Enable continuous Coulomb counter readings */ +#define BQ769X0_CC_ONESHOT (1 << 5) /* Trigger a single 250ms Coulomb counter read */ + /* Bit 4: Reserved */ + /* Bit 3: Reserved */ + /* Bit 2: Reserved */ +#define BQ769X0_DSG_ON (1 << 1) /* DSG (Discharge FET) control */ +#define BQ769X0_CHG_ON (1 << 0) /* CHG (Charge FET) control */ + +#define BQ769X0_SYS_CTRL2_WRITE_MASK (0xe3) /* Bits 0, 1, 5, 6, and 7 are writeable */ +#define BQ769X0_SYS_CTRL2_CHGDSG_MASK (0x03) /* Bits 0 and 1 for charge/discharge */ + +/* REG_PROTECT1 */ + +#define BQ769X0_RSNS (1 << 7 ) /* Selects low or high input range for OCD/SCD */ + /* Bit 6: Reserved */ + /* Bit 5: Reserved */ +#define BQ769X0_SCD_DELAY_SHIFT (3) /* Short circuit in discharge delay timer */ +#define BQ769X0_SCD_DELAY_MASK (0x03 << BQ769X0_SCD_DELAY_SHIFT) +# define BQ769X0_SCD_DELAY_70US (0 << BQ769X0_SCD_DELAY_SHIFT) +# define BQ769X0_SCD_DELAY_100US (1 << BQ769X0_SCD_DELAY_SHIFT) +# define BQ769X0_SCD_DELAY_200US (2 << BQ769X0_SCD_DELAY_SHIFT) +# define BQ769X0_SCD_DELAY_400US (3 << BQ769X0_SCD_DELAY_SHIFT) +#define BQ769X0_SCD_THRESH_SHIFT (0) /* Short circuit in discharge threshold value */ +#define BQ769X0_SCD_THRESH_MASK (0x07 << BQ769X0_SCD_THRESH_SHIFT) + +/* These defines apply when RSNS is 0 */ + +# define BQ769X0_SCD_THRESH_0_22MV (0 << BQ769X0_SCD_THRESH_SHIFT) +# define BQ769X0_SCD_THRESH_0_33MV (1 << BQ769X0_SCD_THRESH_SHIFT) +# define BQ769X0_SCD_THRESH_0_44MV (2 << BQ769X0_SCD_THRESH_SHIFT) +# define BQ769X0_SCD_THRESH_0_56MV (3 << BQ769X0_SCD_THRESH_SHIFT) +# define BQ769X0_SCD_THRESH_0_67MV (4 << BQ769X0_SCD_THRESH_SHIFT) +# define BQ769X0_SCD_THRESH_0_78MV (5 << BQ769X0_SCD_THRESH_SHIFT) +# define BQ769X0_SCD_THRESH_0_89MV (6 << BQ769X0_SCD_THRESH_SHIFT) +# define BQ769X0_SCD_THRESH_0_100MV (7 << BQ769X0_SCD_THRESH_SHIFT) + +/* These defines apply when RSNS is 1 */ + +# define BQ769X0_SCD_THRESH_1_44MV (0 << BQ769X0_SCD_THRESH_SHIFT) +# define BQ769X0_SCD_THRESH_1_67MV (1 << BQ769X0_SCD_THRESH_SHIFT) +# define BQ769X0_SCD_THRESH_1_89MV (2 << BQ769X0_SCD_THRESH_SHIFT) +# define BQ769X0_SCD_THRESH_1_111MV (3 << BQ769X0_SCD_THRESH_SHIFT) +# define BQ769X0_SCD_THRESH_1_133MV (4 << BQ769X0_SCD_THRESH_SHIFT) +# define BQ769X0_SCD_THRESH_1_155MV (5 << BQ769X0_SCD_THRESH_SHIFT) +# define BQ769X0_SCD_THRESH_1_178MV (6 << BQ769X0_SCD_THRESH_SHIFT) +# define BQ769X0_SCD_THRESH_1_200MV (7 << BQ769X0_SCD_THRESH_SHIFT) + +/* REG_PROTECT2 */ + + /* Bit 7: Reserved */ +#define BQ769X0_OCD_DELAY_SHIFT (4) /* Over current in discharge delay timer */ +#define BQ769X0_OCD_DELAY_MASK (0x07 << BQ769X0_OCD_DELAY_SHIFT) +# define BQ769X0_OCD_DELAY_8MS (0 << BQ769X0_OCD_DELAY_SHIFT) +# define BQ769X0_OCD_DELAY_20MS (1 << BQ769X0_OCD_DELAY_SHIFT) +# define BQ769X0_OCD_DELAY_40MS (2 << BQ769X0_OCD_DELAY_SHIFT) +# define BQ769X0_OCD_DELAY_80MS (3 << BQ769X0_OCD_DELAY_SHIFT) +# define BQ769X0_OCD_DELAY_160MS (4 << BQ769X0_OCD_DELAY_SHIFT) +# define BQ769X0_OCD_DELAY_320MS (5 << BQ769X0_OCD_DELAY_SHIFT) +# define BQ769X0_OCD_DELAY_640MS (6 << BQ769X0_OCD_DELAY_SHIFT) +# define BQ769X0_OCD_DELAY_1280MS (7 << BQ769X0_OCD_DELAY_SHIFT) +#define BQ769X0_OCD_THRESH_SHIFT (0) /* Over current in discharge threshold value */ +#define BQ769X0_OCD_THRESH_MASK (0x0f << BQ769X0_OCD_THRESH_SHIFT) + +/* These defines apply when RSNS is 0 */ + +# define BQ769X0_OCD_THRESH_0_8MV (0 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_0_11MV (1 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_0_14MV (2 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_0_17MV (3 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_0_19MV (4 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_0_22MV (5 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_0_25MV (6 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_0_28MV (7 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_0_31MV (8 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_0_33MV (9 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_0_36MV (10 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_0_39MV (11 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_0_42MV (12 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_0_44MV (13 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_0_47MV (14 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_0_50MV (15 << BQ769X0_OCD_THRESH_SHIFT) + +/* These defines apply when RSNS is 1 */ + +# define BQ769X0_OCD_THRESH_1_17MV (0 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_1_22MV (1 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_1_28MV (2 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_1_33MV (3 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_1_39MV (4 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_1_44MV (5 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_1_50MV (6 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_1_56MV (7 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_1_61MV (8 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_1_67MV (9 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_1_72MV (10 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_1_78MV (11 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_1_83MV (12 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_1_89MV (13 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_1_94MV (14 << BQ769X0_OCD_THRESH_SHIFT) +# define BQ769X0_OCD_THRESH_1_100MV (15 << BQ769X0_OCD_THRESH_SHIFT) + +/* REG_PROTECT3 */ + +#define BQ769X0_UV_DELAY_SHIFT (6) +#define BQ769X0_UV_DELAY_MASK (0x03 << BQ769X0_UV_DELAY_SHIFT) +# define BQ769X0_UV_DELAY_1S (0 << BQ769X0_UV_DELAY_SHIFT) +# define BQ769X0_UV_DELAY_4S (1 << BQ769X0_UV_DELAY_SHIFT) +# define BQ769X0_UV_DELAY_8S (2 << BQ769X0_UV_DELAY_SHIFT) +# define BQ769X0_UV_DELAY_16S (3 << BQ769X0_UV_DELAY_SHIFT) +#define BQ769X0_OV_DELAY_SHIFT (4) +#define BQ769X0_OV_DELAY_MASK (0x03 << BQ769X0_OV_DELAY_SHIFT) +# define BQ769X0_OV_DELAY_1S (0 << BQ769X0_OV_DELAY_SHIFT) +# define BQ769X0_OV_DELAY_2S (1 << BQ769X0_OV_DELAY_SHIFT) +# define BQ769X0_OV_DELAY_4S (2 << BQ769X0_OV_DELAY_SHIFT) +# define BQ769X0_OV_DELAY_8S (3 << BQ769X0_OV_DELAY_SHIFT) + +/* REG_ADCGAIN1 */ + +#define BQ769X0_ADCGAIN1_SHIFT (2) +#define BQ769X0_ADCGAIN1_MASK (0x03 << BQ769X0_ADCGAIN1_SHIFT) + +/* REG_ADCGAIN2 */ + +#define BQ769X0_ADCGAIN2_SHIFT (5) +#define BQ769X0_ADCGAIN2_MASK (0x07 << BQ769X0_ADCGAIN2_SHIFT) + +#endif /* __DRIVERS_POWER_BQ769X0_H */ diff --git a/libs/libc/misc/lib_crc8ccitt.c b/libs/libc/misc/lib_crc8ccitt.c index 0b7e62957f7..f2663fae4bc 100644 --- a/libs/libc/misc/lib_crc8ccitt.c +++ b/libs/libc/misc/lib_crc8ccitt.c @@ -87,6 +87,29 @@ static const uint8_t crc8_tab[256] = * Public Functions ***************************************************************************/ +/*************************************************************************** + * Name: crc8ccitt + * + * Description: + * Continue CRC calculation on a part of the buffer. + * + ***************************************************************************/ + +uint8_t crc8ccittpart(FAR const uint8_t *src, size_t len, uint8_t crc8val) +{ + + uint8_t *pos = (uint8_t *) src; + uint8_t *end = pos + len; + + while (pos < end) + { + crc8val = crc8_tab[crc8val ^ *pos]; + pos++; + } + + return crc8val; +} + /*************************************************************************** * Name: crc8ccitt * @@ -98,16 +121,5 @@ static const uint8_t crc8_tab[256] = uint8_t crc8ccitt(FAR const uint8_t *src, size_t len) { - uint8_t val = 0; - - uint8_t *pos = (uint8_t *) src; - uint8_t *end = pos + len; - - while (pos < end) - { - val = crc8_tab[val ^ *pos]; - pos++; - } - - return val; + return crc8ccittpart(src, len, 0); }