From 64995e2a54a2d77dac291de29b29d39aca12202c Mon Sep 17 00:00:00 2001 From: Hayden Ball Date: Thu, 18 Mar 2021 11:00:32 +0000 Subject: [PATCH] max17055: Set battery parameters from device tree Currently the MAX17055 driver assumes that a battery matching the default characteristics is used. This change allows battery characteristics to be specified in device tree and writes them to the MAX17055 on initialization. Existing default values are maintained for backwards compatibility. Initialization routine taken from MAX17055 Software Implementation Guide, document UG6365. Signed-off-by: Hayden Ball --- drivers/sensor/max17055/max17055.c | 204 +++++++++++++++++++++--- drivers/sensor/max17055/max17055.h | 45 ++++-- dts/bindings/sensor/maxim,max17055.yaml | 15 ++ tests/drivers/build_all/i2c.dtsi | 5 +- 4 files changed, 235 insertions(+), 34 deletions(-) diff --git a/drivers/sensor/max17055/max17055.c b/drivers/sensor/max17055/max17055.c index fe73f8980c7e..f82ec941e829 100644 --- a/drivers/sensor/max17055/max17055.c +++ b/drivers/sensor/max17055/max17055.c @@ -6,6 +6,7 @@ #include #include +#include #include LOG_MODULE_REGISTER(max17055, CONFIG_SENSOR_LOG_LEVEL); @@ -30,8 +31,7 @@ static int max17055_reg_read(struct max17055_data *priv, int reg_addr, uint8_t i2c_data[2]; int rc; - rc = i2c_burst_read(priv->i2c, DT_INST_REG_ADDR(0), reg_addr, - i2c_data, 2); + 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; @@ -41,6 +41,16 @@ static int max17055_reg_read(struct max17055_data *priv, int reg_addr, return 0; } +static int max17055_reg_write(struct max17055_data *priv, int reg_addr, + int16_t val) +{ + uint8_t i2c_data[2]; + + sys_put_le16(val, i2c_data); + + return i2c_burst_write(priv->i2c, DT_INST_REG_ADDR(0), reg_addr, i2c_data, 2); +} + /** * @brief Convert current in MAX17055 units to milliamps * @@ -53,6 +63,18 @@ static int current_to_ma(unsigned int rsense_mohms, int16_t val) return (val * 1.5625) / rsense_mohms; } +/** + * @brief Convert current in milliamps to MAX17055 units + * + * @param rsense_mohms Value of Rsense in milliohms + * @param val Value in mA to convert + * @return corresponding value in MAX17055 units, ready to write to a register + */ +static int current_ma_to_max17055(unsigned int rsense_mohms, uint16_t val) +{ + return val * rsense_mohms / 1.5625; +} + /** * @brief Convert capacity in MAX17055 units to milliamps * @@ -72,6 +94,29 @@ static int capacity_to_ma(unsigned int rsense_mohms, int16_t val) return rem; } +/** + * @brief Convert capacity in milliamphours to MAX17055 units + * + * @param rsense_mohms Value of Rsense in milliohms + * @param val_mha Value in milliamphours to convert + * @return corresponding value in MAX17055 units, ready to write to a register + */ +static int capacity_to_max17055(unsigned int rsense_mohms, uint16_t val_mha) +{ + return val_mha * rsense_mohms / 5; +} + +/** + * @brief Convert voltage in millivolts to MAX17055 units + * + * @param val_mv Value in millivolts to convert + * @return corresponding value in MAX17055 units, ready to write to a register + */ +static int voltage_mV_to_max17055(uint16_t val_mv) +{ + return val_mv * 16 / 1.25; +} + static void set_millis(struct sensor_value *val, int val_millis) { val->val1 = val_millis / 1000; @@ -105,8 +150,7 @@ static int max17055_channel_get(const struct device *dev, case SENSOR_CHAN_GAUGE_AVG_CURRENT: { int current_ma; - current_ma = current_to_ma(config->rsense_mohms, - priv->avg_current); + current_ma = current_to_ma(config->rsense_mohms, priv->avg_current); set_millis(valp, current_ma); break; } @@ -175,6 +219,7 @@ 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; @@ -205,14 +250,100 @@ static int max17055_sample_fetch(const struct device *dev, return 0; } +static int max17055_exit_hibernate(struct max17055_data *priv) +{ + LOG_DBG("Exit hibernate"); + + if (max17055_reg_write(priv, SOFT_WAKEUP, SOFT_WAKEUP_WAKEUP)) { + return -EIO; + } + if (max17055_reg_write(priv, HIB_CFG, HIB_CFG_CLEAR)) { + return -EIO; + } + if (max17055_reg_write(priv, SOFT_WAKEUP, SOFT_WAKEUP_CLEAR)) { + return -EIO; + } + + return 0; +} + +static int max17055_write_config(struct max17055_data *priv, + const struct max17055_config *const config) +{ + uint16_t design_capacity = capacity_to_max17055(config->rsense_mohms, + config->design_capacity); + uint16_t d_qacc = design_capacity / 32; + uint16_t d_pacc = d_qacc * 44138 / design_capacity; + uint16_t i_chg_term = current_ma_to_max17055(config->rsense_mohms, config->i_chg_term); + uint16_t v_empty = voltage_mV_to_max17055(config->v_empty); + + LOG_DBG("Writing configuration parameters"); + LOG_DBG("DesignCap: %u, dQAcc: %u, IChgTerm: %u, VEmpty: %u, dPAcc: %u", + design_capacity, d_qacc, i_chg_term, v_empty, d_pacc); + + if (max17055_reg_write(priv, DESIGN_CAP, design_capacity)) { + return -EIO; + } + if (max17055_reg_write(priv, D_QACC, d_qacc)) { + return -EIO; + } + if (max17055_reg_write(priv, ICHG_TERM, i_chg_term)) { + return -EIO; + } + if (max17055_reg_write(priv, V_EMPTY, v_empty)) { + return -EIO; + } + if (max17055_reg_write(priv, D_PACC, d_pacc)) { + return -EIO; + } + if (max17055_reg_write(priv, MODEL_CFG, MODELCFG_REFRESH)) { + return -EIO; + } + + uint16_t model_cfg = MODELCFG_REFRESH; + + while (model_cfg & MODELCFG_REFRESH) { + max17055_reg_read(priv, MODEL_CFG, &model_cfg); + k_sleep(K_MSEC(10)); + } + + return 0; +} + +static int max17055_init_config(struct max17055_data *priv, + const struct max17055_config *const config) +{ + int16_t hib_cfg; + + if (max17055_reg_read(priv, HIB_CFG, &hib_cfg)) { + return -EIO; + } + + if (max17055_exit_hibernate(priv)) { + return -EIO; + } + + if (max17055_write_config(priv, config)) { + return -EIO; + } + + if (max17055_reg_write(priv, HIB_CFG, hib_cfg)) { + return -EIO; + } + + return 0; +} + /** * @brief initialise the fuel gauge * * @return 0 for success + * @return -EIO on I2C communication error * @return -EINVAL if the I2C controller could not be found */ static int max17055_gauge_init(const struct device *dev) { + int16_t tmp; struct max17055_data *priv = dev->data; const struct max17055_config *const config = dev->config; @@ -222,7 +353,32 @@ static int max17055_gauge_init(const struct device *dev) return -EINVAL; } - return 0; + if (max17055_reg_read(priv, STATUS, &tmp)) { + return -EIO; + } + + if (!(tmp & STATUS_POR)) { + LOG_DBG("No POR event detected - skip device configuration"); + return 0; + } + + /* Wait for FSTAT_DNR to be cleared */ + tmp = FSTAT_DNR; + while (tmp & FSTAT_DNR) { + max17055_reg_read(priv, FSTAT, &tmp); + } + + if (max17055_init_config(priv, config)) { + return -EIO; + } + + /* Clear PowerOnReset bit */ + if (max17055_reg_read(priv, STATUS, &tmp)) { + return -EIO; + } + + tmp &= ~STATUS_POR; + return max17055_reg_write(priv, STATUS, tmp); } static const struct sensor_driver_api max17055_battery_driver_api = { @@ -230,23 +386,25 @@ static const struct sensor_driver_api max17055_battery_driver_api = { .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) +#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), \ + .design_capacity = DT_INST_PROP(index, design_capacity), \ + .design_voltage = DT_INST_PROP(index, design_voltage), \ + .desired_charging_current = DT_INST_PROP(index, desired_charging_current), \ + .desired_voltage = DT_INST_PROP(index, desired_voltage), \ + .i_chg_term = DT_INST_PROP(index, i_chg_term), \ + .rsense_mohms = DT_INST_PROP(index, rsense_mohms), \ + .v_empty = DT_INST_PROP(index, v_empty), \ + }; \ + \ + 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); diff --git a/drivers/sensor/max17055/max17055.h b/drivers/sensor/max17055/max17055.h index 4f3a7a49b5ce..7d717cd72ed6 100644 --- a/drivers/sensor/max17055/max17055.h +++ b/drivers/sensor/max17055/max17055.h @@ -9,16 +9,35 @@ /* 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, + STATUS = 0x0, + REP_CAP = 0x5, + REP_SOC = 0x6, + INT_TEMP = 0x8, + VCELL = 0x9, + AVG_CURRENT = 0xb, + FULL_CAP_REP = 0x10, + TTE = 0x11, + ICHG_TERM = 0x1e, + CYCLES = 0x17, + DESIGN_CAP = 0x18, + TTF = 0x20, + V_EMPTY = 0x3a, + FSTAT = 0x3d, + D_QACC = 0x45, + D_PACC = 0x46, + SOFT_WAKEUP = 0x60, + HIB_CFG = 0xba, + MODEL_CFG = 0xdb, +}; + +/* Masks */ +enum { + FSTAT_DNR = 0x0001, + HIB_CFG_CLEAR = 0x0000, + MODELCFG_REFRESH = 0x8000, + SOFT_WAKEUP_CLEAR = 0x0000, + SOFT_WAKEUP_WAKEUP = 0x0090, + STATUS_POR = 0x0002, }; struct max17055_data { @@ -49,12 +68,18 @@ struct max17055_config { char *bus_name; /* Value of Rsense resistor in milliohms (typicallly 5 or 10) */ uint16_t rsense_mohms; + /* The design capacity (aka label capacity) of the cell in mAh */ + uint16_t design_capacity; /* 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; + /* The charge termination current in uA */ + uint16_t i_chg_term; + /* The empty voltage of the cell in mV */ + uint16_t v_empty; }; #endif diff --git a/dts/bindings/sensor/maxim,max17055.yaml b/dts/bindings/sensor/maxim,max17055.yaml index 2296cd677ae6..3fb40daa8435 100644 --- a/dts/bindings/sensor/maxim,max17055.yaml +++ b/dts/bindings/sensor/maxim,max17055.yaml @@ -11,6 +11,11 @@ compatible: "maxim,max17055" include: i2c-device.yaml properties: + design-capacity: + type: int + required: true + description: The design capacity (aka label capacity) of the cell in mAh + design-voltage: type: int required: true @@ -26,8 +31,18 @@ properties: required: true description: Battery Design Charging Current in mA (e.g. 2000) + i-chg-term: + type: int + required: true + description: The charge termination current in mA + rsense-mohms: type: int required: true description: > Value of Rsense resistor in milliohms (e.g. 5). It cannot be 0. + + v-empty: + type: int + required: true + description: The empty voltage of the cell in mV diff --git a/tests/drivers/build_all/i2c.dtsi b/tests/drivers/build_all/i2c.dtsi index 904354adbecd..13fc1f65485d 100644 --- a/tests/drivers/build_all/i2c.dtsi +++ b/tests/drivers/build_all/i2c.dtsi @@ -564,10 +564,13 @@ test_i2c_max17055: max17055@49 { compatible = "maxim,max17055"; label = "max17055"; reg = <0x49>; + design-capacity = <1500>; design-voltage = <3860>; - desired-voltage = <4400>; desired-charging-current = <2000>; + desired-voltage = <4400>; + i-chg-term = <100>; rsense-mohms = <5>; + v-empty = <3300>; }; test_i2c_vcnl4040: vcnl4040@60 {