2023-06-19 22:33:13 +08:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2023, ithinx GmbH
|
|
|
|
* Copyright (c) 2023, Tonies GmbH
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define DT_DRV_COMPAT ti_bq27z746
|
|
|
|
|
|
|
|
#include "bq27z746.h"
|
|
|
|
|
|
|
|
#include <zephyr/kernel.h>
|
|
|
|
#include <zephyr/drivers/fuel_gauge.h>
|
|
|
|
#include <zephyr/drivers/i2c.h>
|
|
|
|
#include <zephyr/sys/byteorder.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
LOG_MODULE_REGISTER(BQ27Z746);
|
|
|
|
|
|
|
|
#define BQ27Z746_MAC_DATA_LEN 32
|
|
|
|
#define BQ27Z746_MAC_OVERHEAD_LEN 4 /* 2 cmd bytes, 1 length byte, 1 checksum byte */
|
|
|
|
#define BQ27Z746_MAC_COMPLETE_LEN (BQ27Z746_MAC_DATA_LEN + BQ27Z746_MAC_OVERHEAD_LEN)
|
|
|
|
|
|
|
|
static int bq27z746_read16(const struct device *dev, uint8_t reg, uint16_t *value)
|
|
|
|
{
|
|
|
|
uint8_t i2c_data[2];
|
|
|
|
const struct bq27z746_config *cfg = dev->config;
|
|
|
|
const int status = i2c_burst_read_dt(&cfg->i2c, reg, i2c_data, sizeof(i2c_data));
|
|
|
|
|
|
|
|
if (status < 0) {
|
|
|
|
LOG_ERR("Unable to read register");
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
*value = sys_get_le16(i2c_data);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int bq27z746_write16(const struct device *dev, uint8_t reg, uint16_t value)
|
|
|
|
{
|
|
|
|
uint8_t buf[3];
|
|
|
|
const struct bq27z746_config *cfg = dev->config;
|
|
|
|
|
|
|
|
buf[0] = reg;
|
|
|
|
sys_put_le16(value, &buf[1]);
|
|
|
|
|
|
|
|
return i2c_write_dt(&cfg->i2c, buf, sizeof(buf));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int bq27z746_read_mac(const struct device *dev, uint16_t cmd, uint8_t *data, int len)
|
|
|
|
{
|
|
|
|
if (len > BQ27Z746_MAC_DATA_LEN) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t buf[BQ27Z746_MAC_COMPLETE_LEN];
|
|
|
|
const struct bq27z746_config *cfg = dev->config;
|
|
|
|
|
|
|
|
/* Instead of MAC, ALTMAC is used as reccommended in the datasheet */
|
|
|
|
int ret = bq27z746_write16(dev, BQ27Z746_ALTMANUFACTURERACCESS, cmd);
|
|
|
|
|
|
|
|
if (ret != 0) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The data read from BQ27Z746_ALTMANUFACTURERACCESS is:
|
|
|
|
* 0..1: The command (for verification)
|
|
|
|
* 2..33: The data
|
|
|
|
* 34: Checksum calculated as (uint8_t)(0xFF - (sum of all command and data bytes))
|
|
|
|
* 35: Length including command, checksum and length (e.g. data length + 4)
|
|
|
|
*/
|
|
|
|
|
|
|
|
ret = i2c_burst_read_dt(&cfg->i2c, BQ27Z746_ALTMANUFACTURERACCESS, buf,
|
|
|
|
BQ27Z746_MAC_COMPLETE_LEN);
|
|
|
|
if (ret != 0) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The first two bytes read is the command and is used for verification */
|
|
|
|
const uint16_t read_cmd = sys_get_le16(buf);
|
|
|
|
|
|
|
|
if (read_cmd != cmd) {
|
|
|
|
LOG_ERR("Read command 0x%x != written command 0x%x", read_cmd, cmd);
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint8_t checksum_actual = buf[34];
|
|
|
|
uint8_t sum = 0; /* Intentionally 8 bit wide and overflowing */
|
|
|
|
|
|
|
|
for (int i = 0; i < BQ27Z746_MAC_COMPLETE_LEN - 2; i++) {
|
|
|
|
sum += buf[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint8_t checksum_expected = 0xFF - sum;
|
|
|
|
|
|
|
|
if (checksum_expected != checksum_actual) {
|
|
|
|
LOG_ERR("Checksum mismatch");
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* First byte of the user buffer is the length */
|
|
|
|
data[0] = buf[35] - BQ27Z746_MAC_OVERHEAD_LEN;
|
|
|
|
|
|
|
|
/* Copy only the data to the user buffer (= skipping the first two command bytes) */
|
|
|
|
memcpy(&data[1], &buf[2], len);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-09-23 02:17:27 +08:00
|
|
|
static int bq27z746_get_prop(const struct device *dev, fuel_gauge_prop_t prop,
|
|
|
|
union fuel_gauge_prop_val *val)
|
2023-06-19 22:33:13 +08:00
|
|
|
{
|
|
|
|
int rc = 0;
|
2023-09-23 02:17:27 +08:00
|
|
|
uint16_t tmp_val = 0;
|
2023-06-19 22:33:13 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Possibly negative values must be cast from uint16 to int16 first to
|
|
|
|
* then correctly end up in the wider datatypes of `prop`.
|
|
|
|
*/
|
|
|
|
|
2023-09-23 02:17:27 +08:00
|
|
|
switch (prop) {
|
2023-06-19 22:33:13 +08:00
|
|
|
case FUEL_GAUGE_AVG_CURRENT:
|
2023-09-23 02:17:27 +08:00
|
|
|
rc = bq27z746_read16(dev, BQ27Z746_AVERAGECURRENT, &tmp_val);
|
|
|
|
val->avg_current = (int16_t)tmp_val * 1000;
|
2023-06-19 22:33:13 +08:00
|
|
|
break;
|
|
|
|
case FUEL_GAUGE_CYCLE_COUNT:
|
2023-09-23 02:17:27 +08:00
|
|
|
rc = bq27z746_read16(dev, BQ27Z746_CYCLECOUNT, &tmp_val);
|
|
|
|
val->cycle_count = tmp_val * 100;
|
2023-06-19 22:33:13 +08:00
|
|
|
break;
|
|
|
|
case FUEL_GAUGE_CURRENT:
|
2023-09-23 02:17:27 +08:00
|
|
|
rc = bq27z746_read16(dev, BQ27Z746_CURRENT, &tmp_val);
|
|
|
|
val->current = (int16_t)tmp_val * 1000;
|
2023-06-19 22:33:13 +08:00
|
|
|
break;
|
|
|
|
case FUEL_GAUGE_FULL_CHARGE_CAPACITY:
|
2023-09-23 02:17:27 +08:00
|
|
|
rc = bq27z746_read16(dev, BQ27Z746_FULLCHARGECAPACITY, &tmp_val);
|
|
|
|
val->full_charge_capacity = tmp_val * 1000;
|
2023-06-19 22:33:13 +08:00
|
|
|
break;
|
|
|
|
case FUEL_GAUGE_REMAINING_CAPACITY:
|
2023-09-23 02:17:27 +08:00
|
|
|
rc = bq27z746_read16(dev, BQ27Z746_REMAININGCAPACITY, &tmp_val);
|
|
|
|
val->remaining_capacity = tmp_val * 1000;
|
2023-06-19 22:33:13 +08:00
|
|
|
break;
|
|
|
|
case FUEL_GAUGE_RUNTIME_TO_EMPTY:
|
2023-09-23 02:17:27 +08:00
|
|
|
rc = bq27z746_read16(dev, BQ27Z746_AVERAGETIMETOEMPTY, &tmp_val);
|
|
|
|
val->runtime_to_empty = tmp_val;
|
2023-06-19 22:33:13 +08:00
|
|
|
break;
|
|
|
|
case FUEL_GAUGE_RUNTIME_TO_FULL:
|
2023-09-23 02:17:27 +08:00
|
|
|
rc = bq27z746_read16(dev, BQ27Z746_AVERAGETIMETOFULL, &tmp_val);
|
|
|
|
val->runtime_to_full = tmp_val;
|
2023-06-19 22:33:13 +08:00
|
|
|
break;
|
|
|
|
case FUEL_GAUGE_SBS_MFR_ACCESS:
|
2023-09-23 02:17:27 +08:00
|
|
|
rc = bq27z746_read16(dev, BQ27Z746_MANUFACTURERACCESS, &tmp_val);
|
|
|
|
val->sbs_mfr_access_word = tmp_val;
|
2023-06-19 22:33:13 +08:00
|
|
|
break;
|
|
|
|
case FUEL_GAUGE_RELATIVE_STATE_OF_CHARGE:
|
2023-09-23 02:17:27 +08:00
|
|
|
rc = bq27z746_read16(dev, BQ27Z746_RELATIVESTATEOFCHARGE, &tmp_val);
|
|
|
|
val->relative_state_of_charge = tmp_val;
|
2023-06-19 22:33:13 +08:00
|
|
|
break;
|
|
|
|
case FUEL_GAUGE_TEMPERATURE:
|
2023-09-23 02:17:27 +08:00
|
|
|
rc = bq27z746_read16(dev, BQ27Z746_TEMPERATURE, &tmp_val);
|
|
|
|
val->temperature = tmp_val;
|
2023-06-19 22:33:13 +08:00
|
|
|
break;
|
|
|
|
case FUEL_GAUGE_VOLTAGE:
|
2023-09-23 02:17:27 +08:00
|
|
|
rc = bq27z746_read16(dev, BQ27Z746_VOLTAGE, &tmp_val);
|
|
|
|
val->voltage = tmp_val * 1000;
|
2023-06-19 22:33:13 +08:00
|
|
|
break;
|
|
|
|
case FUEL_GAUGE_SBS_ATRATE:
|
2023-09-23 02:17:27 +08:00
|
|
|
rc = bq27z746_read16(dev, BQ27Z746_ATRATE, &tmp_val);
|
|
|
|
val->sbs_at_rate = (int16_t)tmp_val;
|
2023-06-19 22:33:13 +08:00
|
|
|
break;
|
|
|
|
case FUEL_GAUGE_SBS_ATRATE_TIME_TO_EMPTY:
|
2023-09-23 02:17:27 +08:00
|
|
|
rc = bq27z746_read16(dev, BQ27Z746_ATRATETIMETOEMPTY, &tmp_val);
|
|
|
|
val->sbs_at_rate_time_to_empty = tmp_val;
|
2023-06-19 22:33:13 +08:00
|
|
|
break;
|
|
|
|
case FUEL_GAUGE_CHARGE_VOLTAGE:
|
2023-09-23 02:17:27 +08:00
|
|
|
rc = bq27z746_read16(dev, BQ27Z746_CHARGINGVOLTAGE, &tmp_val);
|
2023-10-06 06:59:58 +08:00
|
|
|
val->chg_voltage = tmp_val * 1000;
|
2023-06-19 22:33:13 +08:00
|
|
|
break;
|
|
|
|
case FUEL_GAUGE_CHARGE_CURRENT:
|
2023-09-23 02:17:27 +08:00
|
|
|
rc = bq27z746_read16(dev, BQ27Z746_CHARGINGCURRENT, &tmp_val);
|
2023-10-06 06:59:58 +08:00
|
|
|
val->chg_current = tmp_val * 1000;
|
2023-06-19 22:33:13 +08:00
|
|
|
break;
|
|
|
|
case FUEL_GAUGE_STATUS:
|
2023-09-23 02:17:27 +08:00
|
|
|
rc = bq27z746_read16(dev, BQ27Z746_BATTERYSTATUS, &tmp_val);
|
|
|
|
val->fg_status = tmp_val;
|
2023-06-19 22:33:13 +08:00
|
|
|
break;
|
|
|
|
case FUEL_GAUGE_DESIGN_CAPACITY:
|
2023-09-23 02:17:27 +08:00
|
|
|
rc = bq27z746_read16(dev, BQ27Z746_DESIGNCAPACITY, &tmp_val);
|
|
|
|
val->design_cap = tmp_val;
|
2023-06-19 22:33:13 +08:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
rc = -ENOTSUP;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int bq27z746_get_buffer_prop(const struct device *dev,
|
2023-09-23 02:17:27 +08:00
|
|
|
fuel_gauge_prop_t property_type, void *dst,
|
2023-06-19 22:33:13 +08:00
|
|
|
size_t dst_len)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
|
2023-09-23 02:17:27 +08:00
|
|
|
switch (property_type) {
|
2023-06-19 22:33:13 +08:00
|
|
|
case FUEL_GAUGE_MANUFACTURER_NAME:
|
|
|
|
if (dst_len == sizeof(struct sbs_gauge_manufacturer_name)) {
|
|
|
|
rc = bq27z746_read_mac(dev, BQ27Z746_MAC_CMD_MANUFACTURER_NAME,
|
|
|
|
(uint8_t *)dst, dst_len - 1);
|
|
|
|
} else {
|
|
|
|
rc = -EINVAL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case FUEL_GAUGE_DEVICE_NAME:
|
|
|
|
if (dst_len == sizeof(struct sbs_gauge_device_name)) {
|
|
|
|
rc = bq27z746_read_mac(dev, BQ27Z746_MAC_CMD_DEVICE_NAME, (uint8_t *)dst,
|
|
|
|
dst_len - 1);
|
|
|
|
} else {
|
|
|
|
rc = -EINVAL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case FUEL_GAUGE_DEVICE_CHEMISTRY:
|
|
|
|
if (dst_len == sizeof(struct sbs_gauge_device_chemistry)) {
|
|
|
|
rc = bq27z746_read_mac(dev, BQ27Z746_MAC_CMD_DEVICE_CHEM, (uint8_t *)dst,
|
|
|
|
dst_len - 1);
|
|
|
|
} else {
|
|
|
|
rc = -EINVAL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
rc = -ENOTSUP;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2023-09-23 02:17:27 +08:00
|
|
|
static int bq27z746_set_prop(const struct device *dev, fuel_gauge_prop_t prop,
|
|
|
|
union fuel_gauge_prop_val val)
|
2023-06-19 22:33:13 +08:00
|
|
|
{
|
|
|
|
int rc = 0;
|
2023-09-23 02:17:27 +08:00
|
|
|
uint16_t tmp_val = 0;
|
2023-06-19 22:33:13 +08:00
|
|
|
|
2023-09-23 02:17:27 +08:00
|
|
|
switch (prop) {
|
2023-06-19 22:33:13 +08:00
|
|
|
case FUEL_GAUGE_SBS_MFR_ACCESS:
|
2023-09-23 02:17:27 +08:00
|
|
|
rc = bq27z746_write16(dev, BQ27Z746_MANUFACTURERACCESS, val.sbs_mfr_access_word);
|
|
|
|
val.sbs_mfr_access_word = tmp_val;
|
2023-06-19 22:33:13 +08:00
|
|
|
break;
|
|
|
|
case FUEL_GAUGE_SBS_ATRATE:
|
2023-09-23 02:17:27 +08:00
|
|
|
rc = bq27z746_write16(dev, BQ27Z746_ATRATE, val.sbs_at_rate);
|
|
|
|
val.sbs_at_rate = tmp_val;
|
2023-06-19 22:33:13 +08:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
rc = -ENOTSUP;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int bq27z746_init(const struct device *dev)
|
|
|
|
{
|
|
|
|
const struct bq27z746_config *cfg;
|
|
|
|
|
|
|
|
cfg = dev->config;
|
|
|
|
|
|
|
|
if (!device_is_ready(cfg->i2c.bus)) {
|
|
|
|
LOG_ERR("Bus device is not ready");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct fuel_gauge_driver_api bq27z746_driver_api = {
|
2023-09-20 01:40:10 +08:00
|
|
|
.get_property = &bq27z746_get_prop,
|
2023-09-21 06:16:41 +08:00
|
|
|
.set_property = &bq27z746_set_prop,
|
2023-06-19 22:33:13 +08:00
|
|
|
.get_buffer_property = &bq27z746_get_buffer_prop,
|
|
|
|
};
|
|
|
|
|
|
|
|
#define BQ27Z746_INIT(index) \
|
|
|
|
\
|
|
|
|
static const struct bq27z746_config bq27z746_config_##index = { \
|
|
|
|
.i2c = I2C_DT_SPEC_INST_GET(index), \
|
|
|
|
}; \
|
|
|
|
\
|
|
|
|
DEVICE_DT_INST_DEFINE(index, &bq27z746_init, NULL, NULL, &bq27z746_config_##index, \
|
|
|
|
POST_KERNEL, CONFIG_FUEL_GAUGE_INIT_PRIORITY, &bq27z746_driver_api);
|
|
|
|
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(BQ27Z746_INIT)
|