338 lines
10 KiB
C
338 lines
10 KiB
C
/*
|
|
* Copyright (c) 2022 Leica Geosystems AG
|
|
*
|
|
* Copyright 2022 Google LLC
|
|
* Copyright 2023 Microsoft Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT sbs_sbs_gauge_new_api
|
|
|
|
#include "sbs_gauge.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <zephyr/devicetree.h>
|
|
#include <zephyr/drivers/fuel_gauge.h>
|
|
#include <zephyr/drivers/i2c.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
#include <zephyr/sys/util.h>
|
|
|
|
LOG_MODULE_REGISTER(sbs_gauge);
|
|
|
|
static int sbs_cmd_reg_read(const struct device *dev, uint8_t reg_addr, uint16_t *val)
|
|
{
|
|
const struct sbs_gauge_config *cfg;
|
|
uint8_t i2c_data[2];
|
|
int status;
|
|
|
|
cfg = dev->config;
|
|
status = i2c_burst_read_dt(&cfg->i2c, reg_addr, i2c_data, ARRAY_SIZE(i2c_data));
|
|
if (status < 0) {
|
|
LOG_ERR("Unable to read register");
|
|
return status;
|
|
}
|
|
|
|
*val = sys_get_le16(i2c_data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sbs_cmd_reg_write(const struct device *dev, uint8_t reg_addr, uint16_t val)
|
|
{
|
|
const struct sbs_gauge_config *config = dev->config;
|
|
uint8_t buf[2];
|
|
|
|
sys_put_le16(val, buf);
|
|
|
|
return i2c_burst_write_dt(&config->i2c, reg_addr, buf, sizeof(buf));
|
|
}
|
|
|
|
static int sbs_cmd_buffer_read(const struct device *dev, uint8_t reg_addr, char *buffer,
|
|
const uint8_t buffer_size)
|
|
{
|
|
const struct sbs_gauge_config *cfg;
|
|
int status;
|
|
|
|
cfg = dev->config;
|
|
status = i2c_burst_read_dt(&cfg->i2c, reg_addr, buffer, buffer_size);
|
|
if (status < 0) {
|
|
LOG_ERR("Unable to read register");
|
|
return status;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sbs_gauge_get_prop(const struct device *dev, fuel_gauge_prop_t prop,
|
|
union fuel_gauge_prop_val *val)
|
|
{
|
|
int rc = 0;
|
|
uint16_t tmp_val = 0;
|
|
|
|
switch (prop) {
|
|
case FUEL_GAUGE_AVG_CURRENT:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_AVG_CURRENT, &tmp_val);
|
|
val->avg_current = tmp_val * 1000;
|
|
break;
|
|
case FUEL_GAUGE_CYCLE_COUNT:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_CYCLE_COUNT, &tmp_val);
|
|
val->cycle_count = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_CURRENT:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_CURRENT, &tmp_val);
|
|
val->current = (int16_t)tmp_val * 1000;
|
|
break;
|
|
case FUEL_GAUGE_FULL_CHARGE_CAPACITY:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_FULL_CAPACITY, &tmp_val);
|
|
val->full_charge_capacity = tmp_val * 1000;
|
|
break;
|
|
case FUEL_GAUGE_REMAINING_CAPACITY:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_REM_CAPACITY, &tmp_val);
|
|
val->remaining_capacity = tmp_val * 1000;
|
|
break;
|
|
case FUEL_GAUGE_RUNTIME_TO_EMPTY:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_RUNTIME2EMPTY, &tmp_val);
|
|
val->runtime_to_empty = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_RUNTIME_TO_FULL:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_AVG_TIME2FULL, &tmp_val);
|
|
val->runtime_to_full = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_SBS_MFR_ACCESS:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_MANUFACTURER_ACCESS, &tmp_val);
|
|
val->sbs_mfr_access_word = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_ABSOLUTE_STATE_OF_CHARGE:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_ASOC, &tmp_val);
|
|
val->absolute_state_of_charge = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_RELATIVE_STATE_OF_CHARGE:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_RSOC, &tmp_val);
|
|
val->relative_state_of_charge = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_TEMPERATURE:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_TEMP, &tmp_val);
|
|
val->temperature = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_VOLTAGE:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_VOLTAGE, &tmp_val);
|
|
val->voltage = tmp_val * 1000;
|
|
break;
|
|
case FUEL_GAUGE_SBS_MODE:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_BATTERY_MODE, &tmp_val);
|
|
val->sbs_mode = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_CHARGE_CURRENT:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_CHG_CURRENT, &tmp_val);
|
|
val->chg_current = tmp_val * 1000;
|
|
break;
|
|
case FUEL_GAUGE_CHARGE_VOLTAGE:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_CHG_VOLTAGE, &tmp_val);
|
|
val->chg_voltage = tmp_val * 1000;
|
|
break;
|
|
case FUEL_GAUGE_STATUS:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_FLAGS, &tmp_val);
|
|
val->fg_status = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_DESIGN_CAPACITY:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_NOM_CAPACITY, &tmp_val);
|
|
val->design_cap = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_DESIGN_VOLTAGE:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_DESIGN_VOLTAGE, &tmp_val);
|
|
val->design_volt = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_SBS_ATRATE:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_AR, &tmp_val);
|
|
val->sbs_at_rate = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_SBS_ATRATE_TIME_TO_FULL:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_ARTTF, &tmp_val);
|
|
val->sbs_at_rate_time_to_full = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_SBS_ATRATE_TIME_TO_EMPTY:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_ARTTE, &tmp_val);
|
|
val->sbs_at_rate_time_to_empty = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_SBS_ATRATE_OK:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_AROK, &tmp_val);
|
|
val->sbs_at_rate_ok = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_SBS_REMAINING_CAPACITY_ALARM:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_REM_CAPACITY_ALARM, &tmp_val);
|
|
val->sbs_remaining_capacity_alarm = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_SBS_REMAINING_TIME_ALARM:
|
|
rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_REM_TIME_ALARM, &tmp_val);
|
|
val->sbs_remaining_time_alarm = tmp_val;
|
|
break;
|
|
default:
|
|
rc = -ENOTSUP;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int sbs_gauge_do_battery_cutoff(const struct device *dev)
|
|
{
|
|
int rc = -ENOTSUP;
|
|
const struct sbs_gauge_config *cfg = dev->config;
|
|
|
|
if (cfg->cutoff_cfg == NULL) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
for (int i = 0; i < cfg->cutoff_cfg->payload_size; i++) {
|
|
rc = sbs_cmd_reg_write(dev, cfg->cutoff_cfg->reg, cfg->cutoff_cfg->payload[i]);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int sbs_gauge_set_prop(const struct device *dev, fuel_gauge_prop_t prop,
|
|
union fuel_gauge_prop_val val)
|
|
{
|
|
int rc = 0;
|
|
uint16_t tmp_val = 0;
|
|
|
|
switch (prop) {
|
|
|
|
case FUEL_GAUGE_SBS_MFR_ACCESS:
|
|
rc = sbs_cmd_reg_write(dev, SBS_GAUGE_CMD_MANUFACTURER_ACCESS,
|
|
val.sbs_mfr_access_word);
|
|
val.sbs_mfr_access_word = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_SBS_REMAINING_CAPACITY_ALARM:
|
|
rc = sbs_cmd_reg_write(dev, SBS_GAUGE_CMD_REM_CAPACITY_ALARM,
|
|
val.sbs_remaining_capacity_alarm);
|
|
val.sbs_remaining_capacity_alarm = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_SBS_REMAINING_TIME_ALARM:
|
|
rc = sbs_cmd_reg_write(dev, SBS_GAUGE_CMD_REM_TIME_ALARM,
|
|
val.sbs_remaining_time_alarm);
|
|
val.sbs_remaining_time_alarm = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_SBS_MODE:
|
|
rc = sbs_cmd_reg_write(dev, SBS_GAUGE_CMD_BATTERY_MODE, val.sbs_mode);
|
|
val.sbs_mode = tmp_val;
|
|
break;
|
|
case FUEL_GAUGE_SBS_ATRATE:
|
|
rc = sbs_cmd_reg_write(dev, SBS_GAUGE_CMD_AR, val.sbs_at_rate);
|
|
val.sbs_at_rate = tmp_val;
|
|
break;
|
|
default:
|
|
rc = -ENOTSUP;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int sbs_gauge_get_buffer_prop(const struct device *dev,
|
|
fuel_gauge_prop_t prop_type, void *dst,
|
|
size_t dst_len)
|
|
{
|
|
int rc = 0;
|
|
|
|
switch (prop_type) {
|
|
case FUEL_GAUGE_MANUFACTURER_NAME:
|
|
if (dst_len == sizeof(struct sbs_gauge_manufacturer_name)) {
|
|
rc = sbs_cmd_buffer_read(dev, SBS_GAUGE_CMD_MANUFACTURER_NAME, (char *)dst,
|
|
dst_len);
|
|
} else {
|
|
rc = -EINVAL;
|
|
}
|
|
break;
|
|
case FUEL_GAUGE_DEVICE_NAME:
|
|
if (dst_len == sizeof(struct sbs_gauge_device_name)) {
|
|
rc = sbs_cmd_buffer_read(dev, SBS_GAUGE_CMD_DEVICE_NAME, (char *)dst,
|
|
dst_len);
|
|
} else {
|
|
rc = -EINVAL;
|
|
}
|
|
break;
|
|
case FUEL_GAUGE_DEVICE_CHEMISTRY:
|
|
if (dst_len == sizeof(struct sbs_gauge_device_chemistry)) {
|
|
rc = sbs_cmd_buffer_read(dev, SBS_GAUGE_CMD_DEVICE_CHEMISTRY, (char *)dst,
|
|
dst_len);
|
|
} else {
|
|
rc = -EINVAL;
|
|
}
|
|
break;
|
|
default:
|
|
rc = -ENOTSUP;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @brief initialize the fuel gauge
|
|
*
|
|
* @return 0 for success
|
|
*/
|
|
static int sbs_gauge_init(const struct device *dev)
|
|
{
|
|
const struct sbs_gauge_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 sbs_gauge_driver_api = {
|
|
.get_property = &sbs_gauge_get_prop,
|
|
.set_property = &sbs_gauge_set_prop,
|
|
.get_buffer_property = &sbs_gauge_get_buffer_prop,
|
|
.battery_cutoff = &sbs_gauge_do_battery_cutoff,
|
|
};
|
|
|
|
/* Concatenates index to battery config to create unique cfg variable name per instance. */
|
|
#define _SBS_GAUGE_BATT_CUTOFF_CFG_VAR_NAME(index) sbs_gauge_batt_cutoff_cfg_##index
|
|
|
|
/* Declare and define the battery config struct */
|
|
#define _SBS_GAUGE_CONFIG_DEFINE(index) \
|
|
static const struct sbs_gauge_battery_cutoff_config _SBS_GAUGE_BATT_CUTOFF_CFG_VAR_NAME( \
|
|
index) = { \
|
|
.reg = DT_INST_PROP(index, battery_cutoff_reg_addr), \
|
|
.payload = DT_INST_PROP(index, battery_cutoff_payload), \
|
|
.payload_size = DT_INST_PROP_LEN(index, battery_cutoff_payload), \
|
|
};
|
|
|
|
/* Conditionally defined battery config based on battery cutoff support */
|
|
#define SBS_GAUGE_CONFIG_DEFINE(index) \
|
|
COND_CODE_1(DT_INST_PROP(index, battery_cutoff_support), \
|
|
(_SBS_GAUGE_CONFIG_DEFINE(index)), (;))
|
|
|
|
/* Conditionally get the battery config variable name or NULL based on battery cutoff support */
|
|
#define SBS_GAUGE_GET_BATTERY_CONFIG_NAME(index) \
|
|
COND_CODE_1(DT_INST_PROP(index, battery_cutoff_support), \
|
|
(&_SBS_GAUGE_BATT_CUTOFF_CFG_VAR_NAME(index)), (NULL))
|
|
|
|
#define SBS_GAUGE_INIT(index) \
|
|
SBS_GAUGE_CONFIG_DEFINE(index); \
|
|
static const struct sbs_gauge_config sbs_gauge_config_##index = { \
|
|
.i2c = I2C_DT_SPEC_INST_GET(index), \
|
|
.cutoff_cfg = SBS_GAUGE_GET_BATTERY_CONFIG_NAME(index)}; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(index, &sbs_gauge_init, NULL, NULL, &sbs_gauge_config_##index, \
|
|
POST_KERNEL, CONFIG_FUEL_GAUGE_INIT_PRIORITY, \
|
|
&sbs_gauge_driver_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(SBS_GAUGE_INIT)
|
|
|
|
#define CUTOFF_PAYLOAD_SIZE_ASSERT(inst) \
|
|
BUILD_ASSERT(DT_INST_PROP_LEN_OR(inst, battery_cutoff_payload, 0) <= \
|
|
SBS_GAUGE_CUTOFF_PAYLOAD_MAX_SIZE);
|
|
DT_INST_FOREACH_STATUS_OKAY(CUTOFF_PAYLOAD_SIZE_ASSERT)
|