drivers: sensor: Add driver for TI INA23x

This driver supports the TI INA230 and INA231 Bidirectional Current
and Power Monitors. The devices work on the I2C interface and are
created from DT nodes with a compatible property matching "ti,ina23x".

The following datasheets were referenced while developing the driver:
https://www.ti.com/product/INA230
https://www.ti.com/product/INA231

Twister passed:
twister -T tests/drivers/build_all/sensor/

Testing was performed on the stm32g071b_disco board with the following:
Load:    ~170 ohms
Voltage: 5V

Measured Values:
Voltage: 5.1 V
Current: 0.032 A
Power:   0.157 W

Signed-off-by: Sam Hurst <sbh1187@gmail.com>
This commit is contained in:
Sam Hurst 2021-05-03 10:36:56 -07:00 committed by Christopher Friedt
commit 1382d64ed5
13 changed files with 678 additions and 0 deletions

View file

@ -37,6 +37,7 @@ add_subdirectory_ifdef(CONFIG_IIS2DLPC iis2dlpc)
add_subdirectory_ifdef(CONFIG_IIS2ICLX iis2iclx)
add_subdirectory_ifdef(CONFIG_IIS2MDC iis2mdc)
add_subdirectory_ifdef(CONFIG_IIS3DHHC iis3dhhc)
add_subdirectory_ifdef(CONFIG_INA23X ina23x)
add_subdirectory_ifdef(CONFIG_ISL29035 isl29035)
add_subdirectory_ifdef(CONFIG_ISM330DHCX ism330dhcx)
add_subdirectory_ifdef(CONFIG_LIS2DH lis2dh)

View file

@ -112,6 +112,8 @@ source "drivers/sensor/iis2mdc/Kconfig"
source "drivers/sensor/iis3dhhc/Kconfig"
source "drivers/sensor/ina23x/Kconfig"
source "drivers/sensor/isl29035/Kconfig"
source "drivers/sensor/ism330dhcx/Kconfig"

View file

@ -0,0 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources(ina23x.c)
zephyr_library_sources_ifdef(CONFIG_INA23X_TRIGGER ina23x_trigger.c)

View file

@ -0,0 +1,19 @@
# Copyright 2021 The Chromium OS Authors
#
# SPDX-License-Identifier: Apache-2.0
config INA23X
bool "INA23X Current and Power Monitor"
depends on I2C
help
Enable driver for INA23X Current and Power Monitor.
if INA23X
config INA23X_TRIGGER
bool "Trigger mode"
help
Set to enable trigger mode using gpio interrupt, where
interrupts are configured to line ALERT PIN.
endif # INA23X

View file

