Skip to content

Commit 8ef13d0

Browse files
franckduriezcfriedt
authored andcommitted
driver: fuel_gauge/sy24561: add driver
Add driver for silergy sy24561 fuel gauge Signed-off-by: Franck Duriez <[email protected]>
1 parent 81dea96 commit 8ef13d0

File tree

7 files changed

+405
-0
lines changed

7 files changed

+405
-0
lines changed

drivers/fuel_gauge/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ add_subdirectory_ifdef(CONFIG_MAX17048 max17048)
1010
add_subdirectory_ifdef(CONFIG_BQ27Z746 bq27z746)
1111
add_subdirectory_ifdef(CONFIG_FUEL_GAUGE_AXP2101 axp2101)
1212
add_subdirectory_ifdef(CONFIG_LC709203F lc709203f)
13+
add_subdirectory_ifdef(CONFIG_SY24561 sy24561)
1314

1415
zephyr_library_sources_ifdef(CONFIG_USERSPACE fuel_gauge_syscall_handlers.c)
1516

drivers/fuel_gauge/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ source "drivers/fuel_gauge/bq27z746/Kconfig"
2525
source "drivers/fuel_gauge/composite/Kconfig"
2626
source "drivers/fuel_gauge/axp2101/Kconfig"
2727
source "drivers/fuel_gauge/lc709203f/Kconfig"
28+
source "drivers/fuel_gauge/sy24561/Kconfig"
2829

2930
endif # FUEL_GAUGE
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
zephyr_library_sources(sy24561.c)

drivers/fuel_gauge/sy24561/Kconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Copyright (c) 2025 Exceenis SAS
2+
# SPDX-License-Identifier: Apache-2.0
3+
#
4+
# Zephyr sy24561 fuel-gauge device
5+
6+
config SY24561
7+
bool "SILERGY SY24561 fuel gauge"
8+
default y
9+
depends on DT_HAS_SILERGY_SY24561_ENABLED
10+
select I2C
11+
help
12+
Enable driver for the silergy sy24561 fuel gauge device.

