Skip to content
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

feat(drivers): add driver for MAX17048 fuel gauge #1301

Merged
merged 13 commits into from
Sep 26, 2023
3 changes: 2 additions & 1 deletion app/module/drivers/sensor/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
# SPDX-License-Identifier: MIT

add_subdirectory_ifdef(CONFIG_ZMK_BATTERY battery)
add_subdirectory_ifdef(CONFIG_EC11 ec11)
add_subdirectory_ifdef(CONFIG_EC11 ec11)
add_subdirectory_ifdef(CONFIG_MAX17048 max17048)
3 changes: 2 additions & 1 deletion app/module/drivers/sensor/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ if SENSOR

rsource "battery/Kconfig"
rsource "ec11/Kconfig"
rsource "max17048/Kconfig"

endif # SENSOR
endif # SENSOR
9 changes: 9 additions & 0 deletions app/module/drivers/sensor/max17048/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2022 The ZMK Contributors
# SPDX-License-Identifier: MIT

zephyr_include_directories(.)

zephyr_library()

zephyr_library_sources_ifdef(CONFIG_MAX17048 max17048.c)
zephyr_library_sources_ifndef(CONFIG_MAX17048 ${ZEPHYR_BASE}/misc/empty_file.c)
23 changes: 23 additions & 0 deletions app/module/drivers/sensor/max17048/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright (c) 2022 The ZMK Contributors
# SPDX-License-Identifier: MIT

DT_COMPAT_MAXIM_MAX17048 := maxim,max17048

menuconfig MAX17048
bool "MAX17048/9 I2C-based Fuel Gauge"
default $(dt_compat_enabled,$(DT_COMPAT_MAXIM_MAX17048))
depends on I2C
select ZMK_BATTERY
help
Enable driver for MAX17048/9 I2C-based Fuel Gauge. Supports measuring
battery voltage and state-of-charge.

if MAX17048

config SENSOR_MAX17048_INIT_PRIORITY
int "Init priority"
default 75
help
Device driver initialization priority.

endif #MAX17048
222 changes: 222 additions & 0 deletions app/module/drivers/sensor/max17048/max17048.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#define DT_DRV_COMPAT maxim_max17048

#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/util.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/drivers/sensor.h>

#include "max17048.h"

#define LOG_LEVEL CONFIG_SENSOR_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(sensor_max17048);

static int read_register(const struct device *dev, uint8_t reg, uint16_t *value) {

if (k_is_in_isr()) {
return -EWOULDBLOCK;
}

struct max17048_config *config = (struct max17048_config *)dev->config;

uint8_t data[2] = {0};
int ret = i2c_burst_read_dt(&config->i2c_bus, reg, &data[0], sizeof(data));
if (ret != 0) {
LOG_DBG("i2c_write_read FAIL %d\n", ret);
return ret;
}

// the register values are returned in big endian (MSB first)
*value = sys_get_be16(data);
return 0;
}

static int write_register(const struct device *dev, uint8_t reg, uint16_t value) {

if (k_is_in_isr()) {
return -EWOULDBLOCK;
}

struct max17048_config *config = (struct max17048_config *)dev->config;

uint8_t data[2] = {0};
sys_put_be16(value, &data[0]);

return i2c_burst_write_dt(&config->i2c_bus, reg, &data[0], sizeof(data));
}

static int set_rcomp_value(const struct device *dev, uint8_t rcomp_value) {

struct max17048_drv_data *const drv_data = (struct max17048_drv_data *const)dev->data;
k_sem_take(&drv_data->lock, K_FOREVER);

uint16_t tmp = 0;
int err = read_register(dev, REG_CONFIG, &tmp);
if (err != 0) {
goto done;
}

tmp = ((uint16_t)rcomp_value << 8) | (tmp & 0xFF);
err = write_register(dev, REG_CONFIG, tmp);
if (err != 0) {
goto done;
}

LOG_DBG("set RCOMP to %d", rcomp_value);

done:
k_sem_give(&drv_data->lock);
return err;
}

static int set_sleep_enabled(const struct device *dev, bool sleep) {

struct max17048_drv_data *const drv_data = (struct max17048_drv_data *const)dev->data;
k_sem_take(&drv_data->lock, K_FOREVER);

uint16_t tmp = 0;
int err = read_register(dev, REG_CONFIG, &tmp);
if (err != 0) {
goto done;
}

if (sleep) {
tmp |= 0x80;
} else {
tmp &= ~0x0080;
}

err = write_register(dev, REG_CONFIG, tmp);
if (err != 0) {
goto done;
}

LOG_DBG("sleep mode %s", sleep ? "enabled" : "disabled");

done:
k_sem_give(&drv_data->lock);
return err;
}

