| /* |
| * Copyright 2020 Google LLC |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <drivers/i2c.h> |
| #include <drivers/sensor.h> |
| |
| #include <logging/log.h> |
| 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 current 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 current_to_ma(unsigned int rsense_mohms, int16_t val) |
| { |
| return (val * 1.5625) / rsense_mohms; |
| } |
| |
| /** |
| * @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(const struct device *dev, |
| enum sensor_channel chan, |
| struct sensor_value *valp) |
| { |
| const struct max17055_config *const config = dev->config; |
| struct max17055_data *const priv = dev->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 current_ma; |
| |
| current_ma = current_to_ma(config->rsense_mohms, |
| priv->avg_current); |
| set_millis(valp, current_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(const struct device *dev, |
| enum sensor_channel chan) |
| { |
| struct max17055_data *priv = dev->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(const struct device *dev) |
| { |
| struct max17055_data *priv = dev->data; |
| const struct max17055_config *const config = dev->config; |
| |
| 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_DT_INST_DEFINE(index, &max17055_gauge_init, \ |
| device_pm_control_nop, \ |
| &max17055_driver_##index, \ |
| &max17055_config_##index, POST_KERNEL, \ |
| CONFIG_SENSOR_INIT_PRIORITY, \ |
| &max17055_battery_driver_api) |
| |
| DT_INST_FOREACH_STATUS_OKAY(MAX17055_INIT); |