@ -0,0 +1,372 @@
/*
* Copyright 2021 The Chromium OS Authors
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT ti_ina23x
#include <sys/byteorder.h>
#include <device.h>
#include <drivers/gpio.h>
#include <drivers/i2c.h>
#include <logging/log.h>
#include <drivers/sensor.h>
#include "ina23x.h"
LOG_MODULE_REGISTER(INA23X, CONFIG_SENSOR_LOG_LEVEL);
/**
* @brief Macro used to test if the current's sign bit is set
*/
#define CURRENT_SIGN_BIT 0x8000
/**
* @brief Macro used to check if the current's LSB is 1mA
*/
#define CURRENT_LSB_1MA 1
/**
* @brief Macro for creating the INA23X calibration value
* CALIB = (5120 / (current_lsb * rshunt))
* NOTE: The 5120 value is a fixed value internal to the
* INA23X that is used to ensure scaling is properly
* maintained.
*
* @param current_lsb Value of the Current register LSB in milliamps
* @param rshunt Shunt resistor value in milliohms
*/
#define INA23X_CALIB(current_lsb, rshunt) (5120 / ((current_lsb) * (rshunt)))
/**
* @brief Macro to convert raw Bus voltage to millivolts when current_lsb is
* set to 1mA.
*
* reg value read from bus voltage register
* clsb value of current_lsb
*/
#define INA23X_BUS_MV(reg) ((reg) * 125 / 100)
/**
* @brief Macro to convert raw power value to milliwatts when current_lsb is
* set to 1mA.
*
* reg value read from power register
* clsb value of current_lsb
*/
#define INA23X_POW_MW(reg) ((reg) * 25)
static int ina23x_reg_read(const struct device *dev, uint8_t reg, int16_t *val)
{
const struct ina23x_config *const config = dev->config;
uint8_t data[2];
int ret;
ret = i2c_burst_read(config->bus, config->i2c_slv_addr, reg, data, 2);
if (ret < 0) {
return ret;
}
*val = sys_get_be16(data);
return ret;
}
static int ina23x_reg_write(const struct device *dev, uint8_t reg, uint16_t val)
{
const struct ina23x_config *const config = dev->config;
uint8_t tx_buf[3];
tx_buf[0] = reg;
sys_put_be16(val, &tx_buf[1]);
return i2c_write(config->bus, tx_buf, sizeof(tx_buf), config->i2c_slv_addr);
}
/**
* @brief sensor value get
*
* @return 0 for success
* @return -ENOTSUP for unsupported channels
*/
static int ina23x_channel_get(const struct device *dev,
enum sensor_channel chan,
struct sensor_value *val)
{
struct ina23x_data *ina23x = dev->data;
const struct ina23x_config *const config = dev->config;
switch (chan) {
case SENSOR_CHAN_VOLTAGE:
if (config->current_lsb == CURRENT_LSB_1MA) {
val->val1 = INA23X_BUS_MV(ina23x->bus_voltage) / 1000U;
val->val2 = (INA23X_BUS_MV(ina23x->bus_voltage) % 1000) * 1000;
} else {
val->val1 = ina23x->bus_voltage;
val->val2 = 0;
}
break;
case SENSOR_CHAN_CURRENT:
if (config->current_lsb == CURRENT_LSB_1MA) {
/**
* If current is negative, convert it to a
* magnitude and return the negative of that
* magnitude.
*/
if (ina23x->current & CURRENT_SIGN_BIT) {
uint16_t current_mag = (~ina23x->current + 1);
val->val1 = -(current_mag / 1000U);
val->val2 = -(current_mag % 1000) * 1000;
} else {
val->val1 = ina23x->current / 1000U;
val->val2 = (ina23x->current % 1000) * 1000;
}
} else {
val->val1 = ina23x->current;
val->val2 = 0;
}
break;
case SENSOR_CHAN_POWER:
if (config->current_lsb == CURRENT_LSB_1MA) {
val->val1 = INA23X_POW_MW(ina23x->power) / 1000U;
val->val2 = (INA23X_POW_MW(ina23x->power) % 1000) * 1000;
} else {
val->val1 = ina23x->power;
val->val2 = 0;
}
break;
default:
return -ENOTSUP;
}
return 0;
}
/**
* @brief sensor sample fetch
*
* @return 0 for success
* @return -ENOTSUP for unsupported channels
*/
static int ina23x_sample_fetch(const struct device *dev,
enum sensor_channel chan)
{
struct ina23x_data *ina23x = dev->data;
int ret;
switch (chan) {
case SENSOR_CHAN_VOLTAGE:
ret = ina23x_reg_read(dev, INA23X_REG_BUS_VOLT, &ina23x->bus_voltage);
if (ret < 0) {
LOG_ERR("Failed to read bus voltage");
return ret;
}
break;
case SENSOR_CHAN_CURRENT:
ret = ina23x_reg_read(dev, INA23X_REG_CURRENT, &ina23x->current);
if (ret < 0) {
LOG_ERR("Failed to read current");
return ret;
}
break;
case SENSOR_CHAN_POWER:
ret = ina23x_reg_read(dev, INA23X_REG_POWER, &ina23x->power);
if (ret < 0) {
LOG_ERR("Failed to read power");
return ret;
}
break;
default:
return -ENOTSUP;
}
return 0;
}
/**
* @brief sensor attribute set
*
* @return 0 for success
* @return -ENOTSUP for unsupported channels
* @return -EIO for i2c write failure
*/
static int ina23x_attr_set(const struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr,
const struct sensor_value *val)
{
uint16_t data = val->val1;
switch (attr) {
case SENSOR_ATTR_CONFIGURATION:
return ina23x_reg_write(dev, INA23X_REG_CONFIG, data);
case SENSOR_ATTR_CALIBRATION:
return ina23x_reg_write(dev, INA23X_REG_CALIB, data);
case SENSOR_ATTR_FEATURE_MASK:
return ina23x_reg_write(dev, INA23X_REG_MASK, data);
case SENSOR_ATTR_ALERT:
return ina23x_reg_write(dev, INA23X_REG_ALERT, data);
default:
LOG_ERR("INA23X attribute not supported.");
return -ENOTSUP;
}
}
/**
* @brief sensor attribute get
*
* @return 0 for success
* @return -ENOTSUP for unsupported channels
* @return -EIO for i2c read failure
*/
static int ina23x_attr_get(const struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr,
struct sensor_value *val)
{
uint16_t data;
int ret;
switch (attr) {
case SENSOR_ATTR_CONFIGURATION:
ret = ina23x_reg_read(dev, INA23X_REG_CONFIG, &data);
if (ret < 0) {
return ret;
}
break;
case SENSOR_ATTR_CALIBRATION:
ret = ina23x_reg_read(dev, INA23X_REG_CALIB, &data);
if (ret < 0) {
return ret;
}
break;
case SENSOR_ATTR_FEATURE_MASK:
ret = ina23x_reg_read(dev, INA23X_REG_MASK, &data);
if (ret < 0) {
return ret;
}
break;
case SENSOR_ATTR_ALERT:
ret = ina23x_reg_read(dev, INA23X_REG_ALERT, &data);
if (ret < 0) {
return ret;
}
break;
default:
LOG_ERR("INA23X attribute not supported.");
return -ENOTSUP;
}
val->val1 = data;
val->val2 = 0;
return 0;
}
/**
* @brief Initialize the INA23x
*
* @return 0 for success
* @return -EINVAL on error
*/
static int ina23x_init(const struct device *dev)
{
const struct ina23x_config *const config = dev->config;
uint16_t cal;
int ret;
if (!device_is_ready(config->bus)) {
LOG_ERR("Device %s is not ready", config->bus->name);
return -ENODEV;
}
ret = ina23x_reg_write(dev, INA23X_REG_CONFIG, config->config);
if (ret < 0) {
LOG_ERR("Failed to write configuration register!");
return ret;
}
cal = INA23X_CALIB(config->current_lsb, config->rshunt);
ret = ina23x_reg_write(dev, INA23X_REG_CALIB, cal);
if (ret < 0) {
LOG_ERR("Failed to write calibration register!");
return ret;
}
#ifdef CONFIG_INA23X_TRIGGER
if (config->trig_enabled) {
ret = ina23x_trigger_mode_init(dev);
if (ret < 0) {
LOG_ERR("Failed to init trigger mode\n");
return ret;
}
ret = ina23x_reg_write(dev, INA23X_REG_ALERT,
config->alert_limit);
if (ret < 0) {
LOG_ERR("Failed to write alert register!");
return ret;
}
ret = ina23x_reg_write(dev, INA23X_REG_MASK, config->mask);
if (ret < 0) {
LOG_ERR("Failed to write mask register!");
return ret;
}
}
#endif /* CONFIG_INA23X_TRIGGER */
return 0;
}
static const struct sensor_driver_api ina23x_driver_api = {
.attr_set = ina23x_attr_set,
.attr_get = ina23x_attr_get,
#ifdef CONFIG_INA23X_TRIGGER
.trigger_set = ina23x_trigger_set,
#endif
.sample_fetch = ina23x_sample_fetch,
.channel_get = ina23x_channel_get,
};
BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) > 0,
"No compatible ina23x instances found");
#ifdef CONFIG_INA23X_TRIGGER
#define INA23X_CFG_IRQ(inst) \
.trig_enabled = true, \
.mask = DT_INST_PROP(inst, mask), \
.alert_limit = DT_INST_PROP(inst, alert_limit), \
.gpio_alert = GPIO_DT_SPEC_INST_GET(inst, irq_gpios)
#else
#define INA23X_CFG_IRQ(inst)
#endif /* CONFIG_INA23X_TRIGGER */
#define INA23X_DRIVER_INIT(inst) \
static struct ina23x_data drv_data_##inst; \
static const struct ina23x_config drv_config_##inst = { \
.bus = DEVICE_DT_GET(DT_INST_BUS(inst)), \
.i2c_slv_addr = DT_INST_REG_ADDR(inst), \
.config = DT_INST_PROP(inst, config), \
.current_lsb = DT_INST_PROP(inst, current_lsb), \
.rshunt = DT_INST_PROP(inst, rshunt), \
COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, irq_gpios), \
(INA23X_CFG_IRQ(inst)), ()) \
}; \
DEVICE_DT_INST_DEFINE(inst, \
&ina23x_init, \
NULL, \
&drv_data_##inst, \
&drv_config_##inst, \
POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, \
&ina23x_driver_api);
DT_INST_FOREACH_STATUS_OKAY(INA23X_DRIVER_INIT)

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2021 The Chromium OS Authors
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_DRIVERS_SENSOR_INA23X_H_
#define ZEPHYR_DRIVERS_SENSOR_INA23X_H_
#include <drivers/gpio.h>
#define INA23X_REG_CONFIG 0x00
#define INA23X_REG_SHUNT_VOLT 0x01
#define INA23X_REG_BUS_VOLT 0x02
#define INA23X_REG_POWER 0x03
#define INA23X_REG_CURRENT 0x04
#define INA23X_REG_CALIB 0x05
#define INA23X_REG_MASK 0x06
#define INA23X_REG_ALERT 0x07
struct ina23x_data {
const struct device *dev;
uint16_t current;
uint16_t bus_voltage;
uint16_t power;
#ifdef CONFIG_INA23X_TRIGGER
const struct device *gpio;
struct gpio_callback gpio_cb;
struct k_work work;
sensor_trigger_handler_t handler_alert;
#endif /* CONFIG_INA23X_TRIGGER */
};
struct ina23x_config {
const struct device *bus;
const uint16_t i2c_slv_addr;
uint16_t config;
uint16_t current_lsb;
uint16_t rshunt;
#ifdef CONFIG_INA23X_TRIGGER
bool trig_enabled;
uint16_t mask;
const struct gpio_dt_spec gpio_alert;
uint16_t alert_limit;
#endif /* CONFIG_INA23X_TRIGGER */
};
int ina23x_trigger_mode_init(const struct device *dev);
int ina23x_trigger_set(const struct device *dev,
const struct sensor_trigger *trig,
sensor_trigger_handler_t handler);
#endif /* ZEPHYR_DRIVERS_SENSOR_INA23X_H_ */