static int max17048_sample_fetch(const struct device *dev, enum sensor_channel chan) {

struct max17048_drv_data *const drv_data = dev->data;
k_sem_take(&drv_data->lock, K_FOREVER);

int err = 0;

if (chan == SENSOR_CHAN_GAUGE_STATE_OF_CHARGE || chan == SENSOR_CHAN_ALL) {
err = read_register(dev, REG_STATE_OF_CHARGE, &drv_data->raw_state_of_charge);
if (err != 0) {
LOG_WRN("failed to read state-of-charge: %d", err);
goto done;
}
LOG_DBG("read soc: %d", drv_data->raw_state_of_charge);

} else if (chan == SENSOR_CHAN_GAUGE_VOLTAGE || chan == SENSOR_CHAN_ALL) {
err = read_register(dev, REG_VCELL, &drv_data->raw_vcell);
if (err != 0) {
LOG_WRN("failed to read vcell: %d", err);
goto done;
}
LOG_DBG("read vcell: %d", drv_data->raw_vcell);

} else {
LOG_DBG("unsupported channel %d", chan);
err = -ENOTSUP;
}

done:
k_sem_give(&drv_data->lock);
return err;
}

static int max17048_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val) {
int err = 0;

struct max17048_drv_data *const drv_data = dev->data;
k_sem_take(&drv_data->lock, K_FOREVER);

struct max17048_drv_data *const data = dev->data;
unsigned int tmp = 0;

switch (chan) {
case SENSOR_CHAN_GAUGE_VOLTAGE:
// 1250 / 16 = 78.125
tmp = data->raw_vcell * 1250 / 16;
val->val1 = tmp / 1000000;
val->val2 = tmp % 1000000;
break;

case SENSOR_CHAN_GAUGE_STATE_OF_CHARGE:
val->val1 = (data->raw_state_of_charge >> 8);
val->val2 = (data->raw_state_of_charge & 0xFF) * 1000000 / 256;
break;

default:
err = -ENOTSUP;
break;
}

k_sem_give(&drv_data->lock);
return err;
}

static int max17048_init(const struct device *dev) {
struct max17048_drv_data *drv_data = dev->data;
const struct max17048_config *config = dev->config;

if (!device_is_ready(config->i2c_bus.bus)) {
LOG_WRN("i2c bus not ready!");
return -EINVAL;
}

uint16_t ic_version = 0;
int err = read_register(dev, REG_VERSION, &ic_version);
if (err != 0) {
LOG_WRN("could not get IC version!");
return err;
}

// the functions below need the semaphore, so initialise it here
k_sem_init(&drv_data->lock, 1, 1);

// bring the device out of sleep
set_sleep_enabled(dev, false);

// set the default rcomp value -- 0x97, as stated in the datasheet
set_rcomp_value(dev, 0x97);

LOG_INF("device initialised at 0x%x (version %d)", config->i2c_bus.addr, ic_version);

return 0;
}

static const struct sensor_driver_api max17048_api_table = {.sample_fetch = max17048_sample_fetch,
.channel_get = max17048_channel_get};

#define MAX17048_INIT(inst) \
static struct max17048_config max17048_##inst##_config = {.i2c_bus = \
I2C_DT_SPEC_INST_GET(inst)}; \
\
static struct max17048_drv_data max17048_##inst##_drvdata = { \
.raw_state_of_charge = 0, \
.raw_charge_rate = 0, \
.raw_vcell = 0, \
}; \
\
/* This has to init after SPI master */ \
DEVICE_DT_INST_DEFINE(inst, max17048_init, NULL, &max17048_##inst##_drvdata, \
&max17048_##inst##_config, POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, \
&max17048_api_table);

DT_INST_FOREACH_STATUS_OKAY(MAX17048_INIT)
41 changes: 41 additions & 0 deletions app/module/drivers/sensor/max17048/max17048.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#pragma once

#include <zephyr/device.h>
#include <zephyr/sys/util.h>

#ifdef __cplusplus
extern "C" {
#endif

#define REG_VCELL 0x02
#define REG_STATE_OF_CHARGE 0x04
#define REG_MODE 0x06
#define REG_VERSION 0x08
#define REG_HIBERNATE 0x0A
#define REG_CONFIG 0x0C
#define REG_VALERT 0x14
#define REG_CHARGE_RATE 0x16
#define REG_VRESET 0x18
#define REG_STATUS 0x1A

struct max17048_config {
struct i2c_dt_spec i2c_bus;
};

struct max17048_drv_data {
struct k_sem lock;

uint16_t raw_state_of_charge;
uint16_t raw_charge_rate;
uint16_t raw_vcell;
};

#ifdef __cplusplus
}
#endif
12 changes: 12 additions & 0 deletions app/module/dts/bindings/sensor/maxim,max17048.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#
# Copyright (c) 2022 The ZMK Contributors
#
# SPDX-License-Identifier: MIT
#

description: >
This is a representation of the Maxim max17048 I2C Fuel Gauge.

compatible: "maxim,max17048"

include: [i2c-device.yaml]