ADD: Driver for Sensor INA226

INA226 - Bidirectional Current and Power Monitor w/ I2C
Boards Tested: mr_canhubk3

Signed-off-by: Mayank Mahajan <mayankmahajan.x@nxp.com>
This commit is contained in:
Mayank Mahajan 2024-05-02 08:55:16 +05:30 committed by Carles Cufí
commit d1687a557c
8 changed files with 495 additions and 0 deletions

View file

@ -5,6 +5,7 @@
add_subdirectory_ifdef(CONFIG_BQ274XX bq274xx)
add_subdirectory_ifdef(CONFIG_FDC2X1X fdc2x1x)
add_subdirectory_ifdef(CONFIG_INA219 ina219)
add_subdirectory_ifdef(CONFIG_INA226 ina226)
add_subdirectory_ifdef(CONFIG_INA23X ina23x)
add_subdirectory_ifdef(CONFIG_INA3221 ina3221)
add_subdirectory_ifdef(CONFIG_OPT3001 opt3001)

View file

@ -5,6 +5,7 @@
source "drivers/sensor/ti/bq274xx/Kconfig"
source "drivers/sensor/ti/fdc2x1x/Kconfig"
source "drivers/sensor/ti/ina219/Kconfig"
source "drivers/sensor/ti/ina226/Kconfig"
source "drivers/sensor/ti/ina23x/Kconfig"
source "drivers/sensor/ti/ina3221/Kconfig"
source "drivers/sensor/ti/opt3001/Kconfig"

View file

@ -0,0 +1,6 @@
# Copyright 2024 NXP
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources_ifdef(CONFIG_INA226 ina226.c)

View file

@ -0,0 +1,26 @@
# INA226 Bidirectional Current/Power Monitor
# Copyright 2024 NXP
# SPDX-License-Identifier: Apache-2.0
config INA226
bool "INA226 Current/Power Monitor"
default y
depends on DT_HAS_TI_INA226_ENABLED
select I2C
help
Enable driver for INA226 Bidirectional Current/Power Monitor.
config INA226_VSHUNT
bool "INA226 VShunt Measurement Enable"
depends on DT_HAS_TI_INA226_ENABLED
help
Enable shunt voltage measurement for INA226.
This is the actual shunt voltage measured which is scaled within the
INA226 based upon the SHUNT_CAL register. This value is useful for
determining the measurement noise or debugging the SHUNT_CAL value.
Note that enabling this option requires an extra I2C read when the
SENSOR_CHAN_ALL is selected. Hence, only enable this option if the
shunt voltage measurement is required.

View file

