From 5e4bffe06bd517639cc8961b65148be5e122a97b Mon Sep 17 00:00:00 2001 From: zhiayang Date: Sun, 15 May 2022 02:29:39 +0800 Subject: [PATCH] feat(drivers): add driver for MAX17048 fuel gauge --- app/drivers/sensor/CMakeLists.txt | 3 +- app/drivers/sensor/Kconfig | 3 +- app/drivers/sensor/max17048/CMakeLists.txt | 9 + app/drivers/sensor/max17048/Kconfig | 9 + app/drivers/sensor/max17048/max17048.c | 198 +++++++++++++++++++++ app/drivers/sensor/max17048/max17048.h | 42 +++++ 6 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 app/drivers/sensor/max17048/CMakeLists.txt create mode 100644 app/drivers/sensor/max17048/Kconfig create mode 100644 app/drivers/sensor/max17048/max17048.c create mode 100644 app/drivers/sensor/max17048/max17048.h diff --git a/app/drivers/sensor/CMakeLists.txt b/app/drivers/sensor/CMakeLists.txt index b549320f1210..9654600a756a 100644 --- a/app/drivers/sensor/CMakeLists.txt +++ b/app/drivers/sensor/CMakeLists.txt @@ -2,4 +2,5 @@ # SPDX-License-Identifier: MIT add_subdirectory_ifdef(CONFIG_ZMK_BATTERY battery) -add_subdirectory_ifdef(CONFIG_EC11 ec11) \ No newline at end of file +add_subdirectory_ifdef(CONFIG_EC11 ec11) +add_subdirectory_ifdef(CONFIG_MAX17048 max17048) diff --git a/app/drivers/sensor/Kconfig b/app/drivers/sensor/Kconfig index a828f6c63ab2..877569e2a5f0 100644 --- a/app/drivers/sensor/Kconfig +++ b/app/drivers/sensor/Kconfig @@ -2,4 +2,5 @@ # SPDX-License-Identifier: MIT rsource "battery/Kconfig" -rsource "ec11/Kconfig" \ No newline at end of file +rsource "ec11/Kconfig" +rsource "max17048/Kconfig" diff --git a/app/drivers/sensor/max17048/CMakeLists.txt b/app/drivers/sensor/max17048/CMakeLists.txt new file mode 100644 index 000000000000..e895fa11fb8c --- /dev/null +++ b/app/drivers/sensor/max17048/CMakeLists.txt @@ -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) diff --git a/app/drivers/sensor/max17048/Kconfig b/app/drivers/sensor/max17048/Kconfig new file mode 100644 index 000000000000..6d3de88b9bd0 --- /dev/null +++ b/app/drivers/sensor/max17048/Kconfig @@ -0,0 +1,9 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +config MAX17048 + bool "MAX17048 Fuel Gauge" + depends on I2C + help + Enable I2C-based driver for MAX17048/9 Fuel Gauge. Supports measuring + battery voltage and state-of-charge. diff --git a/app/drivers/sensor/max17048/max17048.c b/app/drivers/sensor/max17048/max17048.c new file mode 100644 index 000000000000..3d9147c7edd9 --- /dev/null +++ b/app/drivers/sensor/max17048/max17048.c @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT maxim_max17048 + +#include +#include +#include +#include +#include +#include +#include + +#include "max17048.h" + +LOG_MODULE_REGISTER(MAX17048, CONFIG_SENSOR_LOG_LEVEL); + +static int read_register(const struct device *dev, uint8_t reg, uint16_t *value) { + + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + struct max17048_drv_data *const drv_data = dev->data; + uint16_t dev_addr = ((struct max17048_config *)dev->config)->device_addr; + + uint16_t data = 0; + int ret = i2c_burst_read(drv_data->i2c, dev_addr, reg, (uint8_t *)&data, sizeof(data)); + if (ret != 0) { + LOG_DBG("i2c_write_read FAIL %d\n", ret); + return ret; + } + + *value = sys_le16_to_cpu(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_drv_data *const drv_data = dev->data; + + uint16_t data = sys_cpu_to_le16(value); + uint16_t dev_addr = ((struct max17048_config *)dev->config)->device_addr; + + return i2c_burst_write(drv_data->i2c, dev_addr, reg, (uint8_t *)&data, sizeof(data)); +} + +static int set_rcomp_value(const struct device *dev, uint8_t rcomp_value) { + + uint16_t tmp = 0; + int err = read_register(dev, REG_CONFIG, &tmp); + if (err != 0) { + return err; + } + + tmp = ((uint16_t)rcomp_value << 8) | (tmp & 0xFF); + err = write_register(dev, REG_CONFIG, tmp); + if (err != 0) { + return err; + } + + LOG_DBG("set RCOMP to %d", rcomp_value); + return 0; +} + +static int set_sleep_enabled(const struct device *dev, bool sleep) { + uint16_t tmp = 0; + int err = read_register(dev, REG_CONFIG, &tmp); + if (err != 0) { + return err; + } + + if(sleep) { + tmp |= 0x80; + } else { + tmp &= ~0x0080; + } + + err = write_register(dev, REG_CONFIG, tmp); + if (err != 0) { + return err; + } + + LOG_DBG("sleep mode %s", sleep ? "enabled" : "disabled"); + return 0; +} + + + +static int max17048_sample_fetch(const struct device *dev, enum sensor_channel chan) { + struct max17048_drv_data *const data = dev->data; + + if (chan != SENSOR_CHAN_GAUGE_VOLTAGE && chan != SENSOR_CHAN_GAUGE_STATE_OF_CHARGE) { + LOG_DBG("unsupported channel %d", chan); + return -ENOTSUP; + } + + int err = read_register(dev, REG_STATE_OF_CHARGE, &data->raw_state_of_charge); + if (err != 0) { + LOG_WRN("failed to read state-of-charge: %d", err); + return err; + } + + err = read_register(dev, REG_VCELL, &data->raw_vcell); + if (err != 0) { + LOG_WRN("failed to read vcell: %d", err); + return err; + } + + LOG_DBG("read values: soc=%d, vcell=%d", data->raw_state_of_charge, data->raw_vcell); + + return 0; +} + +static int max17048_channel_get(const struct device *dev, enum sensor_channel chan, + struct sensor_value *val) { + + 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: + return -ENOTSUP; + } + + return 0; +} + +static int max17048_init(const struct device *dev) { + struct max17048_drv_data *driver_data = dev->data; + const struct max17048_config *driver_config = dev->config; + + driver_data->i2c = device_get_binding((char *)driver_config->i2c_device_name); + if (!driver_data->i2c) { + LOG_DBG("Unable to get i2c device"); + return -ENODEV; + } + + if (!device_is_ready(driver_data->i2c)) { + 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; + } + + // 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 (i2c=%s) (version %d)", driver_config->device_addr, + driver_config->i2c_device_name, 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_device_name = DT_INST_BUS_LABEL(inst), \ + .device_addr = DT_INST_REG_ADDR(inst), \ + }; \ + \ + static struct max17048_drv_data max17048_##inst##_drvdata = {}; \ + \ + /* 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) diff --git a/app/drivers/sensor/max17048/max17048.h b/app/drivers/sensor/max17048/max17048.h new file mode 100644 index 000000000000..db7289940160 --- /dev/null +++ b/app/drivers/sensor/max17048/max17048.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +#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 { + const char *i2c_device_name; + uint16_t device_addr; +}; + +struct max17048_drv_data { + const struct device *i2c; + + uint16_t raw_state_of_charge; + uint16_t raw_charge_rate; + uint16_t raw_vcell; +}; + +#ifdef __cplusplus +} +#endif