Skip to content

fuel_gauge: composite: more flexible data sourcing and channel querying #88397

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion boards/nordic/thingy53/thingy53_nrf5340_common.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@

fuel_gauge: fuel_gauge {
compatible = "zephyr,fuel-gauge-composite";
battery-voltage = <&vbatt>;
source-primary = <&vbatt>;
device-chemistry = "lithium-ion-polymer";
ocv-capacity-table-0 = <BATTERY_OCV_CURVE_LITHIUM_ION_POLYMER_DEFAULT>;
charge-full-design-microamp-hours = <1350000>;
Expand Down
9 changes: 9 additions & 0 deletions drivers/fuel_gauge/composite/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,12 @@ config FUEL_GAUGE_COMPOSITE
depends on DT_HAS_ZEPHYR_FUEL_GAUGE_COMPOSITE_ENABLED
help
Enable driver for the Zephyr composite fuel gauge device.

config FUEL_GAUGE_COMPOSITE_DATA_VALIDITY_MS
int "Data from sensor_fetch is cached for this long"
depends on FUEL_GAUGE_COMPOSITE
default 150
help
To ensure that data is consistent across multiple calls to
fuel_gauge_get_prop, sensor_fetch is only called if at least
this long has passed since the previous call.
152 changes: 117 additions & 35 deletions drivers/fuel_gauge/composite/fuel_gauge_composite.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,20 @@
#include <zephyr/kernel.h>

struct composite_config {
const struct device *battery_voltage;
const struct device *battery_current;
const struct device *source_primary;
const struct device *source_secondary;
int32_t ocv_lookup_table[BATTERY_OCV_TABLE_LEN];
uint32_t charge_capacity_microamp_hours;
enum battery_chemistry chemistry;
bool fg_channels;
};

static int composite_read_micro(const struct device *dev, enum sensor_channel chan, int *val)
struct composite_data {
k_ticks_t next_reading;
};

static int composite_fetch(const struct device *dev)
{
struct sensor_value sensor_val;
int rc;

rc = pm_device_runtime_get(dev);
Expand All @@ -34,61 +38,136 @@ static int composite_read_micro(const struct device *dev, enum sensor_channel ch
if (rc < 0) {
return rc;
}
rc = sensor_channel_get(dev, chan, &sensor_val);
if (rc < 0) {
return rc;
}
rc = pm_device_runtime_put(dev);
if (rc < 0) {
return rc;
return pm_device_runtime_put(dev);
}

static int composite_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val)
{
const struct composite_config *config = dev->config;
int rc;

rc = sensor_channel_get(config->source_primary, chan, val);
if ((rc == -ENOTSUP) && config->source_secondary) {
rc = sensor_channel_get(config->source_secondary, chan, val);
}
*val = sensor_value_to_micro(&sensor_val);
return 0;
return rc;
}

static int composite_get_prop(const struct device *dev, fuel_gauge_prop_t prop,
union fuel_gauge_prop_val *val)
{
const struct composite_config *config = dev->config;
int voltage, rc = 0;
struct composite_data *data = dev->data;
k_ticks_t now = k_uptime_ticks();
struct sensor_value sensor_val;
enum sensor_channel sensor_chan;
int64_t voltage;
int rc = 0;

/* Validate at build time that equivalent channel output fields still match */
BUILD_ASSERT(sizeof(val->absolute_state_of_charge) ==
sizeof(val->relative_state_of_charge));
BUILD_ASSERT(offsetof(union fuel_gauge_prop_val, absolute_state_of_charge) ==
offsetof(union fuel_gauge_prop_val, relative_state_of_charge));
BUILD_ASSERT(sizeof(val->current) == sizeof(val->avg_current));
BUILD_ASSERT(offsetof(union fuel_gauge_prop_val, current) ==
offsetof(union fuel_gauge_prop_val, avg_current));

if (now >= data->next_reading) {
/* Trigger a sample on the input devices */
rc = composite_fetch(config->source_primary);
if ((rc == 0) && config->source_secondary) {
rc = composite_fetch(config->source_secondary);
}
if (rc != 0) {
return rc;
}
/* Update timestamp for next reading */
data->next_reading =
now + k_ms_to_ticks_near64(CONFIG_FUEL_GAUGE_COMPOSITE_DATA_VALIDITY_MS);
}

switch (prop) {
case FUEL_GAUGE_FULL_CHARGE_CAPACITY:
if (config->charge_capacity_microamp_hours == 0) {
return -ENOTSUP;
rc = composite_channel_get(dev, SENSOR_CHAN_GAUGE_FULL_AVAIL_CAPACITY, &sensor_val);
if (rc == -ENOTSUP) {
if (config->charge_capacity_microamp_hours == 0) {
return -ENOTSUP;
}
val->full_charge_capacity = config->charge_capacity_microamp_hours;
rc = 0;
}
val->full_charge_capacity = config->charge_capacity_microamp_hours;
break;
case FUEL_GAUGE_DESIGN_CAPACITY:
if (config->charge_capacity_microamp_hours == 0) {
return -ENOTSUP;
rc = composite_channel_get(dev, SENSOR_CHAN_GAUGE_FULL_CHARGE_CAPACITY,
&sensor_val);
if (rc == -ENOTSUP) {
if (config->charge_capacity_microamp_hours == 0) {
return -ENOTSUP;
}
val->design_cap = config->charge_capacity_microamp_hours / 1000;
rc = 0;
}
val->full_charge_capacity = config->charge_capacity_microamp_hours / 1000;
break;
case FUEL_GAUGE_VOLTAGE:
rc = composite_read_micro(config->battery_voltage, SENSOR_CHAN_VOLTAGE,
&val->voltage);
sensor_chan = config->fg_channels ? SENSOR_CHAN_GAUGE_VOLTAGE : SENSOR_CHAN_VOLTAGE;
rc = composite_channel_get(dev, sensor_chan, &sensor_val);
val->voltage = sensor_value_to_micro(&sensor_val);
break;
case FUEL_GAUGE_ABSOLUTE_STATE_OF_CHARGE:
case FUEL_GAUGE_RELATIVE_STATE_OF_CHARGE:
if (config->ocv_lookup_table[0] == -1) {
return -ENOTSUP;
rc = composite_channel_get(dev, SENSOR_CHAN_GAUGE_STATE_OF_CHARGE, &sensor_val);
if (rc == 0) {
val->absolute_state_of_charge = sensor_val.val1;
} else if (rc == -ENOTSUP) {
if (config->ocv_lookup_table[0] == -1) {
return -ENOTSUP;
}
/* Fetch the voltage from the sensor */
sensor_chan = config->fg_channels ? SENSOR_CHAN_GAUGE_VOLTAGE
: SENSOR_CHAN_VOLTAGE;
rc = composite_channel_get(dev, sensor_chan, &sensor_val);
voltage = sensor_value_to_micro(&sensor_val);
if (rc == 0) {
/* Convert voltage to state of charge */
val->relative_state_of_charge =
battery_soc_lookup(config->ocv_lookup_table, voltage) /
1000;
}
}
rc = composite_read_micro(config->battery_voltage, SENSOR_CHAN_VOLTAGE, &voltage);
val->relative_state_of_charge =
battery_soc_lookup(config->ocv_lookup_table, voltage) / 1000;
break;
case FUEL_GAUGE_CURRENT:
if (config->battery_current == NULL) {
return -ENOTSUP;
}
rc = composite_read_micro(config->battery_current, SENSOR_CHAN_CURRENT,
&val->current);
case FUEL_GAUGE_AVG_CURRENT:
sensor_chan =
config->fg_channels ? SENSOR_CHAN_GAUGE_AVG_CURRENT : SENSOR_CHAN_CURRENT;
rc = composite_channel_get(dev, sensor_chan, &sensor_val);
val->current = sensor_value_to_micro(&sensor_val);
break;
case FUEL_GAUGE_TEMPERATURE:
sensor_chan = config->fg_channels ? SENSOR_CHAN_GAUGE_TEMP : SENSOR_CHAN_DIE_TEMP;
rc = composite_channel_get(dev, sensor_chan, &sensor_val);
/* Output unit = 0.1K (10x increase + 273.0) */
val->temperature = sensor_value_to_deci(&sensor_val) + 2730;
break;
default:
return -ENOTSUP;
}

return rc;
}

static int fuel_gauge_composite_init(const struct device *dev)
{
const struct composite_config *config = dev->config;

/* Validate sources are ready */
if (!device_is_ready(config->source_primary)) {
return -ENODEV;
}
if (config->source_secondary && !device_is_ready(config->source_secondary)) {
return -ENODEV;
}
return 0;
}

Expand All @@ -98,15 +177,18 @@ static DEVICE_API(fuel_gauge, composite_api) = {

#define COMPOSITE_INIT(inst) \
static const struct composite_config composite_##inst##_config = { \
.battery_voltage = DEVICE_DT_GET(DT_INST_PROP(inst, battery_voltage)), \
.battery_current = DEVICE_DT_GET_OR_NULL(DT_INST_PROP(inst, battery_current)), \
.source_primary = DEVICE_DT_GET(DT_INST_PROP(inst, source_primary)), \
.source_secondary = DEVICE_DT_GET_OR_NULL(DT_INST_PROP(inst, source_secondary)), \
.ocv_lookup_table = \
BATTERY_OCV_TABLE_DT_GET(DT_DRV_INST(inst), ocv_capacity_table_0), \
.charge_capacity_microamp_hours = \
DT_INST_PROP_OR(inst, charge_full_design_microamp_hours, 0), \
.chemistry = BATTERY_CHEMISTRY_DT_GET(inst), \
.fg_channels = DT_INST_PROP(inst, fuel_gauge_channels), \
}; \
DEVICE_DT_INST_DEFINE(inst, NULL, NULL, NULL, &composite_##inst##_config, POST_KERNEL, \
static struct composite_data composite_##inst##_data; \
DEVICE_DT_INST_DEFINE(inst, fuel_gauge_composite_init, NULL, &composite_##inst##_data, \
&composite_##inst##_config, POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, &composite_api);

DT_INST_FOREACH_STATUS_OKAY(COMPOSITE_INIT)
30 changes: 15 additions & 15 deletions dts/bindings/fuel-gauge/zephyr,fuel-gauge-composite.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,32 @@
# SPDX-License-Identifier: Apache-2.0

description: |
Composite fuel-gauge constructed from analog input values
Composite fuel-gauge constructed from sensor devices

compatible: "zephyr,fuel-gauge-composite"

include: [fuel-gauge.yaml, battery.yaml]

properties:
battery-voltage:
source-primary:
type: phandle
required: true
description: |
Device to read battery voltage from.
Primary device to read sensor data from.
Device must implement the sensor API.

Device must implement the sensor API and provide the
`SENSOR_CHAN_VOLTAGE` channel.

battery-current:
source-secondary:
type: phandle
description: |
Device to read battery current from.

Device must implement the sensor API and provide the
`SENSOR_CHAN_CURRENT` channel.
Secondary device to read sensor data from.
This device will be used if input-primary does not expose
a channel.

device-chemistry:
required: true
Device must implement the sensor API.

ocv-capacity-table-0:
required: true
fuel-gauge-channels:
type: boolean
description: |
Sources should be queried by the sensor API fuel gauge
channels (SENSOR_CHAN_GAUGE_*), instead of the generic
channels (SENSOR_CHAN_VOLTAGE, etc).
2 changes: 1 addition & 1 deletion tests/drivers/build_all/fuel_gauge/app.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
test_fuel_gauge: fuel_gauge {
compatible = "zephyr,fuel-gauge-composite";
status = "okay";
battery-voltage = <&test_vbatt>;
source-primary = <&test_vbatt>;
device-chemistry = "lithium-ion-polymer";
ocv-capacity-table-0 = <0>;
charge-full-design-microamp-hours = <1350000>;
Expand Down
4 changes: 2 additions & 2 deletions tests/drivers/build_all/sensor/adc.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ test_current: current_amp {
test_composite_fuel_gauge: composite_fuel_gauge {
compatible = "zephyr,fuel-gauge-composite";
status = "okay";
battery-voltage = <&test_voltage>;
battery-current = <&test_current>;
source-primary = <&test_voltage>;
source-secondary = <&test_current>;
device-chemistry = "lithium-ion-polymer";
ocv-capacity-table-0 = <BATTERY_OCV_CURVE_LITHIUM_ION_POLYMER_DEFAULT>;
charge-full-design-microamp-hours = <1000000>;
Expand Down