Skip to content

Commit 72fac96

Browse files
LostinTimeandspaceYTjhedberg
authored andcommitted
drivers: fuelgauge: Added test for ADI LTC2959
Adds test and native sim support for ADI LTC2959 fuel gauge. Signed-off-by: Nathan Winslow <[email protected]>
1 parent 22710b7 commit 72fac96

File tree

6 files changed

+325
-0
lines changed

6 files changed

+325
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
5+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
6+
project(device)
7+
8+
target_sources(app PRIVATE src/test_ltc2959.c)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
CONFIG_EMUL=y
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* SPDX-FileCopyrightText: Copyright The Zephyr Project Contributors
4+
*/
5+
6+
&i2c0 {
7+
ltc2959: ltc2959@63 {
8+
compatible = "adi,ltc2959";
9+
reg = <0x63>;
10+
rsense-milliohms = <50>;
11+
status = "okay";
12+
};
13+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CONFIG_ZTEST=y
2+
CONFIG_I2C=y
3+
CONFIG_TEST_USERSPACE=y
4+
CONFIG_LOG=y
5+
6+
CONFIG_FUEL_GAUGE=y
7+
CONFIG_FUEL_GAUGE_LTC2959=y
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
/*
2+
* Copyright (c) 2025 Nathan Winslow <[email protected]>
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/ztest.h>
8+
#include <zephyr/device.h>
9+
#include <zephyr/drivers/fuel_gauge.h>
10+
#include <zephyr/logging/log.h>
11+
#include <zephyr/kernel.h>
12+
#include <zephyr/sys/byteorder.h>
13+
14+
#define LTC_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(adi_ltc2959)
15+
BUILD_ASSERT(DT_NODE_EXISTS(LTC_NODE), "No adi,ltc2959 node in DT for tests");
16+
17+
#define RSENSE_MOHMS DT_PROP(LTC_NODE, rsense_milliohms)
18+
19+
/* Integer LSB sizes (keep tests stable) */
20+
#define CURRENT_LSB_UA (97500000ULL / ((uint64_t)RSENSE_MOHMS * 32768ULL))
21+
#define VOLTAGE_MAX_UV (UINT16_MAX * 955U) /* ~955 = 62.6V full scale / 65536 */
22+
23+
struct ltc2959_fixture {
24+
const struct device *dev;
25+
const struct fuel_gauge_driver_api *api;
26+
};
27+
28+
static void *ltc2959_setup(void)
29+
{
30+
static ZTEST_DMEM struct ltc2959_fixture fixture;
31+
32+
fixture.dev = DEVICE_DT_GET_ANY(adi_ltc2959);
33+
k_object_access_all_grant(fixture.dev);
34+
35+
zassume_true(device_is_ready(fixture.dev), "Fuel Gauge not found");
36+
37+
return &fixture;
38+
}
39+
40+
LOG_MODULE_REGISTER(test_ltc2959, LOG_LEVEL_INF);
41+
42+
ZTEST_F(ltc2959, test_get_props__returns_ok)
43+
{
44+
fuel_gauge_prop_t props[] = {
45+
FUEL_GAUGE_STATUS,
46+
FUEL_GAUGE_VOLTAGE,
47+
FUEL_GAUGE_CURRENT,
48+
FUEL_GAUGE_TEMPERATURE,
49+
};
50+
51+
union fuel_gauge_prop_val vals[ARRAY_SIZE(props)];
52+
int ret = fuel_gauge_get_props(fixture->dev, props, vals, ARRAY_SIZE(props));
53+
54+
#if CONFIG_EMUL
55+
zassert_equal(vals[0].fg_status, 0x01);
56+
zassert_equal(vals[1].voltage, 0x00);
57+
zassert_equal(vals[2].current, 0x00);
58+
zassert_equal(vals[3].temperature, 0x00);
59+
#else
60+
zassert_between_inclusive(vals[0].fg_status, 0, 0xFF);
61+
zassert_between_inclusive(vals[1].voltage, 0, VOLTAGE_MAX_UV);
62+
#endif
63+
zassert_equal(ret, 0, "Getting bad property has a good status.");
64+
}
65+
66+
ZTEST_F(ltc2959, test_set_get_single_prop)
67+
{
68+
int ret;
69+
union fuel_gauge_prop_val in = {.low_voltage_alarm = 1200000}; /* 1.2V */
70+
71+
ret = fuel_gauge_set_prop(fixture->dev, FUEL_GAUGE_LOW_VOLTAGE_ALARM, in);
72+
zassert_equal(ret, 0, "set low voltage threshold failed");
73+
74+
union fuel_gauge_prop_val out;
75+
76+
ret = fuel_gauge_get_prop(fixture->dev, FUEL_GAUGE_LOW_VOLTAGE_ALARM, &out);
77+
zassert_equal(ret, 0, "get low voltage threshold failed");
78+
79+
/* Allow for register quantization: one LSB ≈ 1.91 mV */
80+
const int32_t lsb_uv = 62600000 / 32768; /* integer ≈ 1910 */
81+
int32_t diff = (int32_t)out.low_voltage_alarm - (int32_t)in.low_voltage_alarm;
82+
83+
zassert_true(diff <= lsb_uv && diff >= -lsb_uv,
84+
"Set/get mismatch: in=%d, out=%d, diff=%d > LSB=%d", (int)in.low_voltage_alarm,
85+
(int)out.low_voltage_alarm, (int)(diff), (int)lsb_uv);
86+
87+
LOG_INF("in=%d, out=%d, diff=%d > LSB=%d", (int)in.low_voltage_alarm,
88+
(int)out.low_voltage_alarm, (int)(diff), (int)lsb_uv);
89+
}
90+
91+
ZTEST_F(ltc2959, test_current_threshold_roundtrip)
92+
{
93+
int ret;
94+
union fuel_gauge_prop_val in, out;
95+
int32_t tol = CURRENT_LSB_UA ? (int32_t)CURRENT_LSB_UA : 100;
96+
97+
in.high_current_alarm = 123456; /* µA */
98+
ret = fuel_gauge_set_prop(fixture->dev, FUEL_GAUGE_HIGH_CURRENT_ALARM, in);
99+
zassert_equal(ret, 0, "set current high threshold failed (%d)", ret);
100+
101+
ret = fuel_gauge_get_prop(fixture->dev, FUEL_GAUGE_HIGH_CURRENT_ALARM, &out);
102+
zassert_equal(ret, 0, "get current high threshold failed (%d)", ret);
103+
104+
int32_t diff = out.high_current_alarm - in.high_current_alarm;
105+
106+
if (diff < 0) {
107+
diff = -diff;
108+
}
109+
110+
zassert_true(diff <= tol, "current high threshold mismatch: in=%d out=%d diff=%d tol=%d",
111+
(int)in.high_current_alarm, (int)out.high_current_alarm, (int)diff, (int)tol);
112+
113+
in.low_current_alarm = -78901; /* µA */
114+
ret = fuel_gauge_set_prop(fixture->dev, FUEL_GAUGE_LOW_CURRENT_ALARM, in);
115+
zassert_equal(ret, 0, "set current low threshold failed (%d)", ret);
116+
117+
ret = fuel_gauge_get_prop(fixture->dev, FUEL_GAUGE_LOW_CURRENT_ALARM, &out);
118+
zassert_equal(ret, 0, "get current low threshold failed (%d)", ret);
119+
120+
diff = out.low_current_alarm - in.low_current_alarm;
121+
122+
if (diff < 0) {
123+
diff = -diff;
124+
}
125+
126+
zassert_true(diff <= tol, "current low threshold mismatch: in=%d out=%d diff=%d tol=%d",
127+
(int)in.low_current_alarm, (int)out.low_current_alarm, (int)diff, (int)tol);
128+
}
129+
130+
ZTEST_F(ltc2959, test_temperature_threshold_roundtrip)
131+
{
132+
int ret;
133+
union fuel_gauge_prop_val in;
134+
union fuel_gauge_prop_val out;
135+
136+
in.low_temperature_alarm = 3000;
137+
ret = fuel_gauge_set_prop(fixture->dev, FUEL_GAUGE_LOW_TEMPERATURE_ALARM, in);
138+
zassert_equal(ret, 0, "set temp low threshold failed (%d)", ret);
139+
140+
ret = fuel_gauge_get_prop(fixture->dev, FUEL_GAUGE_LOW_TEMPERATURE_ALARM, &out);
141+
zassert_equal(ret, 0, "get temp low threshold failed (%d)", ret);
142+
int32_t diff = (int32_t)out.low_temperature_alarm - (int32_t)in.low_temperature_alarm;
143+
144+
if (diff < 0) {
145+
diff = -diff;
146+
}
147+
148+
zassert_true(diff <= 1, "temp low threshold mismatch: in=%u out=%u diff=%d",
149+
in.low_temperature_alarm, out.low_temperature_alarm, (int)diff);
150+
151+
in.high_temperature_alarm = 3500;
152+
ret = fuel_gauge_set_prop(fixture->dev, FUEL_GAUGE_HIGH_TEMPERATURE_ALARM, in);
153+
zassert_equal(ret, 0, "set temp high threshold failed (%d)", ret);
154+
155+
ret = fuel_gauge_get_prop(fixture->dev, FUEL_GAUGE_HIGH_TEMPERATURE_ALARM, &out);
156+
zassert_equal(ret, 0, "get temp high threshold failed (%d)", ret);
157+
diff = (int32_t)out.high_temperature_alarm - (int32_t)in.high_temperature_alarm;
158+
159+
if (diff < 0) {
160+
diff = -diff;
161+
}
162+
163+
zassert_true(diff <= 1, "temp high threshold mismatch: in=%u out=%u diff=%d",
164+
in.high_temperature_alarm, out.high_temperature_alarm, (int)diff);
165+
}
166+
167+
ZTEST_F(ltc2959, test_adc_mode_roundtrip)
168+
{
169+
int ret;
170+
union fuel_gauge_prop_val in, out;
171+
172+
in.adc_mode = 0xC0 | 0x10; /* CONT_VIT + GPIO BIPOLAR */
173+
ret = fuel_gauge_set_prop(fixture->dev, FUEL_GAUGE_ADC_MODE, in);
174+
zassert_equal(ret, 0, "set ADC_MODE failed (%d)", ret);
175+
176+
ret = fuel_gauge_get_prop(fixture->dev, FUEL_GAUGE_ADC_MODE, &out);
177+
zassert_equal(ret, 0, "get ADC_MODE failed (%d)", ret);
178+
zassert_equal(out.adc_mode, in.adc_mode, "ADC_MODE mismatch (got 0x%02x)", out.adc_mode);
179+
}
180+
181+
ZTEST_F(ltc2959, test_remaining_capacity_roundtrip)
182+
{
183+
int ret;
184+
union fuel_gauge_prop_val in, out;
185+
186+
in.remaining_capacity = 1234567; /* µAh */
187+
ret = fuel_gauge_set_prop(fixture->dev, FUEL_GAUGE_REMAINING_CAPACITY, in);
188+
zassert_equal(ret, 0, "set ACR failed (%d)", ret);
189+
190+
ret = fuel_gauge_get_prop(fixture->dev, FUEL_GAUGE_REMAINING_CAPACITY, &out);
191+
zassert_equal(ret, 0, "get ACR failed (%d)", ret);
192+
193+
int32_t diff = (int32_t)out.remaining_capacity - (int32_t)in.remaining_capacity;
194+
195+
if (diff < 0) {
196+
diff = -diff;
197+
}
198+
199+
zassert_true(diff <= 1, "ACR mismatch: in=%d out=%d diff=%d tol=1",
200+
(int)in.remaining_capacity, (int)out.remaining_capacity, (int)diff);
201+
}
202+
203+
ZTEST_F(ltc2959, test_remaining_capacity_reserved_guard)
204+
{
205+
int ret;
206+
union fuel_gauge_prop_val in, out;
207+
208+
/* 0xFFFFFFFF counts ≈ 2,289,000,000 µAh (533 nAh/LSB) */
209+
in.remaining_capacity = 2289000000U;
210+
ret = fuel_gauge_set_prop(fixture->dev, FUEL_GAUGE_REMAINING_CAPACITY, in);
211+
zassert_equal(ret, 0, "set ACR near fullscale failed (%d)", ret);
212+
213+
ret = fuel_gauge_get_prop(fixture->dev, FUEL_GAUGE_REMAINING_CAPACITY, &out);
214+
zassert_equal(ret, 0, "get ACR near fullscale failed (%d)", ret);
215+
216+
/* We expect the driver to write 0xFFFFFFFE instead, so out <= in and close */
217+
zassert_true(out.remaining_capacity <= in.remaining_capacity,
218+
"ACR guard failed: got larger than requested");
219+
int32_t diff = (int32_t)in.remaining_capacity - (int32_t)out.remaining_capacity;
220+
221+
if (diff < 0) {
222+
diff = -diff;
223+
}
224+
225+
zassert_true(diff <= 1, "ACR guard too lossy: in=%d out=%d |diff|=%d",
226+
(int)in.remaining_capacity, (int)out.remaining_capacity, (int)diff);
227+
}
228+
229+
ZTEST_F(ltc2959, test_cc_config_sanitized)
230+
{
231+
int ret;
232+
union fuel_gauge_prop_val in, out;
233+
234+
in.cc_config = 0xFF; /* try to set everything */
235+
ret = fuel_gauge_set_prop(fixture->dev, FUEL_GAUGE_CC_CONFIG, in);
236+
zassert_equal(ret, 0, "set cc_config failed (%d)", ret);
237+
238+
ret = fuel_gauge_get_prop(fixture->dev, FUEL_GAUGE_CC_CONFIG, &out);
239+
zassert_equal(ret, 0, "get cc_config failed (%d)", ret);
240+
241+
/* Expect bits 7,6,3 kept; bit 4 forced; others cleared => 0xD8 */
242+
zassert_equal(out.cc_config, 0xD8, "cc_config not sanitized (got 0x%02X)", out.cc_config);
243+
}
244+
245+
ZTEST_USER_F(ltc2959, test_get_some_props_failed__returns_bad_status)
246+
{
247+
fuel_gauge_prop_t props[] = {
248+
/* First invalid property */
249+
FUEL_GAUGE_PROP_MAX,
250+
/* Second invalid property */
251+
FUEL_GAUGE_PROP_MAX,
252+
/* Valid property */
253+
FUEL_GAUGE_VOLTAGE,
254+
};
255+
union fuel_gauge_prop_val vals[ARRAY_SIZE(props)];
256+
257+
int ret = fuel_gauge_get_props(fixture->dev, props, vals, ARRAY_SIZE(props));
258+
259+
zassert_equal(ret, -ENOTSUP, "Getting bad property has a good status.");
260+
}
261+
262+
ZTEST_F(ltc2959, test_set_some_props_failed__returns_err)
263+
{
264+
fuel_gauge_prop_t prop_types[] = {
265+
/* First invalid property */
266+
FUEL_GAUGE_PROP_MAX,
267+
/* Second invalid property */
268+
FUEL_GAUGE_PROP_MAX,
269+
/* Valid property */
270+
FUEL_GAUGE_LOW_VOLTAGE_ALARM,
271+
};
272+
273+
union fuel_gauge_prop_val props[] = {
274+
/* First invalid property */
275+
{0},
276+
/* Second invalid property */
277+
{0},
278+
/* Valid property */
279+
{.voltage = 0},
280+
};
281+
282+
int ret = fuel_gauge_set_props(fixture->dev, prop_types, props, ARRAY_SIZE(props));
283+
284+
zassert_equal(ret, -ENOTSUP);
285+
}
286+
287+
ZTEST_SUITE(ltc2959, NULL, ltc2959_setup, NULL, NULL, NULL);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
tests:
2+
drivers.fuel_gauge.ltc2959:
3+
tags:
4+
- fuel_gauge
5+
filter: dt_compat_enabled("adi,ltc2959")
6+
platform_allow:
7+
- native_sim

0 commit comments

Comments
 (0)