feat(drivers): add driver for MAX17048 fuel gauge
Add driver for MAX17048 fuel gauge for battery reporting.
This commit is contained in:
parent
28ce23d489
commit
8abc449cc2
7 changed files with 311 additions and 2 deletions
|
@ -3,3 +3,4 @@
|
||||||
|
|
||||||
add_subdirectory_ifdef(CONFIG_ZMK_BATTERY battery)
|
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)
|
||||||
|
|
|
@ -5,5 +5,6 @@ if SENSOR
|
||||||
|
|
||||||
rsource "battery/Kconfig"
|
rsource "battery/Kconfig"
|
||||||
rsource "ec11/Kconfig"
|
rsource "ec11/Kconfig"
|
||||||
|
rsource "max17048/Kconfig"
|
||||||
|
|
||||||
endif # SENSOR
|
endif # SENSOR
|
9
app/module/drivers/sensor/max17048/CMakeLists.txt
Normal file
9
app/module/drivers/sensor/max17048/CMakeLists.txt
Normal file
|
@ -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
app/module/drivers/sensor/max17048/Kconfig
Normal file
23
app/module/drivers/sensor/max17048/Kconfig
Normal file
|
@ -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
app/module/drivers/sensor/max17048/max17048.c
Normal file
222
app/module/drivers/sensor/max17048/max17048.c
Normal file
|
@ -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
app/module/drivers/sensor/max17048/max17048.h
Normal file
41
app/module/drivers/sensor/max17048/max17048.h
Normal file
|
@ -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
app/module/dts/bindings/sensor/maxim,max17048.yml
Normal file
12
app/module/dts/bindings/sensor/maxim,max17048.yml
Normal file
|
@ -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]
|
Loading…
Add table
Reference in a new issue