-
Notifications
You must be signed in to change notification settings - Fork 7.7k
drivers: Add fuel gauge max17043 #93021
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?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# MAX17043 Li-Ion battery fuel gauge | ||
|
||
# Copyright (c) 2025, Mohammed Billoo <mab@mab-labs.com> | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
|
||
config MAX17043 | ||
bool "MAX17043 Li-Po fuel gauge" | ||
default y | ||
depends on DT_HAS_MAXIM_MAX17043_ENABLED | ||
select I2C | ||
help | ||
Enable driver for the MAX17043 fuel gauge device. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
/* max17043.c - Driver for max17043 battery fuel gauge */ | ||
|
||
/* | ||
* Copyright (c) 2025 Mohammed Billoo <mab@mab-labs.com> | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
#define DT_DRV_COMPAT maxim_max17043 | ||
|
||
#include "max17043.h" | ||
|
||
#include <zephyr/logging/log.h> | ||
#include <zephyr/drivers/fuel_gauge.h> | ||
#include <zephyr/kernel.h> | ||
#include <zephyr/init.h> | ||
#include <zephyr/pm/device.h> | ||
#include <zephyr/sys/byteorder.h> | ||
#include <zephyr/sys/__assert.h> | ||
|
||
LOG_MODULE_REGISTER(MAX17043); | ||
|
||
#if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 0 | ||
#warning "MAX17043 driver enabled without any devices" | ||
#endif | ||
|
||
/** | ||
* Storage for the fuel gauge basic information | ||
*/ | ||
struct max17043_data { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
/* Charge as percentage */ | ||
uint8_t charge; | ||
/* Voltage as uV */ | ||
uint32_t voltage; | ||
}; | ||
|
||
/** | ||
* I2C communication | ||
* The way we read a value is first writing the address we want to read and then | ||
* wait for 2 bytes containing the data. | ||
*/ | ||
int max17043_read_register(const struct device *dev, uint8_t registerId, uint16_t *response) | ||
{ | ||
uint8_t max17043_buffer[2]; | ||
const struct max17043_config *cfg = dev->config; | ||
int rc = i2c_write_read_dt(&cfg->i2c, ®isterId, sizeof(registerId), max17043_buffer, | ||
sizeof(max17043_buffer)); | ||
if (rc != 0) { | ||
LOG_ERR("Unable to read register, error %d", rc); | ||
return rc; | ||
} | ||
|
||
*response = sys_get_be16(max17043_buffer); | ||
return 0; | ||
} | ||
|
||
/** | ||
* Raw value from the internal ADC | ||
*/ | ||
int max17043_adc(const struct device *i2c_dev, uint16_t *response) | ||
{ | ||
return max17043_read_register(i2c_dev, REGISTER_VCELL, response); | ||
} | ||
|
||
/** | ||
* Battery voltage | ||
*/ | ||
int max17043_voltage(const struct device *i2c_dev, uint32_t *response) | ||
{ | ||
uint16_t raw_voltage; | ||
int rc = max17043_adc(i2c_dev, &raw_voltage); | ||
|
||
if (rc < 0) { | ||
return rc; | ||
} | ||
/** | ||
* Once the value is read, it has to be converted to volts. The datasheet | ||
* https://www.analog.com/media/en/technical-documentation/data-sheets/ | ||
* MAX17043-MAX17049.pdf | ||
* Page 10, Table 2. Register Summary: 78.125µV/cell | ||
* Max17043 only supports one cell so we just have to multiply the value by 78.125 to | ||
* obtain µV | ||
*/ | ||
|
||
*response = (uint32_t)raw_voltage * 78.125; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a way to not use floating point unless E.g. the following change should give the same result but without floating point calculations. - *response = (uint32_t)raw_voltage * 78.125;
+ *response = ((uint32_t)raw_voltage * 78125) / 1000; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should change this in |
||
return 0; | ||
} | ||
|
||
/** | ||
* Battery percentage still available | ||
*/ | ||
int max17043_percent(const struct device *i2c_dev, uint8_t *response) | ||
{ | ||
uint16_t data; | ||
int rc = max17043_read_register(i2c_dev, REGISTER_SOC, &data); | ||
|
||
if (rc < 0) { | ||
return rc; | ||
} | ||
/** | ||
* Once the value is read, it has to be converted to percentage. The datasheet | ||
* Page 8, Table 2. Register Summary: 1%/256 | ||
* So to obtain the total percentaje we just divide the read value by 256 | ||
*/ | ||
*response = data / 256; | ||
return 0; | ||
} | ||
|
||
static int max17043_init(const struct device *dev) | ||
{ | ||
const struct max17043_config *cfg = dev->config; | ||
uint16_t version; | ||
int rc = max17043_read_register(dev, REGISTER_VERSION, &version); | ||
|
||
if (!device_is_ready(cfg->i2c.bus)) { | ||
LOG_ERR("Bus device is not ready"); | ||
return -ENODEV; | ||
} | ||
Comment on lines
+113
to
+118
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Operations seem to be out of order. The bus should be checked before using it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should also change this in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks - should I create another PR for fixes to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mabembedded I think you can do it both ways. If you decide to do it in the same PR, split them up in 2 commits one for the |
||
|
||
if (rc < 0) { | ||
LOG_ERR("Cannot read from I2C"); | ||
return rc; | ||
} | ||
|
||
LOG_INF("MAX17043 version: %x", version); | ||
|
||
return 0; | ||
} | ||
|
||
/** | ||
* Get a single property from the fuel gauge | ||
*/ | ||
int max17043_get_single_prop(const struct device *dev, fuel_gauge_prop_t prop, | ||
union fuel_gauge_prop_val *val) | ||
{ | ||
struct max17043_data *data = dev->data; | ||
int rc = 0; | ||
|
||
switch (prop) { | ||
case FUEL_GAUGE_RELATIVE_STATE_OF_CHARGE: | ||
rc = max17043_percent(dev, &data->charge); | ||
|
||
if (rc < 0) { | ||
return rc; | ||
} | ||
|
||
val->relative_state_of_charge = data->charge; | ||
break; | ||
case FUEL_GAUGE_VOLTAGE: | ||
rc = max17043_voltage(dev, &data->voltage); | ||
if (rc < 0) { | ||
return rc; | ||
} | ||
val->voltage = data->voltage; | ||
break; | ||
Comment on lines
+140
to
+155
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
default: | ||
rc = -ENOTSUP; | ||
} | ||
|
||
return rc; | ||
} | ||
|
||
static const struct fuel_gauge_driver_api max17043_driver_api = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. -static const struct fuel_gauge_driver_api max17043_driver_api = {
+static DEVICE_API(fuel_gauge, max17043_driver_api) = { |
||
.get_property = &max17043_get_single_prop, | ||
}; | ||
|
||
#define MAX17043_DEFINE(inst) \ | ||
static struct max17043_data max17043_data_##inst; \ | ||
\ | ||
static const struct max17043_config max17043_config_##inst = { \ | ||
.i2c = I2C_DT_SPEC_INST_GET(inst)}; \ | ||
\ | ||
DEVICE_DT_INST_DEFINE(inst, &max17043_init, NULL, &max17043_data_##inst, \ | ||
&max17043_config_##inst, POST_KERNEL, \ | ||
CONFIG_FUEL_GAUGE_INIT_PRIORITY, &max17043_driver_api); | ||
|
||
DT_INST_FOREACH_STATUS_OKAY(MAX17043_DEFINE) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* | ||
* Copyright (c) 2025 Mohammed Billoo <mab@mab-labs.com> | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include <zephyr/drivers/i2c.h> | ||
|
||
#define REGISTER_VCELL 0x02 | ||
#define REGISTER_SOC 0x04 | ||
#define REGISTER_MODE 0x06 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it make sense from your perspective to implement/add this and the following registers? |
||
#define REGISTER_VERSION 0x08 | ||
#define REGISTER_HIBRT 0x0A | ||
#define REGISTER_CONFIG 0x0C | ||
#define REGISTER_COMMAND 0xFE | ||
|
||
#define RESET_COMMAND 0x5400 | ||
#define QUICKSTART_MODE 0x4000 | ||
|
||
struct max17043_config { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
struct i2c_dt_spec i2c; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Copyright (c) 2025 Mohammed Billoo <mab@mab-labs.com> | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
description: | | ||
Maxim MAX17043 Fuel Gauge with ModelGauge. See more info at: | ||
https://www.analog.com/media/en/technical-documentation/data-sheets/max17043-max17044.pdf | ||
compatible: "maxim,max17043" | ||
|
||
include: [i2c-device.yaml, fuel-gauge.yaml] |
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 seems that
zephyr/pm/device.h
andzephyr/sys/__assert.h
are not used by the driver.