@ -0,0 +1,335 @@
/*
* Copyright 2024 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
/* TODO: Add functionality for Trigger. */
#define DT_DRV_COMPAT ti_ina226
#include <zephyr/logging/log.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/dt-bindings/sensor/ina226.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util_macro.h>
#include <stdint.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
/* Device register addresses. */
#define INA226_REG_CONFIG 0x00
#define INA226_REG_SHUNT_VOLT 0x01
#define INA226_REG_BUS_VOLT 0x02
#define INA226_REG_POWER 0x03
#define INA226_REG_CURRENT 0x04
#define INA226_REG_CALIB 0x05
#define INA226_REG_MASK 0x06
#define INA226_REG_ALERT 0x07
#define INA226_REG_MANUFACTURER_ID 0xFE
#define INA226_REG_DEVICE_ID 0xFF
/* Device register values. */
#define INA226_MANUFACTURER_ID 0x5449
#define INA226_DEVICE_ID 0x2260
struct ina226_data {
const struct device *dev;
int16_t current;
uint16_t bus_voltage;
uint16_t power;
#ifdef CONFIG_INA226_VSHUNT
int16_t shunt_voltage;
#endif
enum sensor_channel chan;
};
struct ina226_config {
const struct i2c_dt_spec bus;
uint16_t config;
uint32_t current_lsb;
uint16_t cal;
};
LOG_MODULE_REGISTER(INA226, CONFIG_SENSOR_LOG_LEVEL);
/** @brief Calibration scaling value (scaled by 10^-5) */
#define INA226_CAL_SCALING 512ULL
/** @brief The LSB value for the bus voltage register, in microvolts/LSB. */
#define INA226_BUS_VOLTAGE_TO_uV(x) ((x) * 1250U)
/** @brief The LSB value for the shunt voltage register, in microvolts/LSB. */
#define INA226_SHUNT_VOLTAGE_TO_uV(x) ((x) * 2500U / 1000U)
/** @brief Power scaling (need factor of 0.2) */
#define INA226_POWER_TO_uW(x) ((x) * 25ULL)
int ina226_reg_read_16(const struct i2c_dt_spec *bus, uint8_t reg, uint16_t *val)
{
uint8_t data[2];
int ret;
ret = i2c_burst_read_dt(bus, reg, data, sizeof(data));
if (ret < 0) {
return ret;
}
*val = sys_get_be16(data);
return ret;
}
int ina226_reg_write(const struct i2c_dt_spec *bus, uint8_t reg, uint16_t val)
{
uint8_t tx_buf[3];
tx_buf[0] = reg;
sys_put_be16(val, &tx_buf[1]);
return i2c_write_dt(bus, tx_buf, sizeof(tx_buf));
}
static void micro_s32_to_sensor_value(struct sensor_value *val, int32_t value_microX)
{
val->val1 = value_microX / 1000000L;
val->val2 = value_microX % 1000000L;
}
static int ina226_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val)
{
const struct ina226_data *data = dev->data;
const struct ina226_config *config = dev->config;
switch (chan) {
case SENSOR_CHAN_VOLTAGE:
micro_s32_to_sensor_value(val, INA226_BUS_VOLTAGE_TO_uV(data->bus_voltage));
break;
case SENSOR_CHAN_CURRENT:
/* see datasheet "Current and Power calculations" section */
micro_s32_to_sensor_value(val, data->current * config->current_lsb);
break;
case SENSOR_CHAN_POWER:
/* power in uW is power_reg * current_lsb * 0.2 */
micro_s32_to_sensor_value(val,
INA226_POWER_TO_uW((uint32_t)data->power * config->current_lsb));
break;
#ifdef CONFIG_INA226_VSHUNT
case SENSOR_CHAN_VSHUNT:
micro_s32_to_sensor_value(val, INA226_SHUNT_VOLTAGE_TO_uV(data->shunt_voltage));
break;
#endif /* CONFIG_INA226_VSHUNT */
default:
return -ENOTSUP;
}
return 0;
}
static int ina226_read_data(const struct device *dev)
{
struct ina226_data *data = dev->data;
const struct ina226_config *config = dev->config;
int ret;
if ((data->chan == SENSOR_CHAN_ALL) || (data->chan == SENSOR_CHAN_VOLTAGE)) {
ret = ina226_reg_read_16(&config->bus, INA226_REG_BUS_VOLT, &data->bus_voltage);
if (ret < 0) {
LOG_ERR("Failed to read bus voltage");
return ret;
}
}
if ((data->chan == SENSOR_CHAN_ALL) || (data->chan == SENSOR_CHAN_CURRENT)) {
ret = ina226_reg_read_16(&config->bus, INA226_REG_CURRENT, &data->current);
if (ret < 0) {
LOG_ERR("Failed to read current");
return ret;
}
}
if ((data->chan == SENSOR_CHAN_ALL) || (data->chan == SENSOR_CHAN_POWER)) {
ret = ina226_reg_read_16(&config->bus, INA226_REG_POWER, &data->power);
if (ret < 0) {
LOG_ERR("Failed to read power");
return ret;
}
}
#ifdef CONFIG_INA226_VSHUNT
if ((data->chan == SENSOR_CHAN_ALL) || (data->chan == SENSOR_CHAN_VSHUNT)) {
ret = ina226_reg_read_16(&config->bus, INA226_REG_SHUNT_VOLT, &data->shunt_voltage);
if (ret < 0) {
LOG_ERR("Failed to read shunt voltage");
return ret;
}
}
#endif /* CONFIG_INA226_VSHUNT */
return 0;
}
static int ina226_sample_fetch(const struct device *dev, enum sensor_channel chan)
{
struct ina226_data *data = dev->data;
if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_VOLTAGE
&& chan != SENSOR_CHAN_CURRENT && chan != SENSOR_CHAN_POWER
#ifdef CONFIG_INA226_VSHUNT
&& chan != SENSOR_CHAN_VSHUNT
#endif /* CONFIG_INA226_VSHUNT */
) {
return -ENOTSUP;
}
data->chan = chan;
return ina226_read_data(dev);
}
static int ina226_attr_set(const struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr, const struct sensor_value *val)
{
const struct ina226_config *config = dev->config;
uint16_t data = val->val1;
switch (attr) {
case SENSOR_ATTR_CONFIGURATION:
return ina226_reg_write(&config->bus, INA226_REG_CONFIG, data);
case SENSOR_ATTR_CALIBRATION:
return ina226_reg_write(&config->bus, INA226_REG_CALIB, data);
default:
LOG_ERR("INA226 attribute not supported.");
return -ENOTSUP;
}
}
static int ina226_attr_get(const struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr, struct sensor_value *val)
{
const struct ina226_config *config = dev->config;
uint16_t data;
int ret;
switch (attr) {
case SENSOR_ATTR_CONFIGURATION:
ret = ina226_reg_read_16(&config->bus, INA226_REG_CONFIG, &data);
if (ret < 0) {
return ret;
}
break;
case SENSOR_ATTR_CALIBRATION:
ret = ina226_reg_read_16(&config->bus, INA226_REG_CALIB, &data);
if (ret < 0) {
return ret;
}
break;
default:
LOG_ERR("INA226 attribute not supported.");
return -ENOTSUP;
}
val->val1 = data;
val->val2 = 0;
return 0;
}
static int ina226_calibrate(const struct device *dev)
{
const struct ina226_config *config = dev->config;
int ret;
ret = ina226_reg_write(&config->bus, INA226_REG_CALIB, config->cal);
if (ret < 0) {
return ret;
}
return 0;
}
static int ina226_init(const struct device *dev)
{
struct ina226_data *data = dev->data;
const struct ina226_config *config = dev->config;
uint16_t id;
int ret;
if (!i2c_is_ready_dt(&config->bus)) {
LOG_ERR("I2C bus %s is not ready", config->bus.bus->name);
return -ENODEV;
}
data->dev = dev;
ret = ina226_reg_read_16(&config->bus, INA226_REG_MANUFACTURER_ID, &id);
if (ret < 0) {
LOG_ERR("Failed to read manufacturer register.");
return ret;
}
if (id != INA226_MANUFACTURER_ID) {
LOG_ERR("Manufacturer ID doesn't match.");
return -ENODEV;
}
ret = ina226_reg_read_16(&config->bus, INA226_REG_DEVICE_ID, &id);
if (ret < 0) {
LOG_ERR("Failed to read device register.");
return ret;
}
if (id != INA226_DEVICE_ID) {
LOG_ERR("Device ID doesn't match.");
return -ENODEV;
}
ret = ina226_reg_write(&config->bus, INA226_REG_CONFIG, config->config);
if (ret < 0) {
LOG_ERR("Failed to write configuration register.");
return ret;
}
ret = ina226_calibrate(dev);
if (ret < 0) {
LOG_ERR("Failed to write calibration register.");
return ret;
}
return 0;
}
static const struct sensor_driver_api ina226_driver_api = {
.attr_set = ina226_attr_set,
.attr_get = ina226_attr_get,
.sample_fetch = ina226_sample_fetch,
.channel_get = ina226_channel_get,
};
#define INA226_DRIVER_INIT(inst) \
static struct ina226_data ina226_data_##inst; \
static const struct ina226_config ina226_config_##inst = { \
.bus = I2C_DT_SPEC_INST_GET(inst), \
.current_lsb = DT_INST_PROP(inst, current_lsb_microamps), \
.cal = INA226_CAL_SCALING * 10000000ULL / \
(DT_INST_PROP(inst, current_lsb_microamps) * \
DT_INST_PROP(inst, rshunt_micro_ohms)), \
.config = (DT_INST_ENUM_IDX(inst, avg_count) << 9) | \
(DT_INST_ENUM_IDX(inst, vbus_conversion_time_us) << 6) | \
(DT_INST_ENUM_IDX(inst, vshunt_conversion_time_us) << 3) | \
DT_INST_ENUM_IDX(inst, operating_mode), \
}; \
SENSOR_DEVICE_DT_INST_DEFINE(inst, \
&ina226_init, \
NULL, \
&ina226_data_##inst, \
&ina226_config_##inst, \
POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, \
&ina226_driver_api);
DT_INST_FOREACH_STATUS_OKAY(INA226_DRIVER_INIT)