View file

@ -0,0 +1,77 @@
/*
* Copyright 2021 The Chromium OS Authors
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <drivers/sensor.h>
#include <drivers/gpio.h>
#include <logging/log.h>
#include "ina23x.h"
LOG_MODULE_DECLARE(INA23X, CONFIG_SENSOR_LOG_LEVEL);
static void ina23x_gpio_callback(const struct device *port,
struct gpio_callback *cb, uint32_t pin)
{
struct sensor_trigger ina23x_trigger;
struct ina23x_data *ina23x = CONTAINER_OF(cb, struct ina23x_data, gpio_cb);
const struct device *dev = (const struct device *)ina23x->dev;
ARG_UNUSED(port);
ARG_UNUSED(pin);
ARG_UNUSED(cb);
if (ina23x->handler_alert) {
ina23x_trigger.type = SENSOR_TRIG_DATA_READY;
ina23x->handler_alert(dev, &ina23x_trigger);
}
}
int ina23x_trigger_set(const struct device *dev,
const struct sensor_trigger *trig,
sensor_trigger_handler_t handler)
{
struct ina23x_data *ina23x = dev->data;
ARG_UNUSED(trig);
ina23x->handler_alert = handler;
return 0;
}
int ina23x_trigger_mode_init(const struct device *dev)
{
struct ina23x_data *ina23x = dev->data;
const struct ina23x_config *config = dev->config;
int ret;
/* setup alert gpio interrupt */
if (!device_is_ready(config->gpio_alert.port)) {
LOG_ERR("Alert GPIO device not ready");
return -ENODEV;
}
ina23x->dev = dev;
ret = gpio_pin_configure_dt(&config->gpio_alert, GPIO_INPUT);
if (ret < 0) {
LOG_ERR("Could not configure gpio");
return ret;
}
gpio_init_callback(&ina23x->gpio_cb,
ina23x_gpio_callback,
BIT(config->gpio_alert.pin));
ret = gpio_add_callback(config->gpio_alert.port, &ina23x->gpio_cb);
if (ret < 0) {
LOG_ERR("Could not set gpio callback");
return ret;
}
return gpio_pin_interrupt_configure_dt(&config->gpio_alert,
GPIO_INT_EDGE_BOTH);
}