drivers/fuel_gauge/sy24561/sy24561.c

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
/*
2+
* Copyright (c) 2025 Romain Paupe <[email protected]>, Franck Duriez <[email protected]>
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*
6+
* Zephyr driver for SY24561 Battery Monitor
7+
*
8+
* Datasheet:
9+
* https://www.silergy.com/download/downloadFile?id=4987&type=product&ftype=note
10+
*/
11+
#include <inttypes.h>
12+
#include <stdint.h>
13+
14+
#include <zephyr/drivers/fuel_gauge.h>
15+
#include <zephyr/drivers/i2c.h>
16+
#include <zephyr/kernel.h>
17+
#include <zephyr/logging/log.h>
18+
#include <zephyr/sys/byteorder.h>
19+
20+
#include "sy24561.h"
21+
22+
#define DECI_KELVIN_TO_CELSIUS(temp_dk) (((temp_dk) - 2731) / 10)
23+
#define CELSIUS_TO_DECI_KELVIN(temp_c) ((temp_c) * 10 + 2731)
24+
25+
#define PRIdK(_tp) PRI##_tp ".%" PRI##_tp "K"
26+
#define dKARGS(_val) (_val / 10), (_val % 10)
27+
28+
#define DT_DRV_COMPAT silergy_sy24561
29+
30+
LOG_MODULE_REGISTER(SY24561, CONFIG_FUEL_GAUGE_LOG_LEVEL);
31+
32+
struct sy24561_config {
33+
struct i2c_dt_spec i2c;
34+
};
35+
36+
static int sy24561_read_reg(const struct device *dev, uint8_t reg, uint16_t *value)
37+
{
38+
const struct sy24561_config *config = dev->config;
39+
uint8_t buffer[2];
40+
int const ret = i2c_write_read_dt(&config->i2c, &reg, sizeof(reg), buffer, sizeof(buffer));
41+
42+
if (ret != 0) {
43+
LOG_ERR("i2c_write_read failed (reg 0x%02x): %d", reg, ret);
44+
return ret;
45+
}
46+
47+
*value = sys_get_be16(buffer);
48+
LOG_DBG("reg[%02x]: %02x %02x => %04x", reg, buffer[0], buffer[1], *value);
49+
return ret;
50+
}
51+
52+
static int sy24561_write_reg(const struct device *dev, uint8_t reg, uint16_t value)
53+
{
54+
const struct sy24561_config *config = dev->config;
55+
uint8_t buffer[3] = {reg, 0, 0};
56+
57+
sys_put_be16(value, buffer + 1);
58+
59+
int const ret = i2c_write_dt(&config->i2c, buffer, sizeof(buffer));
60+
61+
if (ret != 0) {
62+
LOG_ERR("i2c_write_read failed (reg 0x%02x): %d", reg, ret);
63+
return ret;
64+
}
65+
66+
return ret;
67+
}
68+
69+
static int sy24561_get_voltage(const struct device *dev, int *voltage_uV)
70+
{
71+
uint16_t voltage_reg = 0;
72+
int const ret = sy24561_read_reg(dev, SY24561_REG_VBAT, &voltage_reg);
73+
74+
if (ret != 0) {
75+
return ret;
76+
}
77+
78+
/* Scaling formula taken from datasheet at page 5 */
79+
*voltage_uV = (((voltage_reg * 2500 / 0x1000)) + 2500) * 1000;
80+
81+
LOG_DBG("voltage: %dmV", *voltage_uV);
82+
return 0;
83+
}
84+
85+
static int sy24561_get_soc(const struct device *dev, uint8_t *soc_percent)
86+
{
87+
uint16_t soc_reg = 0;
88+
int const ret = sy24561_read_reg(dev, SY24561_REG_SOC, &soc_reg);
89+
90+
if (ret != 0) {
91+
return ret;
92+
}
93+
94+
/* Scaling formula taken from datasheet at page 5 */
95+
*soc_percent = 100 * soc_reg / 0xffff;
96+
97+
LOG_DBG("RSOC: %" PRIu8 "%%", *soc_percent);
98+
return 0;
99+
}
100+
101+
static int sy24561_get_current_direction(const struct device *dev, uint16_t *current_direction)
102+
{
103+
uint16_t status;
104+
int const ret = sy24561_read_reg(dev, SY24561_REG_STATUS, &status);
105+
106+
if (ret != 0) {
107+
return ret;
108+
}
109+
110+
/* This comes from datasheet at page 6 */
111+
*current_direction = IS_BIT_SET(status, 0);
112+
113+
return 0;
114+
}
115+
116+
static int sy24561_get_version(const struct device *dev, uint16_t *version)
117+
{
118+
int const ret = sy24561_read_reg(dev, SY24561_REG_VERSION, version);
119+
120+
if (ret != 0) {
121+
return ret;
122+
}
123+
124+
return 0;
125+
}
126+
127+
static int sy24561_get_config(const struct device *dev, uint16_t *config)
128+
{
129+
int const ret = sy24561_read_reg(dev, SY24561_REG_CONFIG, config);
130+
131+
if (ret != 0) {
132+
return ret;
133+
}
134+
135+
return 0;
136+
}
137+
138+
static int sy24561_get_status(const struct device *dev, uint16_t *fg_status)
139+
{
140+
uint16_t config;
141+
int const ret = sy24561_get_config(dev, &config);
142+
143+
if (ret != 0) {
144+
LOG_ERR("Failed to read config: %d", ret);
145+
return ret;
146+
}
147+
148+
/* This comes from datasheet at page 6 */
149+
uint16_t const alarm = IS_BIT_SET(config, 5);
150+
151+
*fg_status = alarm;
152+
153+
return ret;
154+
}
155+
156+
/** This function is to reset the alarm bit */
157+
static int sy24561_set_status(const struct device *dev, uint16_t const status)
158+
{
159+
LOG_DBG("Setting status to %" PRIu16, status);
160+
161+
if (status != 0) {
162+
LOG_ERR("Invalid status %" PRIu16 ", it should be 0", status);
163+
return -EINVAL;
164+
}
165+
166+
uint16_t config;
167+
int const ret = sy24561_get_config(dev, &config);
168+
169+
if (ret != 0) {
170+
LOG_ERR("Failed to read config: %d", ret);
171+
return ret;
172+
}
173+
174+
LOG_DBG("config register: 0x%x", config);
175+
176+
/* This comes from datasheet at page 6 */
177+
uint16_t const status_mask = 1 << 5;
178+
179+
config &= ~status_mask;
180+
LOG_DBG("new config register: 0x%x", config);
181+
182+
if (sy24561_write_reg(dev, SY24561_REG_CONFIG, config) != 0) {
183+
LOG_ERR("Impossible to write config register");
184+
return -ENODEV;
185+
}
186+
187+
return 0;
188+
}
189+
190+
static int sy24561_set_alarm_threshold(const struct device *dev, uint16_t percent_threshold)
191+
{
192+
LOG_DBG("Setting SOC alarm threshold to %" PRIu16, percent_threshold);
193+
194+
uint16_t config;
195+
int const ret = sy24561_get_config(dev, &config);
196+
197+
if (ret != 0) {
198+
LOG_ERR("Failed to read config: %d", ret);
199+
return ret;
200+
}
201+
202+
LOG_DBG("config register: 0x%x", config);
203+
204+
/* This comes from datasheet at page 6 */
205+
uint16_t const percent_threshold_max = 32;
206+
uint16_t const percent_threshold_min = 1;
207+
208+
if (percent_threshold > percent_threshold_max) {
209+
LOG_WRN("SOC alarm threshold %" PRIu16 " should be <= %" PRIu16, percent_threshold,
210+
percent_threshold_max);
211+
percent_threshold = percent_threshold_max;
212+
}
213+
214+
if (percent_threshold < percent_threshold_min) {
215+
LOG_WRN("SOC alarm threshold %" PRIu16 " should be >= %" PRIu16, percent_threshold,
216+
percent_threshold_min);
217+
percent_threshold = percent_threshold_min;
218+
}
219+
220+
/* This comes from datasheet at page 6 */
221+
uint16_t const alarm_threshold_mask = 0b11111;
222+
uint16_t const alarm_threshold = 32 - percent_threshold;
223+
224+
config &= ~alarm_threshold_mask;
225+
config |= alarm_threshold;
226+
LOG_DBG("new config register: 0x%x", config);
227+
228+
if (sy24561_write_reg(dev, SY24561_REG_CONFIG, config) != 0) {
229+
LOG_ERR("Impossible to write config register");
230+
return -ENODEV;
231+
}
232+
233+
return 0;
234+
}
235+
236+
static int sy24561_set_temperature(const struct device *dev, uint16_t temperature_dK /* 0.1K */)
237+
{
238+
LOG_DBG("Setting temperature to %" PRIdK(u16), dKARGS(temperature_dK));
239+
240+
uint16_t config;
241+
int const ret = sy24561_get_config(dev, &config);
242+
243+
if (ret != 0) {
244+
LOG_ERR("Failed to read config: %d", ret);
245+
return ret;
246+
}
247+
248+
/* This comes from datasheet at page 5 */
249+
uint16_t const temperature_dK_max = CELSIUS_TO_DECI_KELVIN(60);
250+
uint16_t const temperature_dK_min = CELSIUS_TO_DECI_KELVIN(-20);
251+
252+
if (temperature_dK > temperature_dK_max) {
253+
LOG_WRN("Temperature %" PRIdK(u16) " should be <= %" PRIdK(u16),
254+
dKARGS(temperature_dK), dKARGS(temperature_dK_max));
255+
temperature_dK = temperature_dK_max;
256+
}
257+
258+
if (temperature_dK < temperature_dK_min) {
259+
LOG_WRN("Temperature %" PRIdK(u16) " should be >= %" PRIdK(u16),
260+
dKARGS(temperature_dK), dKARGS(temperature_dK_min));
261+
temperature_dK = temperature_dK_min;
262+
}
263+
264+
/* This comes from datasheet at page 5 and 6 */
265+
uint16_t const temperature_mask = 0xff << 8;
266+
uint16_t const temperature_reg_value = (40 + DECI_KELVIN_TO_CELSIUS(temperature_dK)) << 8;
267+
268+
config &= ~temperature_mask;
269+
config |= temperature_reg_value;
270+
LOG_DBG("new config register: 0x%x", config);
271+
272+
if (sy24561_write_reg(dev, SY24561_REG_CONFIG, config) != 0) {
273+
LOG_ERR("Impossible to write config register");
274+
return -ENODEV;
275+
}
276+
277+
return 0;
278+
}
279+
280+
int sy24561_get_prop(const struct device *dev, fuel_gauge_prop_t prop,
281+
union fuel_gauge_prop_val *val)
282+
{
283+
switch (prop) {
284+
case FUEL_GAUGE_VOLTAGE:
285+
return sy24561_get_voltage(dev, &val->voltage);
286+
case FUEL_GAUGE_RELATIVE_STATE_OF_CHARGE:
287+
return sy24561_get_soc(dev, &val->relative_state_of_charge);
288+
case FUEL_GAUGE_STATUS:
289+
return sy24561_get_status(dev, &val->fg_status);
290+
case FUEL_GAUGE_CURRENT_DIRECTION:
291+
return sy24561_get_current_direction(dev, &val->current_direction);
292+
default:
293+
LOG_ERR("Property %d not supported", (int)prop);
294+
return -ENOTSUP;
295+
}
296+
return 0;
297+
}
298+
299+
static int sy24561_set_prop(const struct device *dev, fuel_gauge_prop_t prop,
300+
union fuel_gauge_prop_val val)
301+
{
302+
int ret = 0;
303+
304+
switch (prop) {
305+
case FUEL_GAUGE_STATE_OF_CHARGE_ALARM:
306+
return sy24561_set_alarm_threshold(dev, val.state_of_charge_alarm);
307+
case FUEL_GAUGE_TEMPERATURE:
308+
return sy24561_set_temperature(dev, val.temperature);
309+
case FUEL_GAUGE_STATUS:
310+
return sy24561_set_status(dev, val.fg_status);
311+
default:
312+
LOG_ERR("Property %d not supported", (int)prop);
313+
ret = -ENOTSUP;
314+
}
315+
316+
return ret;
317+
}
318+
319+
int sy24561_init(const struct device *dev)
320+
{
321+
const struct sy24561_config *cfg = dev->config;
322+
323+
if (!device_is_ready(cfg->i2c.bus)) {
324+
LOG_ERR("Bus device is not ready");
325+
return -ENODEV;
326+
}
327+
328+
if (Z_LOG_CONST_LEVEL_CHECK(LOG_LEVEL_DBG)) {
329+
uint16_t version;
330+
331+
sy24561_get_version(dev, &version);
332+
LOG_DBG("SY24561 version: 0x%x", version);
333+
}
334+
335+
return 0;
336+
}
337+
338+
static DEVICE_API(fuel_gauge, sy24561_driver_api) = {
339+
.set_property = &sy24561_set_prop,
340+
.get_property = &sy24561_get_prop,
341+
};
342+
343+
#define SY24561_INIT(n) \
344+
static const struct sy24561_config sy24561_config_##n = { \
345+
.i2c = I2C_DT_SPEC_INST_GET(n), \
346+
}; \
347+
\
348+
DEVICE_DT_INST_DEFINE(n, &sy24561_init, NULL, NULL, &sy24561_config_##n, POST_KERNEL, \
349+
CONFIG_FUEL_GAUGE_INIT_PRIORITY, &sy24561_driver_api);
350+
351+
DT_INST_FOREACH_STATUS_OKAY(SY24561_INIT)

0 commit comments

Comments
 (0)