diff --git a/drivers/sensor/CMakeLists.txt b/drivers/sensor/CMakeLists.txt index 5cc4fcef150..8e0ce04ea5a 100644 --- a/drivers/sensor/CMakeLists.txt +++ b/drivers/sensor/CMakeLists.txt @@ -47,6 +47,7 @@ add_subdirectory_ifdef(CONFIG_LSM6DSL lsm6dsl) add_subdirectory_ifdef(CONFIG_LSM6DSO lsm6dso) add_subdirectory_ifdef(CONFIG_LSM9DS0_GYRO lsm9ds0_gyro) add_subdirectory_ifdef(CONFIG_LSM9DS0_MFD lsm9ds0_mfd) +add_subdirectory_ifdef(CONFIG_MAX17055 max17055) add_subdirectory_ifdef(CONFIG_MAX30101 max30101) add_subdirectory_ifdef(CONFIG_MAX44009 max44009) add_subdirectory_ifdef(CONFIG_MCP9808 mcp9808) diff --git a/drivers/sensor/Kconfig b/drivers/sensor/Kconfig index 7cdfd4b9739..fd7ac372b1f 100644 --- a/drivers/sensor/Kconfig +++ b/drivers/sensor/Kconfig @@ -123,6 +123,8 @@ source "drivers/sensor/lsm9ds0_gyro/Kconfig" source "drivers/sensor/lsm9ds0_mfd/Kconfig" +source "drivers/sensor/max17055/Kconfig" + source "drivers/sensor/max30101/Kconfig" source "drivers/sensor/max44009/Kconfig" diff --git a/drivers/sensor/max17055/CMakeLists.txt b/drivers/sensor/max17055/CMakeLists.txt new file mode 100644 index 00000000000..5cdcdb3247f --- /dev/null +++ b/drivers/sensor/max17055/CMakeLists.txt @@ -0,0 +1,7 @@ +# Copyright 2020 Google LLC +# +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources_ifdef(CONFIG_MAX17055 max17055.c) diff --git a/drivers/sensor/max17055/Kconfig b/drivers/sensor/max17055/Kconfig new file mode 100644 index 00000000000..c21ef181934 --- /dev/null +++ b/drivers/sensor/max17055/Kconfig @@ -0,0 +1,12 @@ +# Copyright 2020 Google LLC +# +# SPDX-License-Identifier: Apache-2.0 + +config MAX17055 + bool "MAX17055 Fuel Gauge" + depends on I2C && HAS_DTS_I2C + help + Enable I2C-based driver for MAX17055 Fuel Gauge. This driver supports + reading various sensor settings including charge level percentage, + time to full/empty, design voltage, temperature and remaining + capacity in mA. diff --git a/drivers/sensor/max17055/max17055.c b/drivers/sensor/max17055/max17055.c new file mode 100644 index 00000000000..a989a55a14f --- /dev/null +++ b/drivers/sensor/max17055/max17055.c @@ -0,0 +1,237 @@ +/* + * Copyright 2020 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +LOG_MODULE_REGISTER(max17055, CONFIG_SENSOR_LOG_LEVEL); + +#include "max17055.h" + +#define DT_DRV_COMPAT maxim_max17055 + +/** + * @brief Read a register value + * + * Registers have an address and a 16-bit value + * + * @param priv Private data for the driver + * @param reg_addr Register address to read + * @param val Place to put the value on success + * @return 0 if successful, or negative error code from I2C API + */ +static int max17055_reg_read(struct max17055_data *priv, int reg_addr, + int16_t *valp) +{ + uint8_t i2c_data[2]; + int rc; + + rc = i2c_burst_read(priv->i2c, DT_INST_REG_ADDR(0), reg_addr, + i2c_data, 2); + if (rc < 0) { + LOG_ERR("Unable to read register"); + return rc; + } + *valp = (i2c_data[1] << 8) | i2c_data[0]; + + return 0; +} + +/** + * @brief Convert capacity in MAX17055 units to milliamps + * + * @param rsense_mohms Value of Rsense in milliohms + * @param val Value to convert (taken from a MAX17055 register) + * @return corresponding value in milliamps + */ +static int capacity_to_ma(unsigned int rsense_mohms, int16_t val) +{ + int lsb_units, rem; + + /* Get units for the LSB in uA */ + lsb_units = 5 * 1000 / rsense_mohms; + /* Get remaining capacity in uA */ + rem = val * lsb_units; + + return rem; +} + +static void set_millis(struct sensor_value *val, int val_millis) +{ + val->val1 = val_millis / 1000; + val->val2 = (val_millis % 1000) * 1000; +} + +/** + * @brief sensor value get + * + * @param dev MAX17055 device to access + * @param chan Channel number to read + * @param valp Returns the sensor value read on success + * @return 0 if successful + * @return -ENOTSUP for unsupported channels + */ +static int max17055_channel_get(struct device *dev, enum sensor_channel chan, + struct sensor_value *valp) +{ + const struct max17055_config *const config = dev->config_info; + struct max17055_data *const priv = dev->driver_data; + unsigned int tmp; + + switch (chan) { + case SENSOR_CHAN_GAUGE_VOLTAGE: + /* Get voltage in uV */ + tmp = priv->voltage * 1250 / 16; + valp->val1 = tmp / 1000000; + valp->val2 = tmp % 1000000; + break; + case SENSOR_CHAN_GAUGE_AVG_CURRENT: { + int cap_ma; + + cap_ma = capacity_to_ma(config->rsense_mohms, + priv->avg_current); + set_millis(valp, cap_ma); + break; + } + case SENSOR_CHAN_GAUGE_STATE_OF_CHARGE: + valp->val1 = priv->state_of_charge / 256; + valp->val2 = priv->state_of_charge % 256 * 1000000 / 256; + break; + case SENSOR_CHAN_GAUGE_TEMP: + valp->val1 = priv->internal_temp / 256; + valp->val2 = priv->internal_temp % 256 * 1000000 / 256; + break; + case SENSOR_CHAN_GAUGE_FULL_CHARGE_CAPACITY: + tmp = capacity_to_ma(config->rsense_mohms, priv->full_cap); + set_millis(valp, tmp); + break; + case SENSOR_CHAN_GAUGE_REMAINING_CHARGE_CAPACITY: + tmp = capacity_to_ma(config->rsense_mohms, priv->remaining_cap); + set_millis(valp, tmp); + break; + case SENSOR_CHAN_GAUGE_TIME_TO_EMPTY: + /* Get time in ms */ + if (priv->time_to_empty == 0xffff) { + valp->val1 = 0; + valp->val2 = 0; + } else { + tmp = priv->time_to_empty * 5625; + set_millis(valp, tmp); + } + break; + case SENSOR_CHAN_GAUGE_TIME_TO_FULL: + if (priv->time_to_full == 0xffff) { + valp->val1 = 0; + valp->val2 = 0; + } else { + /* Get time in ms */ + tmp = priv->time_to_full * 5625; + set_millis(valp, tmp); + } + break; + case SENSOR_CHAN_GAUGE_CYCLE_COUNT: + valp->val1 = priv->cycle_count / 100; + valp->val2 = priv->cycle_count % 100 * 10000; + break; + case SENSOR_CHAN_GAUGE_NOM_AVAIL_CAPACITY: + tmp = capacity_to_ma(config->rsense_mohms, priv->design_cap); + set_millis(valp, tmp); + break; + case SENSOR_CHAN_GAUGE_DESIGN_VOLTAGE: + set_millis(valp, config->design_voltage); + break; + case SENSOR_CHAN_GAUGE_DESIRED_VOLTAGE: + set_millis(valp, config->desired_voltage); + break; + case SENSOR_CHAN_GAUGE_DESIRED_CHARGING_CURRENT: + valp->val1 = config->desired_charging_current; + valp->val2 = 0; + break; + default: + return -ENOTSUP; + } + + return 0; +} + +static int max17055_sample_fetch(struct device *dev, enum sensor_channel chan) +{ + struct max17055_data *priv = dev->driver_data; + struct { + int reg_addr; + int16_t *dest; + } regs[] = { + { VCELL, &priv->voltage }, + { AVG_CURRENT, &priv->avg_current }, + { REP_SOC, &priv->state_of_charge }, + { INT_TEMP, &priv->internal_temp }, + { REP_CAP, &priv->remaining_cap }, + { FULL_CAP_REP, &priv->full_cap }, + { TTE, &priv->time_to_empty }, + { TTF, &priv->time_to_full }, + { CYCLES, &priv->cycle_count }, + { DESIGN_CAP, &priv->design_cap }, + }; + + __ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL); + for (size_t i = 0; i < ARRAY_SIZE(regs); i++) { + int rc; + + rc = max17055_reg_read(priv, regs[i].reg_addr, regs[i].dest); + if (rc != 0) { + LOG_ERR("Failed to read channel %d", chan); + return rc; + } + } + + return 0; +} + +/** + * @brief initialise the fuel gauge + * + * @return 0 for success + * @return -EINVAL if the I2C controller could not be found + */ +static int max17055_gauge_init(struct device *dev) +{ + struct max17055_data *priv = dev->driver_data; + const struct max17055_config *const config = dev->config_info; + + priv->i2c = device_get_binding(config->bus_name); + if (!priv->i2c) { + LOG_ERR("Could not get pointer to %s device", config->bus_name); + return -EINVAL; + } + + return 0; +} + +static const struct sensor_driver_api max17055_battery_driver_api = { + .sample_fetch = max17055_sample_fetch, + .channel_get = max17055_channel_get, +}; + +#define MAX17055_INIT(index) \ + static struct max17055_data max17055_driver_##index; \ + \ + static const struct max17055_config max17055_config_##index = { \ + .bus_name = DT_INST_BUS_LABEL(index), \ + .rsense_mohms = DT_INST_PROP(index, rsense_mohms), \ + .design_voltage = DT_INST_PROP(index, design_voltage), \ + .desired_voltage = DT_INST_PROP(index, desired_voltage), \ + .desired_charging_current = \ + DT_INST_PROP(index, desired_charging_current), \ + }; \ + \ + DEVICE_AND_API_INIT(max17055_##index, DT_INST_LABEL(index), \ + &max17055_gauge_init, &max17055_driver_##index, \ + &max17055_config_##index, POST_KERNEL, \ + CONFIG_SENSOR_INIT_PRIORITY, \ + &max17055_battery_driver_api) + +DT_INST_FOREACH_STATUS_OKAY(MAX17055_INIT); diff --git a/drivers/sensor/max17055/max17055.h b/drivers/sensor/max17055/max17055.h new file mode 100644 index 00000000000..b0a2e181522 --- /dev/null +++ b/drivers/sensor/max17055/max17055.h @@ -0,0 +1,60 @@ +/* + * Copyright 2020 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_SENSOR_BATTERY_MAX17055_H_ +#define ZEPHYR_DRIVERS_SENSOR_BATTERY_MAX17055_H_ + +/* Register addresses */ +enum { + REP_CAP = 0x5, + REP_SOC = 0x6, + INT_TEMP = 0x8, + VCELL = 0x9, + AVG_CURRENT = 0xb, + FULL_CAP_REP = 0x10, + TTE = 0x11, + CYCLES = 0x17, + DESIGN_CAP = 0x18, + TTF = 0x20, +}; + +struct max17055_data { + struct device *i2c; + /* Current cell voltage in units of 1.25/16mV */ + uint16_t voltage; + /* Average current in units of 1.5625uV / Rsense */ + int16_t avg_current; + /* Remaining capacity as a %age */ + uint16_t state_of_charge; + /* Internal temperature in units of 1/256 degrees C */ + int16_t internal_temp; + /* Full charge capacity in 5/Rsense uA */ + uint16_t full_cap; + /* Remaining capacity in 5/Rsense uA */ + uint16_t remaining_cap; + /* Time to empty in seconds */ + uint16_t time_to_empty; + /* Time to full in seconds */ + uint16_t time_to_full; + /* Cycle count in 1/100ths (number of charge/discharge cycles) */ + uint16_t cycle_count; + /* Design capacity in 5/Rsense uA */ + uint16_t design_cap; +}; + +struct max17055_config { + char *bus_name; + /* Value of Rsense resistor in milliohms (typicallly 5 or 10) */ + uint16_t rsense_mohms; + /* Design voltage of cell in mV */ + uint16_t design_voltage; + /* Desired voltage of cell in mV */ + uint16_t desired_voltage; + /* Desired charging current in mA */ + uint16_t desired_charging_current; +}; + +#endif diff --git a/dts/bindings/sensor/maxim,max17055.yaml b/dts/bindings/sensor/maxim,max17055.yaml new file mode 100644 index 00000000000..2296cd677ae --- /dev/null +++ b/dts/bindings/sensor/maxim,max17055.yaml @@ -0,0 +1,33 @@ +# +# Copyright 2020 Google LLC +# +# SPDX-License-Identifier: Apache-2.0 +# + +description: Maxim MAX17055 Fuel Gauge + +compatible: "maxim,max17055" + +include: i2c-device.yaml + +properties: + design-voltage: + type: int + required: true + description: Battery Design Voltage in mV (3300 to 4400) + + desired-voltage: + type: int + required: true + description: Battery Desired Voltage in mV (3300 to 4400) + + desired-charging-current: + type: int + required: true + description: Battery Design Charging Current in mA (e.g. 2000) + + rsense-mohms: + type: int + required: true + description: > + Value of Rsense resistor in milliohms (e.g. 5). It cannot be 0. diff --git a/tests/drivers/build_all/i2c.dtsi b/tests/drivers/build_all/i2c.dtsi index 8ca626a1ab6..1192fbe133e 100644 --- a/tests/drivers/build_all/i2c.dtsi +++ b/tests/drivers/build_all/i2c.dtsi @@ -543,3 +543,13 @@ test_i2c_itds: itds@18 { reg = <0x18>; int-gpios = <&test_gpio 0 0>; }; + +test_i2c_max17055: max17055@49 { + compatible = "maxim,max17055"; + label = "max17055"; + reg = <0x49>; + design-voltage = <3860>; + desired-voltage = <4400>; + desired-charging-current = <2000>; + rsense-mohms = <5>; +}; diff --git a/tests/drivers/build_all/sensors_i_z.conf b/tests/drivers/build_all/sensors_i_z.conf index ef22932ab84..259df3c176d 100644 --- a/tests/drivers/build_all/sensors_i_z.conf +++ b/tests/drivers/build_all/sensors_i_z.conf @@ -23,6 +23,7 @@ CONFIG_LSM9DS0_GYRO=y CONFIG_LSM9DS0_MFD=y CONFIG_MAX30101=y CONFIG_MAX44009=y +CONFIG_MAX17055=y CONFIG_MCP9808=y CONFIG_MPU6050=y CONFIG_MS5607=y