|
| 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); |
0 commit comments