| /* |
| * Copyright (c) 2020 Linumiz |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * Relevant documents: |
| * - BQ27441 |
| * Datasheet: https://www.ti.com/lit/gpn/bq27441-g1 |
| * Technical reference manual: https://www.ti.com/lit/pdf/sluuac9 |
| * - BQ27421 |
| * Datasheet: https://www.ti.com/lit/gpn/bq27421-g1 |
| * Technical reference manual: https://www.ti.com/lit/pdf/sluuac5 |
| * - BQ27427 |
| * Datasheet: https://www.ti.com/lit/gpn/bq27427 |
| * Technical reference manual: https://www.ti.com/lit/pdf/sluucd5 |
| */ |
| |
| #define DT_DRV_COMPAT ti_bq274xx |
| |
| #include <zephyr/drivers/i2c.h> |
| #include <zephyr/init.h> |
| #include <zephyr/drivers/sensor.h> |
| #include <zephyr/pm/device.h> |
| #include <zephyr/sys/__assert.h> |
| #include <string.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/logging/log.h> |
| |
| #include "bq274xx.h" |
| |
| LOG_MODULE_REGISTER(bq274xx, CONFIG_SENSOR_LOG_LEVEL); |
| |
| /* subclass 64 & 82 needs 5ms delay */ |
| #define BQ274XX_SUBCLASS_DELAY K_MSEC(5) |
| |
| /* Time to wait for CFGUP bit to be set, up to 1 second according to the |
| * technical reference manual, keep some headroom like the Linux driver. |
| */ |
| #define BQ274XX_CFGUP_DELAY K_MSEC(25) |
| #define BQ274XX_CFGUP_MAX_TRIES 100 |
| |
| /* Time to set pin in order to exit shutdown mode */ |
| #define PIN_DELAY_TIME K_MSEC(1) |
| |
| /* Delay from power up or shutdown exit to chip entering active state, this is |
| * defined as 250ms typical in the datasheet (Power-up communication delay). |
| */ |
| #define POWER_UP_DELAY_MS 300 |
| |
| /* Data memory size */ |
| #define BQ27XXX_DM_SZ 32 |
| |
| /* Config update mode flag */ |
| #define BQ27XXX_FLAG_CFGUP BIT(4) |
| |
| /* BQ27427 CC Gain */ |
| #define BQ27427_CC_GAIN BQ274XX_EXT_BLKDAT(5) |
| #define BQ27427_CC_GAIN_SIGN_BIT BIT(7) |
| |
| /* Subclasses */ |
| #define BQ274XX_SUBCLASS_82 82 |
| #define BQ274XX_SUBCLASS_105 105 |
| |
| /* For temperature conversion */ |
| #define KELVIN_OFFSET 273.15 |
| |
| static const struct bq274xx_regs bq27421_regs = { |
| .dm_design_capacity = 10, |
| .dm_design_energy = 12, |
| .dm_terminate_voltage = 16, |
| .dm_taper_rate = 27, |
| }; |
| |
| static const struct bq274xx_regs bq27427_regs = { |
| .dm_design_capacity = 6, |
| .dm_design_energy = 8, |
| .dm_terminate_voltage = 10, |
| .dm_taper_rate = 21, |
| }; |
| |
| static int bq274xx_cmd_reg_read(const struct device *dev, uint8_t reg_addr, |
| int16_t *val) |
| { |
| const struct bq274xx_config *config = dev->config; |
| uint8_t i2c_data[2]; |
| int ret; |
| |
| ret = i2c_burst_read_dt(&config->i2c, reg_addr, i2c_data, sizeof(i2c_data)); |
| if (ret < 0) { |
| LOG_ERR("Unable to read register"); |
| return -EIO; |
| } |
| |
| *val = sys_get_le16(i2c_data); |
| |
| return 0; |
| } |
| |
| static int bq274xx_ctrl_reg_write(const struct device *dev, uint16_t subcommand) |
| { |
| const struct bq274xx_config *config = dev->config; |
| int ret; |
| |
| uint8_t tx_buf[3]; |
| |
| tx_buf[0] = BQ274XX_CMD_CONTROL; |
| sys_put_le16(subcommand, &tx_buf[1]); |
| |
| ret = i2c_write_dt(&config->i2c, tx_buf, sizeof(tx_buf)); |
| if (ret < 0) { |
| LOG_ERR("Failed to write into control register"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int bq274xx_get_device_type(const struct device *dev, uint16_t *val) |
| { |
| int ret; |
| |
| ret = bq274xx_ctrl_reg_write(dev, BQ274XX_CTRL_DEVICE_TYPE); |
| if (ret < 0) { |
| LOG_ERR("Unable to write control register"); |
| return -EIO; |
| } |
| |
| ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_CONTROL, val); |
| if (ret < 0) { |
| LOG_ERR("Unable to read register"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int bq274xx_read_block(const struct device *dev, |
| uint8_t subclass, |
| uint8_t *block, uint8_t num_bytes) |
| { |
| const struct bq274xx_config *const config = dev->config; |
| int ret; |
| |
| ret = i2c_reg_write_byte_dt(&config->i2c, BQ274XX_EXT_DATA_CLASS, subclass); |
| if (ret < 0) { |
| LOG_ERR("Failed to update state subclass"); |
| return -EIO; |
| } |
| |
| /* DataBlock(), 0 for the first 32 bytes. */ |
| ret = i2c_reg_write_byte_dt(&config->i2c, BQ274XX_EXT_DATA_BLOCK, 0x00); |
| if (ret < 0) { |
| LOG_ERR("Failed to update block offset"); |
| return -EIO; |
| } |
| |
| k_sleep(BQ274XX_SUBCLASS_DELAY); |
| |
| ret = i2c_burst_read_dt(&config->i2c, BQ274XX_EXT_BLKDAT_START, block, num_bytes); |
| if (ret < 0) { |
| LOG_ERR("Unable to read block data"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int bq274xx_write_block(const struct device *dev, |
| uint8_t *block, uint8_t num_bytes) |
| { |
| const struct bq274xx_config *const config = dev->config; |
| uint8_t checksum = 0; |
| int ret; |
| uint8_t buf[1 + BQ27XXX_DM_SZ]; |
| |
| __ASSERT_NO_MSG(num_bytes <= BQ27XXX_DM_SZ); |
| |
| buf[0] = BQ274XX_EXT_BLKDAT_START; |
| memcpy(&buf[1], block, num_bytes); |
| |
| ret = i2c_write_dt(&config->i2c, buf, 1 + num_bytes); |
| if (ret < 0) { |
| LOG_ERR("Unable to write block data"); |
| return -EIO; |
| } |
| |
| for (uint8_t i = 0; i < num_bytes; i++) { |
| checksum += block[i]; |
| } |
| checksum = 0xff - checksum; |
| |
| ret = i2c_reg_write_byte_dt(&config->i2c, BQ274XX_EXT_CHECKSUM, checksum); |
| if (ret < 0) { |
| LOG_ERR("Failed to update block checksum"); |
| return -EIO; |
| } |
| |
| k_sleep(BQ274XX_SUBCLASS_DELAY); |
| |
| return 0; |
| } |
| |
| static void bq274xx_update_block(uint8_t *block, |
| uint8_t offset, uint16_t val, |
| bool *block_modified) |
| { |
| uint16_t old_val; |
| |
| old_val = sys_get_be16(&block[offset]); |
| |
| LOG_DBG("update block: off=%d old=%d new=%d\n", offset, old_val, val); |
| |
| if (val == old_val) { |
| return; |
| } |
| |
| sys_put_be16(val, &block[offset]); |
| |
| *block_modified = true; |
| } |
| |
| static int bq274xx_mode_cfgupdate(const struct device *dev, bool enabled) |
| { |
| uint16_t flags; |
| uint8_t try; |
| int ret; |
| uint16_t val = enabled ? BQ274XX_CTRL_SET_CFGUPDATE : BQ274XX_CTRL_SOFT_RESET; |
| bool enabled_flag; |
| |
| ret = bq274xx_ctrl_reg_write(dev, val); |
| if (ret < 0) { |
| LOG_ERR("Unable to set device mode to %02x", val); |
| return -EIO; |
| } |
| |
| for (try = 0; try < BQ274XX_CFGUP_MAX_TRIES; try++) { |
| ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_FLAGS, &flags); |
| if (ret < 0) { |
| LOG_ERR("Unable to read flags"); |
| return -EIO; |
| } |
| |
| enabled_flag = !!(flags & BQ27XXX_FLAG_CFGUP); |
| if (enabled_flag == enabled) { |
| LOG_DBG("CFGUP ready, try %u", try); |
| break; |
| } |
| |
| k_sleep(BQ274XX_CFGUP_DELAY); |
| } |
| |
| if (try >= BQ274XX_CFGUP_MAX_TRIES) { |
| LOG_ERR("Config mode change timeout"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * BQ27427 needs the CC Gain polarity swapped from the ROM value. |
| * The details are currently only documented in the TI E2E support forum: |
| * https://e2e.ti.com/support/power-management-group/power-management/f/power-management-forum/1215460/bq27427evm-misbehaving-stateofcharge |
| */ |
| static int bq27427_ccgain_quirk(const struct device *dev) |
| { |
| const struct bq274xx_config *const config = dev->config; |
| int ret; |
| uint8_t val, checksum; |
| |
| ret = i2c_reg_write_byte_dt(&config->i2c, BQ274XX_EXT_DATA_CLASS, |
| BQ274XX_SUBCLASS_105); |
| if (ret < 0) { |
| LOG_ERR("Failed to update state subclass"); |
| return -EIO; |
| } |
| |
| ret = i2c_reg_write_byte_dt(&config->i2c, BQ274XX_EXT_DATA_BLOCK, 0x00); |
| if (ret < 0) { |
| LOG_ERR("Failed to update block offset"); |
| return -EIO; |
| } |
| |
| k_sleep(BQ274XX_SUBCLASS_DELAY); |
| |
| ret = i2c_reg_read_byte_dt(&config->i2c, BQ27427_CC_GAIN, &val); |
| if (ret < 0) { |
| LOG_ERR("Failed to read ccgain"); |
| return -EIO; |
| } |
| |
| if (!(val & BQ27427_CC_GAIN_SIGN_BIT)) { |
| LOG_DBG("bq27427 quirk already applied"); |
| return 0; |
| } |
| |
| ret = i2c_reg_read_byte_dt(&config->i2c, BQ274XX_EXT_CHECKSUM, &checksum); |
| if (ret < 0) { |
| LOG_ERR("Failed to read block checksum"); |
| return -EIO; |
| } |
| |
| /* Flip the sign bit on both value and checksum. */ |
| val ^= BQ27427_CC_GAIN_SIGN_BIT; |
| checksum ^= BQ27427_CC_GAIN_SIGN_BIT; |
| |
| LOG_DBG("bq27427: val=%02x checksum=%02x", val, checksum); |
| |
| ret = i2c_reg_write_byte_dt(&config->i2c, BQ27427_CC_GAIN, val); |
| if (ret < 0) { |
| LOG_ERR("Failed to update ccgain"); |
| return -EIO; |
| } |
| |
| ret = i2c_reg_write_byte_dt(&config->i2c, BQ274XX_EXT_CHECKSUM, checksum); |
| if (ret < 0) { |
| LOG_ERR("Failed to update block checksum"); |
| return -EIO; |
| } |
| |
| k_sleep(BQ274XX_SUBCLASS_DELAY); |
| |
| return 0; |
| } |
| |
| static int bq274xx_ensure_chemistry(const struct device *dev) |
| { |
| struct bq274xx_data *data = dev->data; |
| const struct bq274xx_config *const config = dev->config; |
| uint16_t chem_id = config->chemistry_id; |
| |
| if (chem_id == 0) { |
| /* No chemistry ID set, rely on the default of the device.*/ |
| return 0; |
| } |
| int ret; |
| uint16_t val; |
| |
| ret = bq274xx_ctrl_reg_write(dev, BQ274XX_CTRL_CHEM_ID); |
| if (ret < 0) { |
| LOG_ERR("Unable to write control register"); |
| return -EIO; |
| } |
| |
| ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_CONTROL, &val); |
| if (ret < 0) { |
| LOG_ERR("Unable to read register"); |
| return -EIO; |
| } |
| |
| LOG_DBG("Chem ID: %04x", val); |
| |
| if (val != chem_id) { |
| /* Only the bq27427 has a configurable Chemistry ID. On bq27421, it depends on the |
| * variant of the chip, so just error out if the chemistry ID is wrong. |
| */ |
| if (data->regs != &bq27427_regs) { |
| LOG_ERR("Unable to confirm chemistry ID 0x%04x. Device reported 0x%04x", |
| chem_id, val); |
| return -EIO; |
| } |
| |
| uint16_t cmd; |
| |
| switch (val) { |
| case BQ27427_CHEM_ID_A: |
| cmd = BQ27427_CTRL_CHEM_A; |
| break; |
| case BQ27427_CHEM_ID_B: |
| cmd = BQ27427_CTRL_CHEM_B; |
| break; |
| case BQ27427_CHEM_ID_C: |
| cmd = BQ27427_CTRL_CHEM_C; |
| break; |
| default: |
| LOG_ERR("Unsupported chemistry ID 0x%04x", val); |
| return -EINVAL; |
| } |
| |
| ret = bq274xx_ctrl_reg_write(dev, cmd); |
| if (ret < 0) { |
| LOG_ERR("Unable to configure chemistry"); |
| return -EIO; |
| } |
| } |
| return 0; |
| } |
| |
| static int bq274xx_gauge_configure(const struct device *dev) |
| { |
| const struct bq274xx_config *const config = dev->config; |
| struct bq274xx_data *data = dev->data; |
| const struct bq274xx_regs *regs = data->regs; |
| int ret; |
| uint16_t designenergy_mwh, taperrate; |
| uint8_t block[BQ27XXX_DM_SZ]; |
| bool block_modified = false; |
| |
| designenergy_mwh = (uint32_t)config->design_capacity * 37 / 10; /* x3.7 */ |
| taperrate = config->design_capacity * 10 / config->taper_current; |
| |
| ret = bq274xx_ctrl_reg_write(dev, BQ274XX_UNSEAL_KEY_A); |
| if (ret < 0) { |
| LOG_ERR("Unable to unseal the battery"); |
| return -EIO; |
| } |
| |
| ret = bq274xx_ctrl_reg_write(dev, BQ274XX_UNSEAL_KEY_B); |
| if (ret < 0) { |
| LOG_ERR("Unable to unseal the battery"); |
| return -EIO; |
| } |
| |
| ret = bq274xx_mode_cfgupdate(dev, true); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = i2c_reg_write_byte_dt(&config->i2c, BQ274XX_EXT_DATA_CONTROL, 0x00); |
| if (ret < 0) { |
| LOG_ERR("Failed to enable block data memory"); |
| return -EIO; |
| } |
| |
| ret = bq274xx_read_block(dev, BQ274XX_SUBCLASS_82, block, sizeof(block)); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| bq274xx_update_block(block, |
| regs->dm_design_capacity, config->design_capacity, |
| &block_modified); |
| bq274xx_update_block(block, |
| regs->dm_design_energy, designenergy_mwh, |
| &block_modified); |
| bq274xx_update_block(block, |
| regs->dm_terminate_voltage, config->terminate_voltage, |
| &block_modified); |
| bq274xx_update_block(block, |
| regs->dm_taper_rate, taperrate, |
| &block_modified); |
| |
| if (block_modified) { |
| LOG_INF("bq274xx: updating fuel gauge parameters"); |
| |
| ret = bq274xx_write_block(dev, block, sizeof(block)); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| if (data->regs == &bq27427_regs) { |
| ret = bq27427_ccgain_quirk(dev); |
| if (ret < 0) { |
| return ret; |
| } |
| } |
| |
| ret = bq274xx_ensure_chemistry(dev); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = bq274xx_mode_cfgupdate(dev, false); |
| if (ret < 0) { |
| return ret; |
| } |
| } |
| |
| ret = bq274xx_ctrl_reg_write(dev, BQ274XX_CTRL_SEALED); |
| if (ret < 0) { |
| LOG_ERR("Failed to seal the gauge"); |
| return -EIO; |
| } |
| |
| ret = bq274xx_ctrl_reg_write(dev, BQ274XX_CTRL_BAT_INSERT); |
| if (ret < 0) { |
| LOG_ERR("Unable to configure BAT Detect"); |
| return -EIO; |
| } |
| |
| data->configured = true; |
| |
| return 0; |
| } |
| |
| static int bq274xx_channel_get(const struct device *dev, enum sensor_channel chan, |
| struct sensor_value *val) |
| { |
| struct bq274xx_data *data = dev->data; |
| int32_t int_temp; |
| |
| switch (chan) { |
| case SENSOR_CHAN_GAUGE_VOLTAGE: |
| val->val1 = ((data->voltage / 1000)); |
| val->val2 = ((data->voltage % 1000) * 1000U); |
| break; |
| |
| case SENSOR_CHAN_GAUGE_AVG_CURRENT: |
| val->val1 = ((data->avg_current / 1000)); |
| val->val2 = ((data->avg_current % 1000) * 1000U); |
| break; |
| |
| case SENSOR_CHAN_GAUGE_STDBY_CURRENT: |
| val->val1 = ((data->stdby_current / 1000)); |
| val->val2 = ((data->stdby_current % 1000) * 1000U); |
| break; |
| |
| case SENSOR_CHAN_GAUGE_MAX_LOAD_CURRENT: |
| val->val1 = ((data->max_load_current / 1000)); |
| val->val2 = ((data->max_load_current % 1000) * 1000U); |
| break; |
| |
| case SENSOR_CHAN_GAUGE_TEMP: |
| /* Convert units from 0.1K to 0.01K */ |
| int_temp = data->internal_temperature * 10; |
| /* Convert to 0.01C */ |
| int_temp -= (int32_t)(100.0 * KELVIN_OFFSET); |
| val->val1 = int_temp / 100; |
| val->val2 = (int_temp % 100) * 10000; |
| break; |
| |
| case SENSOR_CHAN_GAUGE_STATE_OF_CHARGE: |
| val->val1 = data->state_of_charge; |
| val->val2 = 0; |
| break; |
| |
| case SENSOR_CHAN_GAUGE_STATE_OF_HEALTH: |
| val->val1 = data->state_of_health; |
| val->val2 = 0; |
| break; |
| |
| case SENSOR_CHAN_GAUGE_FULL_CHARGE_CAPACITY: |
| val->val1 = data->full_charge_capacity; |
| val->val2 = 0; |
| break; |
| |
| case SENSOR_CHAN_GAUGE_REMAINING_CHARGE_CAPACITY: |
| val->val1 = data->remaining_charge_capacity; |
| val->val2 = 0; |
| break; |
| |
| case SENSOR_CHAN_GAUGE_NOM_AVAIL_CAPACITY: |
| val->val1 = data->nom_avail_capacity; |
| val->val2 = 0; |
| break; |
| |
| case SENSOR_CHAN_GAUGE_FULL_AVAIL_CAPACITY: |
| val->val1 = data->full_avail_capacity; |
| val->val2 = 0; |
| break; |
| |
| case SENSOR_CHAN_GAUGE_AVG_POWER: |
| val->val1 = data->avg_power; |
| val->val2 = 0; |
| break; |
| |
| default: |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| |
| static int bq274xx_sample_fetch(const struct device *dev, enum sensor_channel chan) |
| { |
| struct bq274xx_data *data = dev->data; |
| int ret = -ENOTSUP; |
| |
| if (!data->configured) { |
| ret = bq274xx_gauge_configure(dev); |
| if (ret < 0) { |
| return ret; |
| } |
| } |
| |
| if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_GAUGE_VOLTAGE) { |
| ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_VOLTAGE, |
| &data->voltage); |
| if (ret < 0) { |
| LOG_ERR("Failed to read voltage"); |
| return -EIO; |
| } |
| } |
| |
| if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_GAUGE_AVG_CURRENT) { |
| ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_AVG_CURRENT, |
| &data->avg_current); |
| if (ret < 0) { |
| LOG_ERR("Failed to read average current "); |
| return -EIO; |
| } |
| } |
| |
| if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_GAUGE_TEMP) { |
| ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_INT_TEMP, |
| &data->internal_temperature); |
| if (ret < 0) { |
| LOG_ERR("Failed to read internal temperature"); |
| return -EIO; |
| } |
| } |
| |
| if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_GAUGE_STDBY_CURRENT) { |
| ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_STDBY_CURRENT, |
| &data->stdby_current); |
| if (ret < 0) { |
| LOG_ERR("Failed to read standby current"); |
| return -EIO; |
| } |
| } |
| |
| if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_GAUGE_MAX_LOAD_CURRENT) { |
| ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_MAX_CURRENT, |
| &data->max_load_current); |
| if (ret < 0) { |
| LOG_ERR("Failed to read maximum current"); |
| return -EIO; |
| } |
| } |
| |
| if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_GAUGE_STATE_OF_CHARGE) { |
| ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_SOC, |
| &data->state_of_charge); |
| if (ret < 0) { |
| LOG_ERR("Failed to read state of charge"); |
| return -EIO; |
| } |
| } |
| |
| if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_GAUGE_FULL_CHARGE_CAPACITY) { |
| ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_FULL_CAPACITY, |
| &data->full_charge_capacity); |
| if (ret < 0) { |
| LOG_ERR("Failed to read full charge capacity"); |
| return -EIO; |
| } |
| } |
| |
| if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_GAUGE_REMAINING_CHARGE_CAPACITY) { |
| ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_REM_CAPACITY, |
| &data->remaining_charge_capacity); |
| if (ret < 0) { |
| LOG_ERR("Failed to read remaining charge capacity"); |
| return -EIO; |
| } |
| } |
| |
| if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_GAUGE_NOM_AVAIL_CAPACITY) { |
| ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_NOM_CAPACITY, |
| &data->nom_avail_capacity); |
| if (ret < 0) { |
| LOG_ERR("Failed to read nominal available capacity"); |
| return -EIO; |
| } |
| } |
| |
| if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_GAUGE_FULL_AVAIL_CAPACITY) { |
| ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_AVAIL_CAPACITY, |
| &data->full_avail_capacity); |
| if (ret < 0) { |
| LOG_ERR("Failed to read full available capacity"); |
| return -EIO; |
| } |
| } |
| |
| if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_GAUGE_AVG_POWER) { |
| ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_AVG_POWER, |
| &data->avg_power); |
| if (ret < 0) { |
| LOG_ERR("Failed to read battery average power"); |
| return -EIO; |
| } |
| } |
| |
| if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_GAUGE_STATE_OF_HEALTH) { |
| ret = bq274xx_cmd_reg_read(dev, BQ274XX_CMD_SOH, |
| &data->state_of_health); |
| |
| data->state_of_health = (data->state_of_health) & 0x00FF; |
| |
| if (ret < 0) { |
| LOG_ERR("Failed to read state of health"); |
| return -EIO; |
| } |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * @brief initialise the fuel gauge |
| * |
| * @return 0 for success |
| */ |
| static int bq274xx_gauge_init(const struct device *dev) |
| { |
| const struct bq274xx_config *const config = dev->config; |
| struct bq274xx_data *data = dev->data; |
| int ret; |
| uint16_t id; |
| |
| if (!device_is_ready(config->i2c.bus)) { |
| LOG_ERR("I2C bus device not ready"); |
| return -ENODEV; |
| } |
| |
| #if defined(CONFIG_BQ274XX_PM) || defined(CONFIG_BQ274XX_TRIGGER) |
| if (!gpio_is_ready_dt(&config->int_gpios)) { |
| LOG_ERR("GPIO device pointer is not ready to be used"); |
| return -ENODEV; |
| } |
| #endif |
| |
| k_sleep(K_TIMEOUT_ABS_MS(POWER_UP_DELAY_MS)); |
| |
| ret = bq274xx_get_device_type(dev, &id); |
| if (ret < 0) { |
| LOG_ERR("Unable to get device ID"); |
| return -EIO; |
| } |
| |
| if (id == BQ27421_DEVICE_ID) { |
| data->regs = &bq27421_regs; |
| } else if (id == BQ27427_DEVICE_ID) { |
| data->regs = &bq27427_regs; |
| } else { |
| LOG_ERR("Unsupported device ID: 0x%04x", id); |
| return -ENOTSUP; |
| } |
| |
| #ifdef CONFIG_BQ274XX_TRIGGER |
| ret = bq274xx_trigger_mode_init(dev); |
| if (ret < 0) { |
| LOG_ERR("Unable set up trigger mode."); |
| return ret; |
| } |
| #endif |
| |
| if (!config->lazy_loading) { |
| ret = bq274xx_gauge_configure(dev); |
| } |
| |
| return ret; |
| } |
| |
| #ifdef CONFIG_BQ274XX_PM |
| static int bq274xx_enter_shutdown_mode(const struct device *dev) |
| { |
| int ret; |
| |
| ret = bq274xx_ctrl_reg_write(dev, BQ274XX_UNSEAL_KEY_A); |
| if (ret < 0) { |
| LOG_ERR("Unable to unseal the battery"); |
| return ret; |
| } |
| |
| ret = bq274xx_ctrl_reg_write(dev, BQ274XX_UNSEAL_KEY_B); |
| if (ret < 0) { |
| LOG_ERR("Unable to unseal the battery"); |
| return ret; |
| } |
| |
| ret = bq274xx_ctrl_reg_write(dev, BQ274XX_CTRL_SHUTDOWN_ENABLE); |
| if (ret < 0) { |
| LOG_ERR("Unable to enable shutdown mode"); |
| return ret; |
| } |
| |
| ret = bq274xx_ctrl_reg_write(dev, BQ274XX_CTRL_SHUTDOWN); |
| if (ret < 0) { |
| LOG_ERR("Unable to enter shutdown mode"); |
| return ret; |
| } |
| |
| ret = bq274xx_ctrl_reg_write(dev, BQ274XX_CTRL_SEALED); |
| if (ret < 0) { |
| LOG_ERR("Failed to seal the gauge"); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int bq274xx_exit_shutdown_mode(const struct device *dev) |
| { |
| const struct bq274xx_config *const config = dev->config; |
| int ret; |
| |
| ret = gpio_pin_configure_dt(&config->int_gpios, GPIO_OUTPUT | GPIO_OPEN_DRAIN); |
| if (ret < 0) { |
| LOG_ERR("Unable to configure interrupt pin to output and open drain"); |
| return ret; |
| } |
| |
| ret = gpio_pin_set_dt(&config->int_gpios, 0); |
| if (ret < 0) { |
| LOG_ERR("Unable to set interrupt pin to low"); |
| return ret; |
| } |
| |
| k_sleep(PIN_DELAY_TIME); |
| |
| ret = gpio_pin_configure_dt(&config->int_gpios, GPIO_INPUT); |
| if (ret < 0) { |
| LOG_ERR("Unable to configure interrupt pin to input"); |
| return ret; |
| } |
| |
| if (!config->lazy_loading) { |
| k_sleep(K_MSEC(POWER_UP_DELAY_MS)); |
| |
| ret = bq274xx_gauge_configure(dev); |
| if (ret < 0) { |
| LOG_ERR("Unable to configure bq274xx gauge"); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int bq274xx_pm_action(const struct device *dev, |
| enum pm_device_action action) |
| { |
| int ret; |
| |
| switch (action) { |
| case PM_DEVICE_ACTION_TURN_OFF: |
| ret = bq274xx_enter_shutdown_mode(dev); |
| break; |
| case PM_DEVICE_ACTION_RESUME: |
| ret = bq274xx_exit_shutdown_mode(dev); |
| break; |
| default: |
| ret = -ENOTSUP; |
| break; |
| } |
| |
| return ret; |
| } |
| #endif /* CONFIG_BQ274XX_PM */ |
| |
| static const struct sensor_driver_api bq274xx_battery_driver_api = { |
| .sample_fetch = bq274xx_sample_fetch, |
| .channel_get = bq274xx_channel_get, |
| #ifdef CONFIG_BQ274XX_TRIGGER |
| .trigger_set = bq274xx_trigger_set, |
| #endif |
| }; |
| |
| #if defined(CONFIG_BQ274XX_PM) || defined(CONFIG_BQ274XX_TRIGGER) |
| #define BQ274XX_INT_CFG(index) \ |
| .int_gpios = GPIO_DT_SPEC_INST_GET(index, int_gpios), |
| #define PM_BQ274XX_DT_INST_DEFINE(index, bq274xx_pm_action) \ |
| PM_DEVICE_DT_INST_DEFINE(index, bq274xx_pm_action) |
| #define PM_BQ274XX_DT_INST_GET(index) PM_DEVICE_DT_INST_GET(index) |
| #else |
| #define BQ274XX_INT_CFG(index) |
| #define PM_BQ274XX_DT_INST_DEFINE(index, bq274xx_pm_action) |
| #define PM_BQ274XX_DT_INST_GET(index) NULL |
| #endif |
| |
| #define BQ274XX_INIT(index) \ |
| static struct bq274xx_data bq274xx_driver_##index; \ |
| \ |
| static const struct bq274xx_config bq274xx_config_##index = { \ |
| .i2c = I2C_DT_SPEC_INST_GET(index), \ |
| BQ274XX_INT_CFG(index) \ |
| .design_voltage = DT_INST_PROP(index, design_voltage), \ |
| .design_capacity = DT_INST_PROP(index, design_capacity), \ |
| .taper_current = DT_INST_PROP(index, taper_current), \ |
| .terminate_voltage = DT_INST_PROP(index, terminate_voltage), \ |
| .chemistry_id = DT_INST_PROP_OR(index, chemistry_id, 0), \ |
| .lazy_loading = DT_INST_PROP(index, zephyr_lazy_load), \ |
| }; \ |
| \ |
| PM_BQ274XX_DT_INST_DEFINE(index, bq274xx_pm_action); \ |
| \ |
| SENSOR_DEVICE_DT_INST_DEFINE(index, &bq274xx_gauge_init, \ |
| PM_BQ274XX_DT_INST_GET(index), \ |
| &bq274xx_driver_##index, \ |
| &bq274xx_config_##index, POST_KERNEL, \ |
| CONFIG_SENSOR_INIT_PRIORITY, \ |
| &bq274xx_battery_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(BQ274XX_INIT) |