View file

@ -0,0 +1,56 @@
#
# Copyright 2021 The Chromium OS Authors
#
# SPDX-License-Identifier: Apache-2.0
#
description: |
TI INA230 and INA231 Bidirectional Current and Power Monitor.
The <include/dt-bindings/sensor/ina23x.h> file should be included
in the DeviceTree and it provides Macros that can be used for
initializing the Configuration register.
compatible: "ti,ina23x"
include: i2c-device.yaml
properties:
config:
type: int
required: true
description: Configuration register
current_lsb:
type: int
required: true
description: |
Value of Current LSB in milliamps. When set to 1mA,
Current is read in A, Bus Voltage in V, Shunt
Voltage in V, and Power in mW. Any other value
results in Current, Voltage, and Power registers
being read in counts.
rshunt:
type: int
required: true
description: Shunt resistor value in milliohms
mask:
type: int
required: false
default: 0
# default all alert sources to disabled
description: Mask register, default matches the power-on reset value
alert_limit:
type: int
required: false
default: 0
# default alert limit is 0V
description: Alert register, default matches the power-on reset value
irq-gpios:
type: phandle-array
required: false
description: IRQ Alert pin

View file

@ -121,6 +121,9 @@ enum sensor_channel {
SENSOR_CHAN_VOLTAGE,
/** Current, in amps **/
SENSOR_CHAN_CURRENT,
/** Power in watts **/
SENSOR_CHAN_POWER,
/** Resistance , in Ohm **/
SENSOR_CHAN_RESISTANCE,
@ -294,6 +297,14 @@ enum sensor_attribute {
* algorithms to calibrate itself on a certain axis, or all of them.
*/
SENSOR_ATTR_CALIB_TARGET,
/** Configure the operating modes of a sensor. */
SENSOR_ATTR_CONFIGURATION,
/** Set a calibration value needed by a sensor. */
SENSOR_ATTR_CALIBRATION,
/** Enable/disable sensor features */
SENSOR_ATTR_FEATURE_MASK,
/** Alert threshold or alert enable/disable */
SENSOR_ATTR_ALERT,
/**
* Number of all common sensor attributes.

View file

@ -0,0 +1,67 @@
/*
* Copyright 2021 The Chromium OS Authors
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_INA23X_H_
#define ZEPHYR_INCLUDE_DT_BINDINGS_INA23X_H_
#include <dt-bindings/dt-util.h>
/* Mask/Enable bits that asserts the ALERT pin */
#define INA23X_SHUNT_VOLTAGE_OVER BIT(15)
#define INA23X_SHUNT_VOLTAGE_UNDER BIT(14)
#define INA23X_BUS_VOLTAGE_OVER BIT(13)
#define INA23X_BUS_VOLTAGE_UNDER BIT(12)
#define INA23X_OVER_LIMIT_POWER BIT(11)
#define INA23X_CONVERSION_READY BIT(10)
#define INA23X_ALERT_FUNCTION_FLAG BIT(4)
#define INA23X_CONVERSION_READY_FLAG BIT(3)
#define INA23X_MATH_OVERFLOW_FLAG BIT(2)
#define INA23X_ALERT_POLARITY BIT(1)
#define INA23X_ALERT_LATCH_ENABLE BIT(0)
/* Operating Mode */
#define INA23X_OPER_MODE_POWER_DOWN 0x00
#define INA23X_OPER_MODE_SHUNT_VOLTAGE_TRIG 0x01
#define INA23X_OPER_MODE_BUS_VOLTAGE_TRIG 0x02
#define INA23X_OPER_MODE_SHUNT_BUS_VOLTAGE_TRIG 0x03
#define INA23X_OPER_MODE_SHUNT_VOLTAGE_CONT 0x05
#define INA23X_OPER_MODE_BUS_VOLTAGE_CONT 0x06
#define INA23X_OPER_MODE_SHUNT_BUS_VOLTAGE_CONT 0x07
/* Conversion time for bus and shunt in micro-seconds */
#define INA23X_CONV_TIME_140 0x00
#define INA23X_CONV_TIME_204 0x01
#define INA23X_CONV_TIME_332 0x02
#define INA23X_CONV_TIME_588 0x03
#define INA23X_CONV_TIME_1100 0x04
#define INA23X_CONV_TIME_2116 0x05
#define INA23X_CONV_TIME_4156 0x06
#define INA23X_CONV_TIME_8244 0x07
/* Averaging Mode */
#define INA23X_AVG_MODE_1 0x00
#define INA23X_AVG_MODE_4 0x01
#define INA23X_AVG_MODE_16 0x02
#define INA23X_AVG_MODE_64 0x03
#define INA23X_AVG_MODE_128 0x04
#define INA23X_AVG_MODE_256 0x05
#define INA23X_AVG_MODE_512 0x06
#define INA23X_AVG_MODE_1024 0x07
/**
* @brief Macro for creating the INA23X configuration value
*
* @param mode Operating mode.
* @param svct Conversion time for shunt voltage.
* @param bvct Conversion time for bus voltage.
* @param avg Averaging mode.
*/
#define INA23X_CONFIG(mode, \
svct, \
bvct, \
avg) \
(((avg) << 9) | ((bvct) << 6) | ((svct) << 3) | (mode))
#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_INA23X_H_ */

View file

@ -657,3 +657,15 @@ test_i2c_lm75: lm75@4e {
label = "LM75";
reg = <0x4e>;
};
test_i2c_ina23x: ina23x@4f {
compatible = "ti,ina23x";
label = "INA23X";
reg = <0x4f>;
config = <0>;
current_lsb = <1>;
rshunt = <0>;
mask = <0>;
alert_limit = <0>;
irq-gpios = <&test_gpio 0 0>;
};

View file

@ -94,3 +94,4 @@ CONFIG_TMP112=y
CONFIG_TMP116=y
CONFIG_VCNL4040=y
CONFIG_VL53L0X=y
CONFIG_INA23X=y

View file

@ -36,3 +36,4 @@ CONFIG_STTS751_TRIGGER_OWN_THREAD=y
CONFIG_SX9500_TRIGGER_OWN_THREAD=y
CONFIG_TMP007_TRIGGER_OWN_THREAD=y
CONFIG_VCNL4040_TRIGGER_OWN_THREAD=y
CONFIG_INA23X_TRIGGER=y