View file

@ -0,0 +1,73 @@
# Copyright 2024 NXP
# SPDX-License-Identifier: Apache-2.0
description: |
TI INA226 Bidirectional Current and Power Monitor.
The <zephyr/dt-bindings/sensor/ina226.h> file should be included in the
DeviceTree as it provides macros that can be used for initializing the
configuration registers.
compatible: "ti,ina226"
include: [sensor-device.yaml, i2c-device.yaml]
properties:
avg-count:
type: int
description: |
Number of samples to average (applies to all inputs).
Default is the power-on reset value.
default: 1
enum: [1, 4, 16, 64, 128, 256, 512, 1024]
vbus-conversion-time-us:
type: int
description: |
Vbus conversion time in microseconds.
Default is the power-on reset value.
default: 1100
enum: [140, 204, 332, 588, 1100, 2116, 4156, 8244]
vshunt-conversion-time-us:
type: int
description: |
Vshunt conversion time in microseconds.
Default is the power-on reset value.
default: 1100
enum: [140, 204, 332, 588, 1100, 2116, 4156, 8244]
operating-mode:
type: string
description: |
Selects mode of operation.
Default is the power-on reset value.
default: "Shunt and Bus, Continuous"
enum:
- "Power-Down (or Shutdown)"
- "Shunt Voltage, Triggered"
- "Bus Voltage, Triggered"
- "Shunt and Bus, Triggered"
- "Power-Down (or Shutdown)"
- "Shunt Voltage, Continuous"
- "Bus Voltage, Continuous"
- "Shunt and Bus, Continuous"
current-lsb-microamps:
type: int
required: true
description: |
Current LSB value in microAmpere.
This value gives the measurement resolution for current measurement.
Formula: current-lsb [μA] = maximum expected current [μA] / 2^15
Higher resolution means lower range of current measurement, vice versa.
For example, if maximum expected current is 15 [A]:
then, current-lsb [μA] = 15000000 [μA] / 2^15 ~= 457.763 [μA].
Note: rounded values may be used for convenience, e.g. 500uA/LSB.
rshunt-micro-ohms:
type: int
required: true
description: |
Shunt resistor value in micro-ohms.

