--- /dev/null
+/****************************************************************************
+ * extdrv/bq769x0_bms.c
+ *
+ *
+ * Copyright 2020 Nathael Pajani <nathael.pajani@ed3l.fr>
+ *
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *************************************************************************** */
+
+
+#include "core/system.h"
+#include "lib/errno.h"
+#include "lib/string.h"
+#include "drivers/i2c.h"
+#include "extdrv/bq769x0_bms.h"
+
+
+/***************************************************************************** */
+/* Support for TMP101 temperature bmss from Texas Instrument */
+/***************************************************************************** */
+/* This driver is made for the TMP101 version of the chip, though there's few
+ * diferences between the TMP100 and TMP101 version.
+ * This driver does not handle the SMBus Alert command.
+ */
+
+
+enum bq769x0_internal_reg_numbers {
+ SYSTEM_STATUS = 0,
+ CELL_BALANCE_1,
+ CELL_BALANCE_2,
+ CELL_BALANCE_3,
+ SYS_CTRL_1,
+ SYS_CTRL_2,
+ PROTECT_1,
+ PROTECT_2,
+ PROTECT_3,
+ OVER_VOLTAGE_TRIP,
+ UNDER_VOLTAGE_TRIP,
+ CC_CFG, /* Coulomb Counter */
+ VC1_HIGH, /* 0x0C */
+ VC1_LOW,
+ /* VC2 to VC15 ... */
+ BAT_HIGH = 0x2A,
+ BAT_LOW,
+ TS1_HIGH,
+ TS1_LOW,
+ /* TS2 and TS3 */
+ CC_HIGH = 0x32,
+ CC_LOW,
+ ADC_GAIN_1 = 0x50,
+ ADC_OFFSET,
+ ADC_GAIN_2 = 0x59,
+ BQ769X0_LAST_REG,
+};
+
+
+/* Aditional defines, not exported to userspace */
+/* SYS Status */
+#define CC_READY(x) ((x) & 0x80) /* New Coulomb Counter reading available */
+#define DEVICE_NOT_READY(x) ((x) & 0x20) /* Set means internal fault */
+#define EXT_OVERRIDE_ALERT(x) ((x) & 0x10)
+#define UNDER_VOLTAGE(x) ((x) & 0x20) /* UV */
+#define OVER_VOLTAGE(x) ((x) & 0x20) /* OV */
+#define SHORT_CIRCUIT_DISCHARGE(x) ((x) & 0x20) /* SCD */
+#define OVER_CURRENT_DISCHARGE(x) ((x) & 0x20) /* OCD */
+
+
+/* Sys Ctrl 1 */
+#define LOAD_PRESENT(x) ((x) & 0x80)
+#define ADC_ENABLE ((0x01) << 4)
+#define TEMP_SEL_DIE (0 << 3)
+#define TEMP_SEL_TSX (1 << 3)
+#define SHUTDOWN_CMD_A (0x01)
+#define SHUTDOWN_CMD_B (0x02)
+
+/* Sys Ctrl 2 */
+#define DELAYS_OFF_FOR_TESTING (0x01 << 7)
+#define CC_ENABLE ((0x01) << 6)
+#define CC_ONE_SHOT ((0x01) << 5)
+#define DISCHARGE_ON (0x01 << 1)
+#define CHARGE_ON (0x01 << 0)
+
+/* CC Config */
+/* The documentation asks for CC_CFG to be set to 0x19 upon startup */
+#define CC_CFG_DEFAULT 0x019
+
+
+/* Check the bms presence, return 1 if bms was found.
+ * This is a basic check, it could be anything with the same address ...
+ */
+int bq769x0_probe_bms(struct bq769x0_bms_conf* conf)
+{
+ char cmd_buf = (conf->addr | I2C_READ_BIT);
+
+ /* Did we already probe the bms ? */
+ if (conf->probe_ok != 1) {
+ conf->probe_ok = i2c_read(conf->bus_num, &cmd_buf, 1, NULL, NULL, 0);
+ }
+ return conf->probe_ok;
+}
+
+
+/* Read registers from the BMS
+ * Return value(s):
+ * Upon successfull completion, returns 0 and the registers content is placed in the buffer.
+ * On error, returns a negative integer equivalent to errors from glibc.
+ */
+#define CMD_BUF_SIZE 3
+static int bq769x0_bms_get_regs(struct bq769x0_bms_conf* conf,
+ uint8_t reg_start, uint8_t* values, uint8_t len)
+{
+ int ret = 0;
+ char cmd_buf[CMD_BUF_SIZE] = { conf->addr, 0, (conf->addr | I2C_READ_BIT), };
+ char ctrl_buf[CMD_BUF_SIZE] = { I2C_CONT, I2C_DO_REPEATED_START, I2C_CONT, };
+
+ if (bq769x0_probe_bms(conf) != 1) {
+ return -ENODEV;
+ }
+ if (values == NULL) {
+ return -EINVAL;
+ }
+ if ((reg_start + len - 1) > BQ769X0_LAST_REG) {
+ return -EINVAL;
+ }
+ cmd_buf[1] = reg_start;
+
+ ret = i2c_read(conf->bus_num, cmd_buf, CMD_BUF_SIZE, ctrl_buf, values, len);
+ if (ret != len) {
+ conf->probe_ok = 0;
+ return ret;
+ }
+ return 0;
+}
+
+#define REG_WR_CMD_SIZE 2
+#define MAX_WR_BUF_SIZE (REG_WR_CMD_SIZE + CC_CFG) /* Maybe someone wants to write all regs at once ? */
+int bq769x0_bms_set_regs(struct bq769x0_bms_conf* conf,
+ uint8_t reg_start, uint8_t* values, uint8_t len)
+{
+ int ret = 0;
+ char cmd_buf[MAX_WR_BUF_SIZE] = { conf->addr, };
+
+ if (bq769x0_probe_bms(conf) != 1) {
+ return -ENODEV;
+ }
+ if (values == NULL) {
+ return -EINVAL;
+ }
+ if ((reg_start + len - 1) > CC_CFG) { /* CC_CFG is the last RW reg */
+ return -EINVAL;
+ }
+ cmd_buf[1] = reg_start;
+
+ memcpy((cmd_buf + REG_WR_CMD_SIZE), values, len);
+ ret = i2c_write(conf->bus_num, cmd_buf, (REG_WR_CMD_SIZE + len), NULL);
+ if (ret != (REG_WR_CMD_SIZE + len)) {
+ conf->probe_ok = 0;
+ return ret;
+ }
+ return 0;
+}
+
+/* Start a conversion when the bms is not in Continuous CC mode. */
+int bq769x0_bms_start_conversion(struct bq769x0_bms_conf* conf)
+{
+ return 0; /* Conversion start success */
+}
+
+/* Place the bms in continuous CC convertion mode */
+int bq769x0_bms_set_continuous_conversion(struct bq769x0_bms_conf* conf)
+{
+ return 0; /* Configuration change success */
+}
+
+
+static void bq769x0_bms_read_adc_gain(struct bq769x0_bms_conf* conf)
+{
+ int ret = 0;
+ uint8_t gain = 0, tmp = 0;
+
+ /* Get GAIN two MSB bits */
+ ret = bq769x0_bms_get_regs(conf, ADC_GAIN_1, &tmp, 1);
+ if (ret != 0) {
+ conf->probe_ok = 6;
+ return;
+ }
+ gain = (tmp & 0x0C) << 1;
+
+ /* Get GAIN three LSB bits */
+ ret = bq769x0_bms_get_regs(conf, ADC_GAIN_2, &tmp, 1);
+ if (ret != 0) {
+ conf->probe_ok = 7;
+ return;
+ }
+ gain |= ((tmp >> 5) & 0x07);
+
+ /* Store gain and gain in µV */
+ conf->adc_gain = gain;
+ conf->adc_gain_uV = 365 + gain;
+
+ /* Get ADC offset */
+ ret = bq769x0_bms_get_regs(conf, ADC_OFFSET, &tmp, 1);
+ if (ret != 0) {
+ conf->probe_ok = 8;
+ return;
+ }
+ conf->adc_offset = (int8_t)tmp;
+}
+
+int bq769x0_bms_read_adc(struct bq769x0_bms_conf* conf, uint16_t* data, int offset, int nb_val)
+{
+
+ return 0;
+}
+
+int bq769x0_bms_get_and_erase_status(struct bq769x0_bms_conf* conf, uint8_t* status)
+{
+ int ret = 0;
+ uint8_t tmp = 0;
+
+ /* Store the new configuration */
+ ret = bq769x0_bms_get_regs(conf, SYSTEM_STATUS, status, 1);
+ if (ret != 0) {
+ return ret;
+ }
+ ret = bq769x0_bms_set_regs(conf, SYSTEM_STATUS, &tmp, 1);
+ if (ret != 0) {
+ return ret;
+ }
+ return 0;
+}
+
+
+/* Sensor config
+ * Performs default configuration of the bms.
+ * Return value :
+ * Upon successfull completion, returns 0. On error, returns a negative integer
+ * equivalent to errors from glibc.
+ */
+#define CONF_BUF_SIZE 4
+int bq769x0_bms_config(struct bq769x0_bms_conf* conf, uint8_t* old_status)
+{
+ int ret = 0;
+ uint8_t tmp = 0;
+
+ /* Store the new configuration */
+ ret = bq769x0_bms_get_and_erase_status(conf, &tmp);
+ if (ret != 0) {
+ conf->probe_ok = 2;
+ return ret;
+ }
+ if (old_status != NULL) {
+ *old_status = tmp;
+ }
+
+ /* Programm CC_CFG as requested in datasheet */
+ tmp = CC_CFG_DEFAULT;
+ ret = bq769x0_bms_set_regs(conf, CC_CFG, &tmp, 1);
+ if (ret != 0) {
+ conf->probe_ok = 3;
+ return ret;
+ }
+
+ /* Program first control register :
+ * - enable ADC
+ * - set temp readings to externel thermistor instead of Die temperature
+ */
+ tmp = ADC_ENABLE | TEMP_SEL_TSX;
+ ret = bq769x0_bms_set_regs(conf, SYS_CTRL_1, &tmp, 1);
+ if (ret != 0) {
+ conf->probe_ok = 4;
+ return ret;
+ }
+
+ /* Program second control register :
+ * - FIXME : Change delays to test mode, should be removed in production
+ * - Enable coulomb counter
+ * - Enable discharge
+ * - Enable charge
+ */
+// tmp = DELAYS_OFF_FOR_TESTING | CC_ENABLE | DISCHARGE_ON | CHARGE_ON;
+ tmp = DELAYS_OFF_FOR_TESTING | DISCHARGE_ON | CHARGE_ON;
+ ret = bq769x0_bms_set_regs(conf, SYS_CTRL_2, &tmp, 1);
+ if (ret != 0) {
+ conf->probe_ok = 5;
+ return ret;
+ }
+ bq769x0_bms_get_and_erase_status(conf, &tmp);
+
+ /* Get the ADC gain and offset once upon startup */
+ bq769x0_bms_read_adc_gain(conf);
+
+ return 0; /* Config success */
+}
+
--- /dev/null
+/****************************************************************************
+ * extdrv/bq769x0_bms.h
+ *
+ *
+ * Copyright 2020 Nathael Pajani <nathael.pajani@ed3l.fr>
+ *
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *************************************************************************** */
+
+#ifndef EXTDRV_BQ769X0_H
+#define EXTDRV_BQ769X0_H
+
+#include "lib/stdint.h"
+
+
+/***************************************************************************** */
+/* Support for BQ769X0 Battery Protection from Texas Instrument */
+/***************************************************************************** */
+/* This driver is made for the BQ76920 version of the chip, though there's few
+ * diferences between the BQ76920, BQ76930 and BQ76950 versions.
+ * This driver does not handle the Alert command.
+ */
+
+/* BQ769X0 instance data.
+ * Use one of this for each BMS you want to access.
+ * - addr is the BMS address on most significant bits.
+ */
+struct bq769x0_bms_conf {
+ uint8_t addr;
+ uint8_t bus_num;
+ uint8_t probe_ok;
+ uint8_t crc_check; /* 0 : No CRC check */
+ uint8_t adc_gain;
+ int8_t adc_offset;
+ uint16_t adc_gain_uV; /* ADC gain converted to µV */
+};
+
+/* Check the bms presence, return 1 if found
+ * This is a basic check, it could be anything with the same address ...
+ */
+int bq769x0_probe_bms(struct bq769x0_bms_conf* conf);
+
+
+/* Read the BMS status Word and erase it's content
+ * conf: the bms configuration structure.
+ * status: pointer to an uint8_t value to store the old status value. May NOT be NULL.
+ * Return value:
+ * Upon successfull completion, returns 0. On error, returns a negative integer
+ * equivalent to errors from glibc.
+ */
+int bq769x0_bms_get_and_erase_status(struct bq769x0_bms_conf* conf, uint8_t* status);
+
+int bq769x0_bms_set_regs(struct bq769x0_bms_conf* conf,
+ uint8_t reg_start, uint8_t* values, uint8_t len);
+
+
+/* BMS config
+ * Performs default configuration of the BMS.
+ * conf: the bms configuration structure.
+ * old_status: pointer to an uint8_t value to store the old status value. May be NULL.
+ * Return value:
+ * Upon successfull completion, returns 0. On error, returns a negative integer
+ * equivalent to errors from glibc.
+ */
+int bq769x0_bms_config(struct bq769x0_bms_conf* conf, uint8_t* old_status);
+
+#endif /* EXTDRV_BQ769X0_H */
+