You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by gn...@apache.org on 2020/02/29 00:19:06 UTC

[incubator-nuttx] branch master updated: Battery monitor support (#404)

This is an automated email from the ASF dual-hosted git repository.

gnutt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-nuttx.git


The following commit(s) were added to refs/heads/master by this push:
     new 43dfbdb  Battery monitor support (#404)
43dfbdb is described below

commit 43dfbdb3481cd01a498c4e37325bd63729092bbe
Author: macman88 <jj...@gmail.com>
AuthorDate: Fri Feb 28 18:17:27 2020 -0600

    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        |   24 +-
 9 files changed, 3403 insertions(+), 6 deletions(-)

diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 8246ca4..0eb6b2e 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 25cd824..9280884 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 0000000..22489e3
--- /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 <nuttx/config.h>
+
+#include <stdbool.h>
+#include <errno.h>
+#include <debug.h>
+
+#include <nuttx/semaphore.h>
+#include <nuttx/fs/fs.h>
+#include <nuttx/power/battery_monitor.h>
+#include <nuttx/power/battery_ioctl.h>
+
+/* 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 0000000..f3123fd
--- /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 <nuttx/config.h>
+
+#include <sys/types.h>
+
+#include <unistd.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <debug.h>
+
+#include <crc8.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/signal.h>
+#include <nuttx/i2c/i2c_master.h>
+#include <nuttx/power/battery_monitor.h>
+#include <nuttx/power/battery_ioctl.h>
+
+#include <nuttx/power/bq769x0.h>
+
+/* 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 <I2C write address> ACK <Reg address> ACK
+ *   REPEATED-START <I2C read address> 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, &regaddr, 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 <I2C write address> ACK <Reg address> 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 <I2C write address> ACK <Reg address> ACK
+ *   REPEATED-START <I2C read address> 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 <I2C write address> ACK <Reg address> ACK
+ *   REPEATED-START <I2C read address> 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, &regaddr, 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, &regval);
+  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, &regval);
+  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, &regval);
+  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, &regval);
+
+  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, &regval);
+
+  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, &regval);
+  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 <gain>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 <gain>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, &regval);
+      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, &regval);
+      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 568d1de..19caefe 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 bf61c09..f55d6ad 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 0000000..e032d78
--- /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 <nuttx/config.h>
+#include <nuttx/fs/ioctl.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <semaphore.h>
+#include <fixedmath.h>
+
+#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 0000000..0917f77
--- /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 0b7e629..f2663fa 100644
--- a/libs/libc/misc/lib_crc8ccitt.c
+++ b/libs/libc/misc/lib_crc8ccitt.c
@@ -91,23 +91,35 @@ static const uint8_t crc8_tab[256] =
  * Name: crc8ccitt
  *
  * Description:
- *   Return a 8-bit CRC of the contents of the 'src' buffer, length 'len
- *   using a CRC with the polynomial x^8+x^2+x^1+1 ("0x07" polynomial).
+ *   Continue CRC calculation on a part of the buffer.
  *
  ***************************************************************************/
 
-uint8_t crc8ccitt(FAR const uint8_t *src, size_t len)
+uint8_t crc8ccittpart(FAR const uint8_t *src, size_t len, uint8_t crc8val)
 {
-  uint8_t val = 0;
 
   uint8_t *pos = (uint8_t *) src;
   uint8_t *end = pos + len;
 
   while (pos < end)
     {
-      val = crc8_tab[val ^ *pos];
+      crc8val = crc8_tab[crc8val ^ *pos];
       pos++;
     }
 
-  return val;
+  return crc8val;
+}
+
+/***************************************************************************
+ * Name: crc8ccitt
+ *
+ * Description:
+ *   Return a 8-bit CRC of the contents of the 'src' buffer, length 'len
+ *   using a CRC with the polynomial x^8+x^2+x^1+1 ("0x07" polynomial).
+ *
+ ***************************************************************************/
+
+uint8_t crc8ccitt(FAR const uint8_t *src, size_t len)
+{
+    return crc8ccittpart(src, len, 0);
 }