View file

@ -0,0 +1,45 @@
/*
* Copyright 2024 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_INA226_H_
#define ZEPHYR_INCLUDE_DT_BINDINGS_INA226_H_
#include <zephyr/dt-bindings/dt-util.h>
/* Reset Mode. */
#define INA226_RST_NORMAL_OPERATION 0x00
#define INA226_RST_SYSTEM_RESET 0x01
/* Averaging Mode. */
#define INA226_AVG_MODE_1 0x00
#define INA226_AVG_MODE_4 0x01
#define INA226_AVG_MODE_16 0x02
#define INA226_AVG_MODE_64 0x03
#define INA226_AVG_MODE_128 0x04
#define INA226_AVG_MODE_256 0x05
#define INA226_AVG_MODE_512 0x06
#define INA226_AVG_MODE_1024 0x07
/* Conversion time for bus and shunt voltage in micro-seconds. */
#define INA226_CONV_TIME_140 0x00
#define INA226_CONV_TIME_204 0x01
#define INA226_CONV_TIME_332 0x02
#define INA226_CONV_TIME_588 0x03
#define INA226_CONV_TIME_1100 0x04
#define INA226_CONV_TIME_2116 0x05
#define INA226_CONV_TIME_4156 0x06
#define INA226_CONV_TIME_8244 0x07
/* Operating Mode. */
#define INA226_OPER_MODE_POWER_DOWN 0x00
#define INA226_OPER_MODE_SHUNT_VOLTAGE_TRIG 0x01
#define INA226_OPER_MODE_BUS_VOLTAGE_TRIG 0x02
#define INA226_OPER_MODE_SHUNT_BUS_VOLTAGE_TRIG 0x03
#define INA226_OPER_MODE_SHUNT_VOLTAGE_CONT 0x05
#define INA226_OPER_MODE_BUS_VOLTAGE_CONT 0x06
#define INA226_OPER_MODE_SHUNT_BUS_VOLTAGE_CONT 0x07
#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_INA226_H_ */

View file

@ -22,6 +22,7 @@
#include <zephyr/dt-bindings/sensor/lis2de12.h>
#include <zephyr/dt-bindings/sensor/tmag5273.h>
#include <zephyr/dt-bindings/sensor/stts22h.h>
#include <zephyr/dt-bindings/sensor/ina226.h>
/****************************************
* PLEASE KEEP REG ADDRESSES SEQUENTIAL *
@ -1041,3 +1042,10 @@ test_i2c_tmp114: tmp114@8c {
compatible = "ti,tmp114";
reg = <0x8c>;
};
test_i2c_ina226: ina226@8d {
compatible = "ti,ina226";
reg = <0x8d>;
current-lsb-microamps = <5000>;
rshunt-micro-ohms = <500>;
};