| /* |
| * Copyright (c) 2023 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT ti_ina3221 |
| |
| #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 "ina3221.h" |
| |
| LOG_MODULE_REGISTER(INA3221, CONFIG_SENSOR_LOG_LEVEL); |
| |
| #define MAX_RETRIES 10 |
| |
| static int reg_read(const struct device *dev, uint8_t reg_addr, uint16_t *reg_data) |
| { |
| const struct ina3221_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 reg_write(const struct device *dev, uint8_t addr, uint16_t reg_data) |
| { |
| const struct ina3221_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 ina3221_init(const struct device *dev) |
| { |
| int ret; |
| const struct ina3221_config *cfg = dev->config; |
| struct ina3221_data *data = dev->data; |
| uint16_t reg_data; |
| |
| __ASSERT_NO_MSG(cfg->avg_mode < 8); |
| __ASSERT_NO_MSG(cfg->conv_time_bus < 8); |
| __ASSERT_NO_MSG(cfg->conv_time_shunt < 8); |
| |
| /* select first enabled channel */ |
| for (size_t i = 0; i < 3; ++i) { |
| if (cfg->enable_channel[i]) { |
| data->selected_channel = i; |
| break; |
| } |
| } |
| |
| /* check bus */ |
| if (!i2c_is_ready_dt(&cfg->bus)) { |
| LOG_ERR("Device not ready."); |
| return -ENODEV; |
| } |
| |
| /* check connection */ |
| ret = reg_read(dev, INA3221_MANUF_ID, ®_data); |
| if (ret) { |
| LOG_ERR("No answer from sensor."); |
| return ret; |
| } |
| if (reg_data != INA3221_MANUF_ID_VALUE) { |
| LOG_ERR("Unexpected manufacturer ID: 0x%04x", reg_data); |
| return -EFAULT; |
| } |
| ret = reg_read(dev, INA3221_CHIP_ID, ®_data); |
| if (ret) { |
| return ret; |
| } |
| if (reg_data != INA3221_CHIP_ID_VALUE) { |
| LOG_ERR("Unexpected chip ID: 0x%04x", reg_data); |
| return -EFAULT; |
| } |
| |
| /* reset */ |
| ret = reg_read(dev, INA3221_CONFIG, ®_data); |
| if (ret) { |
| return ret; |
| } |
| reg_data |= INA3221_CONFIG_RST; |
| ret = reg_write(dev, INA3221_CONFIG, reg_data); |
| if (ret) { |
| return ret; |
| } |
| |
| /* configure */ |
| reg_data = (cfg->conv_time_shunt << 3) | (cfg->conv_time_bus << 6) | (cfg->avg_mode << 9) | |
| (INA3221_CONFIG_CH1 * cfg->enable_channel[0]) | |
| (INA3221_CONFIG_CH2 * cfg->enable_channel[1]) | |
| (INA3221_CONFIG_CH3 * cfg->enable_channel[2]); |
| |
| ret = reg_write(dev, INA3221_CONFIG, reg_data); |
| if (ret) { |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int start_measurement(const struct device *dev, bool bus, bool shunt) |
| { |
| int ret; |
| uint16_t reg_data; |
| |
| ret = reg_read(dev, INA3221_CONFIG, ®_data); |
| if (ret) { |
| return ret; |
| } |
| |
| reg_data &= ~(INA3221_CONFIG_BUS | INA3221_CONFIG_SHUNT); |
| reg_data |= (INA3221_CONFIG_BUS * bus) | (INA3221_CONFIG_SHUNT * shunt); |
| |
| ret = reg_write(dev, INA3221_CONFIG, reg_data); |
| if (ret) { |
| return ret; |
| } |
| return 0; |
| } |
| |
| static bool measurement_ready(const struct device *dev) |
| { |
| int ret; |
| uint16_t reg_data; |
| |
| ret = reg_read(dev, INA3221_MASK_ENABLE, ®_data); |
| if (ret) { |
| return false; |
| } |
| return reg_data & INA3221_MASK_ENABLE_CONVERSION_READY; |
| } |
| |
| static int ina3221_sample_fetch(const struct device *dev, enum sensor_channel chan) |
| { |
| struct ina3221_data *data = dev->data; |
| const struct ina3221_config *cfg = dev->config; |
| bool measurement_successful = false; |
| k_timeout_t measurement_time = K_NO_WAIT; |
| int ret; |
| |
| /* Trigger measurement and wait for completion */ |
| if (chan == SENSOR_CHAN_VOLTAGE) { |
| ret = start_measurement(dev, true, false); |
| if (ret) { |
| return ret; |
| } |
| measurement_time = |
| K_USEC(avg_mode_samples[cfg->avg_mode] * |
| conv_time_us[cfg->conv_time_bus]); |
| } else if (chan == SENSOR_CHAN_CURRENT) { |
| ret = start_measurement(dev, false, true); |
| if (ret) { |
| return ret; |
| } |
| measurement_time = |
| K_USEC(avg_mode_samples[cfg->avg_mode] * |
| conv_time_us[cfg->conv_time_shunt]); |
| } else if (chan == SENSOR_CHAN_POWER || chan == SENSOR_CHAN_ALL) { |
| ret = start_measurement(dev, true, true); |
| if (ret) { |
| return ret; |
| } |
| measurement_time = |
| K_USEC(avg_mode_samples[cfg->avg_mode] * |
| conv_time_us[MAX(cfg->conv_time_shunt, cfg->conv_time_bus)]); |
| } else { |
| return -ENOTSUP; |
| } |
| |
| for (size_t i = 0; i < MAX_RETRIES; ++i) { |
| k_sleep(measurement_time); |
| if (measurement_ready(dev)) { |
| measurement_successful = true; |
| break; |
| } |
| } |
| |
| if (!measurement_successful) { |
| LOG_ERR("Measurement timed out."); |
| return -EFAULT; |
| } |
| |
| for (size_t i = 0; i < 3; ++i) { |
| if (!cfg->enable_channel[i]) { |
| continue; |
| } |
| if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_POWER || |
| chan == SENSOR_CHAN_VOLTAGE) { |
| ret = reg_read(dev, INA3221_BUS_V1 + 2 * i, &(data->v_bus[i])); |
| if (ret) { |
| return ret; |
| } |
| data->v_bus[i] = data->v_bus[i] >> 3; |
| } |
| if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_POWER || |
| chan == SENSOR_CHAN_CURRENT) { |
| ret = reg_read(dev, INA3221_SHUNT_V1 + 2 * i, &(data->v_shunt[i])); |
| if (ret) { |
| return ret; |
| } |
| data->v_shunt[i] = data->v_shunt[i] >> 3; |
| } |
| } |
| |
| return ret; |
| } |
| |
| static int ina3221_channel_get(const struct device *dev, enum sensor_channel chan, |
| struct sensor_value *val) |
| { |
| const struct ina3221_config *cfg = dev->config; |
| struct ina3221_data *data = dev->data; |
| double result; |
| |
| switch (chan) { |
| case SENSOR_CHAN_VOLTAGE: |
| result = data->v_bus[data->selected_channel] * INA3221_BUS_VOLTAGE_LSB; |
| break; |
| case SENSOR_CHAN_CURRENT: |
| result = data->v_shunt[data->selected_channel] * INA3221_SHUNT_VOLTAGE_LSB / |
| (cfg->shunt_r[data->selected_channel] / 1000.0f); |
| break; |
| case SENSOR_CHAN_POWER: |
| result = data->v_bus[data->selected_channel] * INA3221_BUS_VOLTAGE_LSB * |
| data->v_shunt[data->selected_channel] * INA3221_SHUNT_VOLTAGE_LSB / |
| (cfg->shunt_r[data->selected_channel] / 1000.0f); |
| break; |
| default: |
| LOG_DBG("Channel not supported by device!"); |
| return -ENOTSUP; |
| } |
| |
| return sensor_value_from_double(val, result); |
| } |
| |
| static int ina3221_attr_set(const struct device *dev, enum sensor_channel chan, |
| enum sensor_attribute attr, const struct sensor_value *val) |
| { |
| ARG_UNUSED(chan); |
| |
| if (attr != SENSOR_ATTR_INA3221_SELECTED_CHANNEL) { |
| return -ENOTSUP; |
| } |
| |
| struct ina3221_data *data = dev->data; |
| |
| if (val->val1 < 1 || val->val1 > 3) { |
| return -EINVAL; |
| } |
| |
| data->selected_channel = val->val1 - 1; |
| return 0; |
| } |
| |
| static const struct sensor_driver_api ina3221_api = { |
| .sample_fetch = ina3221_sample_fetch, |
| .channel_get = ina3221_channel_get, |
| .attr_set = ina3221_attr_set, |
| }; |
| |
| #define INST_DT_INA3221(index) \ |
| static const struct ina3221_config ina3221_config_##index = { \ |
| .bus = I2C_DT_SPEC_INST_GET(index), \ |
| .avg_mode = DT_INST_PROP(index, avg_mode), \ |
| .conv_time_bus = DT_INST_PROP(index, conv_time_bus), \ |
| .conv_time_shunt = DT_INST_PROP(index, conv_time_shunt), \ |
| .enable_channel = DT_INST_PROP(index, enable_channel), \ |
| .shunt_r = DT_INST_PROP(index, shunt_resistors), \ |
| }; \ |
| static struct ina3221_data ina3221_data_##index; \ |
| \ |
| SENSOR_DEVICE_DT_INST_DEFINE(index, ina3221_init, NULL, &ina3221_data_##index, \ |
| &ina3221_config_##index, POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, \ |
| &ina3221_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(INST_DT_INA3221); |