-
Notifications
You must be signed in to change notification settings - Fork 7.7k
driver: fuel_gauge/sy24561: add driver #93525
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
base: main
Are you sure you want to change the base?
driver: fuel_gauge/sy24561: add driver #93525
Conversation
Hello @franckduriez, and thank you very much for your first pull request to the Zephyr project! |
7c66c3f
to
d4403da
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First of all, thanks for your contribution, we really appreciate that and overall it looks well done!
I also looked inside the datasheet and as far as I see you implemented the two most basic functions. Consider exposing more properties, e.g. temperature compensation (you mentioned that in the silergy,sy24561.yaml
), alarms/alerts, STATUS Register (which indicates if the battery will be charged/discharged), etc., which are available on this IC.
Especially the temperature compensation seems quite interesting, as according to the datasheet this increases the SOC accuracy.
Please also add an e.g. sy24561-emulation
, for example emul_lc709203f.c, emul_max17048, or emul_bq27z746.c and also a test (look here), because this helps a lot to avoid possible future breaking changes and further increase the test coverage and quality of Zephyr in general.
#include <zephyr/kernel.h> | ||
#include <zephyr/logging/log.h> | ||
#include <zephyr/sys/byteorder.h> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are using the PRIu8
macro here LOG_DBG("RSOC: %" PRIu8 "%%", val->relative_state_of_charge);
, it would be better to also include inttypes.h
explicitly, even if it seems to be included implicitly by some other header.
#include <inttypes.h> | |
drivers/fuel_gauge/sy24561/sy24561.c
Outdated
return -ENODEV; | ||
} | ||
|
||
val->voltage = (((sys_be16_to_cpu(voltage) * 2500 / 0x1000)) + 2500); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are magic numbers, I know its taken directly from the datasheet, but at least write a comment that the scale and offset (2500) and/or the whole formula are taken from the datasheet so its clear where the magic numbers and formula come from.
Furthermore, do the sys_be16_to_cpu(voltage)
inside of sy24561_read_reg)
as I suggested.
drivers/fuel_gauge/sy24561/sy24561.c
Outdated
static int sy24561_read_reg(const struct device *dev, uint16_t *output, uint8_t addr) | ||
{ | ||
const struct sy24561_config *config = dev->config; | ||
|
||
return i2c_write_read_dt(&config->i2c, &addr, sizeof(addr), output, sizeof(*output)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static int sy24561_read_reg(const struct device *dev, uint16_t *output, uint8_t addr) | |
{ | |
const struct sy24561_config *config = dev->config; | |
return i2c_write_read_dt(&config->i2c, &addr, sizeof(addr), output, sizeof(*output)); | |
} | |
static int sy24561_read_reg(const struct device *dev, uint8_t reg, uint16_t *value) | |
{ | |
uint8_t buffer[2]; | |
const struct sy24561_config *config = dev->config; | |
int ret = i2c_write_read_dt(&config->i2c, ®, sizeof(reg), buffer, sizeof(buffer)); | |
if (ret) { | |
LOG_ERR("i2c_write_read failed (reg 0x%02x): %d", reg, ret); | |
return ret; | |
} | |
*value = sys_get_be16(buf); | |
return ret; |
drivers/fuel_gauge/sy24561/sy24561.c
Outdated
return -ENODEV; | ||
} | ||
|
||
val->relative_state_of_charge = 100 * sys_be16_to_cpu(soc) / 0xffff; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, these are magic numbers, I know its taken directly from the datasheet, but at least add a comment that the formula with its values are taken from the datasheet so its clear where the magic numbers and formula come from.
Furthermore, do the sys_be16_to_cpu(soc)
inside of sy24561_read_reg()
as I suggested.
drivers/fuel_gauge/sy24561/sy24561.c
Outdated
case FUEL_GAUGE_RELATIVE_STATE_OF_CHARGE: | ||
return sy24561_get_soc(dev, val); | ||
default: | ||
LOG_ERR("Not supported"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you add a logging error be more specific, e.g.:
LOG_ERR("Not supported"); | |
LOG_ERR("Property %d not supported", prop); |
d4403da
to
b609396
Compare
Hello @DBS06, I tried to integrate your remarks, except for the emulation and test that I will make later. For the new registers added in the latest version, I will be able to test them only once I receive my first PCB prototypes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reviewed it again. First, don't be scared by the amount of requested changes, most of them are basically the same! I see that you invested a lot of time and effort, which is great!
except for the emulation and test that I will make later.
Ok
Can you give me some feedback on the fuel gauge property usage? I made some comments when things were not clear to me.
I don't see your comments, where are they? Nevertheless, I think I gave you feedback with my new review.
For the new registers added in the latest version, I will be able to test them only once I receive my first PCB prototypes.
Do you mean the ones you recently added? I wrote "consider", if you can't test them, it would have been okay to not to implement them right now and do and update later when you can. If you write a test, you can test the new added properties over them (test-driven-development). Another possibility would be to just wait until you receive your PCB, its up to you.
Kr
drivers/fuel_gauge/sy24561/sy24561.c
Outdated
|
||
sys_put_be16(value, buffer + 1); | ||
|
||
int const ret = i2c_write_read_dt(&config->i2c, ®, sizeof(reg), NULL, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
int const ret = i2c_write_read_dt(&config->i2c, ®, sizeof(reg), NULL, 0); | |
int ret = i2c_write_dt(&config->i2c, buffer, sizeof(buffer)); |
drivers/fuel_gauge/sy24561/sy24561.c
Outdated
return -ENODEV; | ||
} | ||
|
||
// Scaling formula taken from datasheet at page 5 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Scaling formula taken from datasheet at page 5 | |
/* Scaling formula taken from datasheet at page 5 */ |
And for all occurrences
drivers/fuel_gauge/sy24561/sy24561.c
Outdated
{ | ||
switch (prop) { | ||
case FUEL_GAUGE_VOLTAGE: | ||
return sy24561_get_voltage(dev, val); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better to pass the dedicated value from union fuel_gauge_prop_val *val
instead of the whole union.
Note: Voltage is expected to be in [µV]
return sy24561_get_voltage(dev, val); | |
return sy24561_get_voltage(dev, &val->voltage); |
drivers/fuel_gauge/sy24561/sy24561.c
Outdated
return 0; | ||
} | ||
|
||
static int sy24561_set_temperature(const struct device *dev, union fuel_gauge_prop_val val) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You will receive the temperature in deci Kelvin [0.1 °K] not in [°C]
see here
Futhermore, change the interface to:
static int sy24561_set_temperature(const struct device *dev, union fuel_gauge_prop_val val) | |
static int sy24561_set_temperature(const struct device *dev, uint16_t temperature) |
drivers/fuel_gauge/sy24561/sy24561.c
Outdated
int16_t const temperature = val.temperature; | ||
|
||
// This comes from datasheet at page 5 | ||
int16_t const temperature_max = 60; | ||
int16_t const temperature_min = -20; | ||
|
||
if (temperature > temperature_max) { | ||
LOG_ERR("Temperature should be <= %" PRIi16, temperature_max); | ||
return -EINVAL; | ||
} | ||
|
||
if (temperature > temperature_min) { | ||
LOG_ERR("Temperature should be >= %" PRIi16, temperature_min); | ||
return -EINVAL; | ||
} | ||
|
||
// This comes from datasheet at page 5 and 6 | ||
uint16_t const temperature_mask = 0xff; | ||
uint16_t const temperature_reg_value = 40 + temperature; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be easier to work with deci Kelvin (avoids converting from unsigned to signed and back), compensate values which are out of the temperature range of the IC, and in the end convert it to °C+40.
Add #define DECI_KELVIN_TO_CELSIUS 2732
at the top of the file, to the other constants.
int16_t const temperature = val.temperature; | |
// This comes from datasheet at page 5 | |
int16_t const temperature_max = 60; | |
int16_t const temperature_min = -20; | |
if (temperature > temperature_max) { | |
LOG_ERR("Temperature should be <= %" PRIi16, temperature_max); | |
return -EINVAL; | |
} | |
if (temperature > temperature_min) { | |
LOG_ERR("Temperature should be >= %" PRIi16, temperature_min); | |
return -EINVAL; | |
} | |
// This comes from datasheet at page 5 and 6 | |
uint16_t const temperature_mask = 0xff; | |
uint16_t const temperature_reg_value = 40 + temperature; | |
/* This is taken from datasheet at page 5 */ | |
uint16_t const temperature_min = 2532; /* in [0.1°K] == -20[°C] */ | |
uint16_t const temperature_max = 3332; /* in [0.1°K] == 60[°C] */ | |
if (temperature > temperature_max) { | |
LOG_WRN("Temperature above max temperature of 60°C forced to 60°C); | |
temperature = temperature_max; | |
} | |
if (temperature < temperature_min) { | |
LOG_WRN("Temperature below min temperature of -20°C forced to -20°C); | |
temperature = temperature_min; | |
} | |
/* convert deci-Kelvin [0.1°K] to Celsius + 40°C offset; constants are taken from datasheet at page 5 and 6 */ | |
uint16_t const temperature_reg_value = ((temperature + 400 - DECI_KELVIN_TO_CELSIUS) / 10); | |
uint16_t const temperature_mask = 0xff; |
drivers/fuel_gauge/sy24561/sy24561.c
Outdated
{ | ||
uint16_t voltage = 0; | ||
|
||
if (sy24561_read_reg(dev, SY24561_REG_VBAT, &voltage) != 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (sy24561_read_reg(dev, SY24561_REG_VBAT, &voltage) != 0) { | |
if (sy24561_read_reg(dev, SY24561_REG_VBAT, &tmp_voltage) != 0) { |
drivers/fuel_gauge/sy24561/sy24561.c
Outdated
} | ||
|
||
// Scaling formula taken from datasheet at page 5 | ||
val->voltage = (((voltage * 2500 / 0x1000)) + 2500); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
drivers/fuel_gauge/sy24561/sy24561.c
Outdated
// This comes from datasheet at page 6 | ||
uint16_t charging = IS_BIT_SET(status, 8); | ||
|
||
val->current_direction = charging; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change according to interface change
val->current_direction = charging; | |
*current_direction = charging; |
drivers/fuel_gauge/sy24561/sy24561.c
Outdated
return 0; | ||
} | ||
|
||
static int sy24561_get_status(const struct device *dev, union fuel_gauge_prop_val *val) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better to pass the dedicated value from union fuel_gauge_prop_val *val
instead of the whole union.
static int sy24561_get_status(const struct device *dev, union fuel_gauge_prop_val *val) | |
static int sy24561_get_status(const struct device *dev, uint16_t *fg_status) |
drivers/fuel_gauge/sy24561/sy24561.c
Outdated
// This comes from datasheet at page 6 | ||
uint16_t alarm = IS_BIT_SET(config, 5); | ||
|
||
val->fg_status = alarm; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change according to interface change
val->fg_status = alarm; | |
*fg_status = alarm; |
drivers/fuel_gauge/sy24561/sy24561.c
Outdated
|
||
LOG_MODULE_REGISTER(SY24561, CONFIG_FUEL_GAUGE_LOG_LEVEL); | ||
|
||
enum { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enum { | |
enum sy24561_regs { |
drivers/fuel_gauge/sy24561/sy24561.c
Outdated
static int sy24561_get_buffer_prop(const struct device *dev, fuel_gauge_prop_t prop, void *dst, | ||
size_t dst_len) | ||
{ | ||
int rc = 0; | ||
|
||
switch (prop) { | ||
case FUEL_GAUGE_MANUFACTURER_NAME: | ||
if (dst_len == sizeof(struct sbs_gauge_manufacturer_name)) { | ||
struct sbs_gauge_manufacturer_name *const output = dst; | ||
char const manufacturer[] = "Sylergy"; | ||
output->manufacturer_name_length = sizeof(manufacturer) - 1; | ||
memcpy(output->manufacturer_name, manufacturer, | ||
output->manufacturer_name_length); | ||
} else { | ||
rc = -EINVAL; | ||
} | ||
break; | ||
case FUEL_GAUGE_DEVICE_NAME: | ||
if (dst_len == sizeof(struct sbs_gauge_device_name)) { | ||
struct sbs_gauge_device_name *const output = dst; | ||
char const name[] = "SY24561"; | ||
output->device_name_length = sizeof(name) - 1; | ||
memcpy(output->device_name, name, output->device_name_length); | ||
} else { | ||
rc = -EINVAL; | ||
} | ||
break; | ||
case FUEL_GAUGE_DEVICE_CHEMISTRY: | ||
if (dst_len == sizeof(struct sbs_gauge_device_chemistry)) { | ||
struct sbs_gauge_device_chemistry *const output = dst; | ||
char const chem[] = "LION"; | ||
output->device_chemistry_length = sizeof(chem) - 1; | ||
memcpy(output->device_chemistry, chem, output->device_chemistry_length); | ||
} else { | ||
rc = -EINVAL; | ||
} | ||
break; | ||
default: | ||
LOG_ERR("Property %d not supported", (int)prop); | ||
rc = -ENOTSUP; | ||
} | ||
|
||
return rc; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it is not a property which can be read from the chip, don't do it! Therefore, the whole function can be removed.
static int sy24561_get_buffer_prop(const struct device *dev, fuel_gauge_prop_t prop, void *dst, | |
size_t dst_len) | |
{ | |
int rc = 0; | |
switch (prop) { | |
case FUEL_GAUGE_MANUFACTURER_NAME: | |
if (dst_len == sizeof(struct sbs_gauge_manufacturer_name)) { | |
struct sbs_gauge_manufacturer_name *const output = dst; | |
char const manufacturer[] = "Sylergy"; | |
output->manufacturer_name_length = sizeof(manufacturer) - 1; | |
memcpy(output->manufacturer_name, manufacturer, | |
output->manufacturer_name_length); | |
} else { | |
rc = -EINVAL; | |
} | |
break; | |
case FUEL_GAUGE_DEVICE_NAME: | |
if (dst_len == sizeof(struct sbs_gauge_device_name)) { | |
struct sbs_gauge_device_name *const output = dst; | |
char const name[] = "SY24561"; | |
output->device_name_length = sizeof(name) - 1; | |
memcpy(output->device_name, name, output->device_name_length); | |
} else { | |
rc = -EINVAL; | |
} | |
break; | |
case FUEL_GAUGE_DEVICE_CHEMISTRY: | |
if (dst_len == sizeof(struct sbs_gauge_device_chemistry)) { | |
struct sbs_gauge_device_chemistry *const output = dst; | |
char const chem[] = "LION"; | |
output->device_chemistry_length = sizeof(chem) - 1; | |
memcpy(output->device_chemistry, chem, output->device_chemistry_length); | |
} else { | |
rc = -EINVAL; | |
} | |
break; | |
default: | |
LOG_ERR("Property %d not supported", (int)prop); | |
rc = -ENOTSUP; | |
} | |
return rc; | |
} |
b609396
to
ccb2684
Compare
Add driver for silergy sy24561 fuel gauge Signed-off-by: Franck Duriez <franck.lucien.duriez@gmail.com>
Add emulator for fuel gauge SY24561 Signed-off-by: Franck Duriez <franck.lucien.duriez@gmail.com>
Add test for fuel gauge SY24561 Signed-off-by: Franck Duriez <franck.lucien.duriez@gmail.com>
ccb2684
to
b303902
Compare
Hello @DBS06 I added the emulator and test. Let me know if changes are still required |
|
Add driver for silergy sy24561 fuel gauge