| /* |
| * Copyright (c) 2021 Leonard Pollak |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT ti_ina219 |
| |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/i2c.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/drivers/sensor.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/pm/device.h> |
| #include <zephyr/sys/byteorder.h> |
| |
| #include "ina219.h" |
| |
| LOG_MODULE_REGISTER(INA219, CONFIG_SENSOR_LOG_LEVEL); |
| |
| static int ina219_reg_read(const struct device *dev, |
| uint8_t reg_addr, |
| uint16_t *reg_data) |
| { |
| const struct ina219_config *cfg = dev->config; |
| uint8_t rx_buf[2]; |
| int rc; |
| |
| rc = i2c_write_read_dt(&cfg->bus, |
| ®_addr, sizeof(reg_addr), |
| rx_buf, sizeof(rx_buf)); |
| |
| *reg_data = sys_get_be16(rx_buf); |
| |
| return rc; |
| } |
| |
| static int ina219_reg_write(const struct device *dev, |
| uint8_t addr, |
| uint16_t reg_data) |
| { |
| const struct ina219_config *cfg = dev->config; |
| uint8_t tx_buf[3]; |
| |
| tx_buf[0] = addr; |
| sys_put_be16(reg_data, &tx_buf[1]); |
| |
| return i2c_write_dt(&cfg->bus, tx_buf, sizeof(tx_buf)); |
| } |
| |
| static int ina219_reg_field_update(const struct device *dev, |
| uint8_t addr, |
| uint16_t mask, |
| uint16_t field) |
| { |
| uint16_t reg_data; |
| int rc; |
| |
| rc = ina219_reg_read(dev, addr, ®_data); |
| if (rc) { |
| return rc; |
| } |
| |
| reg_data = (reg_data & ~mask) | field; |
| |
| return ina219_reg_write(dev, addr, reg_data); |
| } |
| |
| static int ina219_set_msr_delay(const struct device *dev) |
| { |
| const struct ina219_config *cfg = dev->config; |
| struct ina219_data *data = dev->data; |
| |
| data->msr_delay = ina219_conv_delay(cfg->badc) + |
| ina219_conv_delay(cfg->sadc); |
| return 0; |
| } |
| |
| static int ina219_set_config(const struct device *dev) |
| { |
| const struct ina219_config *cfg = dev->config; |
| uint16_t reg_data; |
| |
| reg_data = (cfg->brng & INA219_BRNG_MASK) << INA219_BRNG_SHIFT | |
| (cfg->pg & INA219_PG_MASK) << INA219_PG_SHIFT | |
| (cfg->badc & INA219_ADC_MASK) << INA219_BADC_SHIFT | |
| (cfg->sadc & INA219_ADC_MASK) << INA219_SADC_SHIFT | |
| (cfg->mode & INA219_MODE_NORMAL); |
| |
| return ina219_reg_write(dev, INA219_REG_CONF, reg_data); |
| } |
| |
| static int ina219_set_calib(const struct device *dev) |
| { |
| const struct ina219_config *cfg = dev->config; |
| uint16_t cal; |
| |
| cal = INA219_SCALING_FACTOR / ((cfg->r_shunt) * (cfg->current_lsb)); |
| |
| return ina219_reg_write(dev, INA219_REG_CALIB, cal); |
| } |
| |
| static int ina219_sample_fetch(const struct device *dev, |
| enum sensor_channel chan) |
| { |
| struct ina219_data *data = dev->data; |
| uint16_t status; |
| uint16_t tmp; |
| int rc; |
| |
| if (chan != SENSOR_CHAN_ALL && |
| chan != SENSOR_CHAN_VOLTAGE && |
| chan != SENSOR_CHAN_POWER && |
| chan != SENSOR_CHAN_CURRENT) { |
| return -ENOTSUP; |
| } |
| |
| /* Trigger measurement and wait for completion */ |
| rc = ina219_reg_field_update(dev, |
| INA219_REG_CONF, |
| INA219_MODE_MASK, |
| INA219_MODE_NORMAL); |
| if (rc) { |
| LOG_ERR("Failed to start measurement."); |
| return rc; |
| } |
| |
| k_sleep(K_USEC(data->msr_delay)); |
| |
| rc = ina219_reg_read(dev, INA219_REG_V_BUS, &status); |
| if (rc) { |
| LOG_ERR("Failed to read device status."); |
| return rc; |
| } |
| |
| while (!(INA219_CNVR_RDY(status))) { |
| rc = ina219_reg_read(dev, INA219_REG_V_BUS, &status); |
| if (rc) { |
| LOG_ERR("Failed to read device status."); |
| return rc; |
| } |
| k_sleep(K_USEC(INA219_WAIT_MSR_RETRY)); |
| } |
| |
| /* Check for overflow */ |
| if (INA219_OVF_STATUS(status)) { |
| LOG_WRN("Power and/or Current calculations are out of range."); |
| } |
| |
| if (chan == SENSOR_CHAN_ALL || |
| chan == SENSOR_CHAN_VOLTAGE) { |
| |
| rc = ina219_reg_read(dev, INA219_REG_V_BUS, &tmp); |
| if (rc) { |
| LOG_ERR("Error reading bus voltage."); |
| return rc; |
| } |
| data->v_bus = INA219_VBUS_GET(tmp); |
| } |
| |
| if (chan == SENSOR_CHAN_ALL || |
| chan == SENSOR_CHAN_POWER) { |
| |
| rc = ina219_reg_read(dev, INA219_REG_POWER, &tmp); |
| if (rc) { |
| LOG_ERR("Error reading power register."); |
| return rc; |
| } |
| data->power = tmp; |
| } |
| |
| if (chan == SENSOR_CHAN_ALL || |
| chan == SENSOR_CHAN_CURRENT) { |
| |
| rc = ina219_reg_read(dev, INA219_REG_CURRENT, &tmp); |
| if (rc) { |
| LOG_ERR("Error reading current register."); |
| return rc; |
| } |
| data->current = tmp; |
| } |
| |
| return rc; |
| } |
| |
| static int ina219_channel_get(const struct device *dev, |
| enum sensor_channel chan, |
| struct sensor_value *val) |
| { |
| const struct ina219_config *cfg = dev->config; |
| struct ina219_data *data = dev->data; |
| double tmp; |
| int8_t sign = 1; |
| |
| switch (chan) { |
| case SENSOR_CHAN_VOLTAGE: |
| tmp = data->v_bus * INA219_V_BUS_MUL; |
| break; |
| case SENSOR_CHAN_POWER: |
| tmp = data->power * cfg->current_lsb * INA219_POWER_MUL * INA219_SI_MUL; |
| break; |
| case SENSOR_CHAN_CURRENT: |
| if (INA219_SIGN_BIT(data->current)) { |
| data->current = ~data->current + 1; |
| sign = -1; |
| } |
| tmp = sign * data->current * cfg->current_lsb * INA219_SI_MUL; |
| break; |
| default: |
| LOG_DBG("Channel not supported by device!"); |
| return -ENOTSUP; |
| } |
| |
| return sensor_value_from_double(val, tmp); |
| } |
| |
| #ifdef CONFIG_PM_DEVICE |
| static int ina219_pm_action(const struct device *dev, |
| enum pm_device_action action) |
| { |
| uint16_t reg_val; |
| |
| switch (action) { |
| case PM_DEVICE_ACTION_RESUME: |
| return ina219_init(dev); |
| case PM_DEVICE_ACTION_SUSPEND: |
| reg_val = INA219_MODE_SLEEP; |
| break; |
| case PM_DEVICE_ACTION_TURN_OFF: |
| reg_val = INA219_MODE_OFF; |
| break; |
| default: |
| return -ENOTSUP; |
| } |
| |
| return ina219_reg_field_update(dev, |
| INA219_REG_CONF, |
| INA219_MODE_MASK, |
| reg_val); |
| } |
| #endif /* CONFIG_PM_DEVICE */ |
| |
| static int ina219_init(const struct device *dev) |
| { |
| const struct ina219_config *cfg = dev->config; |
| int rc; |
| |
| if (!device_is_ready(cfg->bus.bus)) { |
| LOG_ERR("Device not ready."); |
| return -ENODEV; |
| } |
| |
| rc = ina219_reg_write(dev, INA219_REG_CONF, INA219_RST); |
| if (rc) { |
| LOG_ERR("Could not reset device."); |
| return rc; |
| } |
| |
| rc = ina219_set_config(dev); |
| if (rc) { |
| LOG_ERR("Could not set configuration data."); |
| return rc; |
| } |
| |
| rc = ina219_set_calib(dev); |
| if (rc) { |
| LOG_DBG("Could not set calibration data."); |
| return rc; |
| } |
| |
| /* Set measurement delay */ |
| rc = ina219_set_msr_delay(dev); |
| if (rc) { |
| LOG_ERR("Could not get measurement delay."); |
| return rc; |
| } |
| |
| k_sleep(K_USEC(INA219_WAIT_STARTUP)); |
| |
| return 0; |
| } |
| |
| static const struct sensor_driver_api ina219_api = { |
| .sample_fetch = ina219_sample_fetch, |
| .channel_get = ina219_channel_get, |
| }; |
| |
| |
| #define INA219_INIT(n) \ |
| static struct ina219_data ina219_data_##n; \ |
| \ |
| static const struct ina219_config ina219_config_##n = { \ |
| .bus = I2C_DT_SPEC_INST_GET(n), \ |
| .current_lsb = DT_INST_PROP(n, lsb_microamp), \ |
| .r_shunt = DT_INST_PROP(n, shunt_milliohm), \ |
| .brng = DT_INST_PROP(n, brng), \ |
| .pg = DT_INST_PROP(n, pg), \ |
| .badc = DT_INST_PROP(n, badc), \ |
| .sadc = DT_INST_PROP(n, sadc), \ |
| .mode = INA219_MODE_NORMAL \ |
| }; \ |
| \ |
| PM_DEVICE_DT_INST_DEFINE(n, ina219_pm_action); \ |
| \ |
| SENSOR_DEVICE_DT_INST_DEFINE(n, \ |
| ina219_init, \ |
| PM_DEVICE_DT_INST_GET(n), \ |
| &ina219_data_##n, \ |
| &ina219_config_##n, \ |
| POST_KERNEL, \ |
| CONFIG_SENSOR_INIT_PRIORITY, \ |
| &ina219_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(INA219_INIT) |