Skip to content

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions drivers/fuel_gauge/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/fuel_gauge.h)
add_subdirectory_ifdef(CONFIG_SBS_GAUGE_NEW_API sbs_gauge)
add_subdirectory_ifdef(CONFIG_FUEL_GAUGE_COMPOSITE composite)
add_subdirectory_ifdef(CONFIG_MAX17048 max17048)
add_subdirectory_ifdef(CONFIG_MAX17043 max17043)
add_subdirectory_ifdef(CONFIG_BQ27Z746 bq27z746)
add_subdirectory_ifdef(CONFIG_FUEL_GAUGE_AXP2101 axp2101)
add_subdirectory_ifdef(CONFIG_LC709203F lc709203f)
Expand Down
1 change: 1 addition & 0 deletions drivers/fuel_gauge/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ config FUEL_GAUGE_INIT_PRIORITY
help
Battery fuel gauge initialization priority.

source "drivers/fuel_gauge/max17043/Kconfig"
source "drivers/fuel_gauge/max17048/Kconfig"
source "drivers/fuel_gauge/sbs_gauge/Kconfig"
source "drivers/fuel_gauge/bq27z746/Kconfig"
Expand Down
13 changes: 13 additions & 0 deletions drivers/fuel_gauge/max17043/Kconfig
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.
177 changes: 177 additions & 0 deletions drivers/fuel_gauge/max17043/max17043.c
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>
Comment on lines +17 to +19
Copy link
Contributor

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 and zephyr/sys/__assert.h are not used by the driver.


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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @msalau already mentioned here the max17043_data struct can be removed, because it is only used in max17043_get_single_prop() function as a local variable, but besides that it will not be used anywhere.

/* 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, &registerId, 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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to not use floating point unless CONFIG_FPU=y?

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;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should change this in drivers/fuel_gauge/max17048 too

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
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also change this in drivers/fuel_gauge/max17048.
I never really looked closely into the max17048 driver which @mabembedded took as a template for the max17043, but to see all this minor issues which are the same in the max17048 I am thinking about "refactoring" the max17048 driver and extend it with some missing functionality.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks - should I create another PR for fixes to the max17048 after addressing issues in this PR?

Copy link
Contributor

Choose a reason for hiding this comment

The 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 max17043 and one for the changes in max17048.


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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

data->charge and data->voltage are set but never actually used.
The function uses them as local variables.
Did I miss something?

default:
rc = -ENOTSUP;
}

return rc;
}

static const struct fuel_gauge_driver_api max17043_driver_api = {
Copy link
Contributor

Choose a reason for hiding this comment

The 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)
24 changes: 24 additions & 0 deletions drivers/fuel_gauge/max17043/max17043.h
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The max17043_config struct can be moved inside the max17043.c, because I don't see a need to have it in the header.

struct i2c_dt_spec i2c;
};
10 changes: 10 additions & 0 deletions dts/bindings/fuel-gauge/maxim,max17043.yaml
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]