blob: 89dac7d0ff835de4897efe404a35c791a0029f0b [file] [log] [blame]
/*
* Copyright 2021 The Chromium OS Authors
* Copyright 2021 Grinn
* Copyright 2025 Nova Dynamics LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "ina230.h"
#include <zephyr/logging/log.h>
#include <zephyr/drivers/sensor.h>
LOG_MODULE_REGISTER(INA230, CONFIG_SENSOR_LOG_LEVEL);
/** @brief Calibration scaling value (value scaled by 100000) */
#define INA230_CAL_SCALING 512ULL
/** @brief The LSB value for the bus voltage register, in microvolts/LSB. */
#define INA230_BUS_VOLTAGE_UV_LSB 1250U
#define INA236_BUS_VOLTAGE_UV_LSB 1600U
/** @brief The scaling for the power register. */
#define INA230_POWER_SCALING 25
#define INA236_POWER_SCALING 32
INA2XX_REG_DEFINE(ina230_config, INA230_REG_CONFIG, 16);
INA2XX_REG_DEFINE(ina230_cal, INA230_REG_CALIB, 16);
#if DT_HAS_COMPAT_STATUS_OKAY(ti_ina230)
INA2XX_CHANNEL_DEFINE(ina230_current, INA230_REG_CURRENT, 16, 0, 1, 1);
INA2XX_CHANNEL_DEFINE(ina230_bus_voltage, INA230_REG_BUS_VOLT, 16, 0, INA230_BUS_VOLTAGE_UV_LSB, 1);
INA2XX_CHANNEL_DEFINE(ina230_power, INA230_REG_POWER, 16, 0, INA230_POWER_SCALING, 1);
static struct ina2xx_channels ina230_channels = {
.voltage = &ina230_bus_voltage,
.current = &ina230_current,
.power = &ina230_power,
};
#endif /* ti_ina230 */
#if DT_HAS_COMPAT_STATUS_OKAY(ti_ina236)
INA2XX_CHANNEL_DEFINE(ina236_current, INA230_REG_CURRENT, 16, 0, 1, 1);
INA2XX_CHANNEL_DEFINE(ina236_bus_voltage, INA230_REG_BUS_VOLT, 16, 0, INA236_BUS_VOLTAGE_UV_LSB, 1);
INA2XX_CHANNEL_DEFINE(ina236_power, INA230_REG_POWER, 16, 0, INA236_POWER_SCALING, 1);
static struct ina2xx_channels ina236_channels = {
.voltage = &ina236_bus_voltage,
.current = &ina236_current,
.power = &ina236_power,
};
#endif /* ti_ina236 */
static int ina230_set_feature_mask(const struct device *dev, const struct sensor_value *val)
{
const struct ina2xx_config *config = dev->config;
uint16_t data = val->val1;
return ina2xx_reg_write(&config->bus, INA230_REG_MASK, data);
}
static int ina230_set_alert(const struct device *dev, const struct sensor_value *val)
{
const struct ina2xx_config *config = dev->config;
uint16_t data = val->val1;
return ina2xx_reg_write(&config->bus, INA230_REG_ALERT, data);
}
static int ina230_attr_set(const struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr, const struct sensor_value *val)
{
if (attr == SENSOR_ATTR_FEATURE_MASK) {
return ina230_set_feature_mask(dev, val);
}
if (attr == SENSOR_ATTR_ALERT) {
return ina230_set_alert(dev, val);
}
return ina2xx_attr_set(dev, chan, attr, val);
}
static int ina230_get_feature_mask(const struct device *dev, struct sensor_value *val)
{
const struct ina2xx_config *config = dev->config;
uint16_t data;
int ret;
ret = ina2xx_reg_read_16(&config->bus, INA230_REG_MASK, &data);
if (ret < 0) {
return ret;
}
val->val1 = data;
val->val2 = 0;
return 0;
}
static int ina230_get_alert(const struct device *dev, struct sensor_value *val)
{
const struct ina2xx_config *config = dev->config;
uint16_t data;
int ret;
ret = ina2xx_reg_read_16(&config->bus, INA230_REG_ALERT, &data);
if (ret < 0) {
return ret;
}
val->val1 = data;
val->val2 = 0;
return 0;
}
static int ina230_attr_get(const struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr, struct sensor_value *val)
{
if (attr == SENSOR_ATTR_FEATURE_MASK) {
return ina230_get_feature_mask(dev, val);
}
if (attr == SENSOR_ATTR_ALERT) {
return ina230_get_alert(dev, val);
}
return ina2xx_attr_get(dev, chan, attr, val);
}
static int ina230_init_trigger(const struct device *dev)
{
if (IS_ENABLED(CONFIG_INA230_TRIGGER)) {
const struct ina230_config *const config = dev->config;
const struct i2c_dt_spec *bus = &config->common.bus;
int ret;
if (!config->trig_enabled) {
return 0;
}
ret = ina230_trigger_mode_init(dev);
if (ret < 0) {
LOG_ERR("Failed to init trigger mode\n");
return ret;
}
ret = ina2xx_reg_write(bus, INA230_REG_ALERT, config->alert_limit);
if (ret < 0) {
LOG_ERR("Failed to write alert register!");
return ret;
}
ret = ina2xx_reg_write(bus, INA230_REG_MASK, config->mask);
if (ret < 0) {
LOG_ERR("Failed to write mask register!");
return ret;
}
}
return 0;
}
static int ina230_init(const struct device *dev)
{
int ret;
ret = ina2xx_init(dev);
if (ret < 0) {
return ret;
}
ret = ina230_init_trigger(dev);
if (ret < 0) {
return ret;
}
return 0;
}
static DEVICE_API(sensor, ina230_driver_api) = {
.attr_set = ina230_attr_set,
.attr_get = ina230_attr_get,
#ifdef CONFIG_INA230_TRIGGER
.trigger_set = ina230_trigger_set,
#endif
.sample_fetch = ina2xx_sample_fetch,
.channel_get = ina2xx_channel_get,
};
#ifdef CONFIG_INA230_TRIGGER
#define INA230_CFG_IRQ(inst) \
.trig_enabled = true, .mask = DT_INST_PROP(inst, mask), \
.alert_limit = DT_INST_PROP(inst, alert_limit), \
.alert_gpio = GPIO_DT_SPEC_INST_GET(inst, alert_gpios)
#else
#define INA230_CFG_IRQ(inst)
#endif /* CONFIG_INA230_TRIGGER */
#define INA230_DT_CONFIG(inst) \
(DT_INST_PROP_OR(inst, high_precision, 0) << 12) | \
(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, adc_mode))
#define INA230_DT_CAL(inst) \
(uint16_t)(((INA230_CAL_SCALING * 10000000ULL) / \
((uint64_t)DT_INST_PROP(inst, current_lsb_microamps) * \
DT_INST_PROP(inst, rshunt_micro_ohms))) >> \
(DT_INST_PROP_OR(inst, high_precision, 0) << 1))
#define INA230_DRIVER_INIT(inst) \
static struct ina230_data ina230_data_##inst; \
static const struct ina230_config ina230_config_##inst = { \
.common = { \
.bus = I2C_DT_SPEC_INST_GET(inst), \
.current_lsb = DT_INST_PROP(inst, current_lsb_microamps), \
.config = INA230_DT_CONFIG(inst), \
.cal = INA230_DT_CAL(inst), \
.id_reg = NULL, \
.config_reg = &ina230_config, \
.adc_config_reg = NULL, \
.cal_reg = &ina230_cal, \
.channels = &ina230_channels, \
}, \
.uv_lsb = INA230_BUS_VOLTAGE_UV_LSB, \
.power_scale = INA230_POWER_SCALING, \
COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, alert_gpios), \
(INA230_CFG_IRQ(inst)), ())}; \
SENSOR_DEVICE_DT_INST_DEFINE(inst, &ina230_init, NULL, \
&ina230_data_##inst, &ina230_config_##inst, \
POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &ina230_driver_api);
#define INA236_DRIVER_INIT(inst) \
static struct ina230_data ina236_data_##inst; \
static const struct ina230_config ina236_config_##inst = { \
.common = { \
.bus = I2C_DT_SPEC_INST_GET(inst), \
.current_lsb = DT_INST_PROP(inst, current_lsb_microamps), \
.config = INA230_DT_CONFIG(inst), \
.cal = INA230_DT_CAL(inst), \
.id_reg = NULL, \
.config_reg = &ina230_config, \
.adc_config_reg = NULL, \
.cal_reg = &ina230_cal, \
.channels = &ina236_channels, \
}, \
.uv_lsb = INA236_BUS_VOLTAGE_UV_LSB, \
.power_scale = INA236_POWER_SCALING, \
COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, alert_gpios), \
(INA230_CFG_IRQ(inst)), ())}; \
SENSOR_DEVICE_DT_INST_DEFINE(inst, &ina230_init, NULL, \
&ina236_data_##inst, &ina236_config_##inst, \
POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &ina230_driver_api);
#define DT_DRV_COMPAT ti_ina230
DT_INST_FOREACH_STATUS_OKAY(INA230_DRIVER_INIT)
#undef DT_DRV_COMPAT
#define DT_DRV_COMPAT ti_ina236
DT_INST_FOREACH_STATUS_OKAY(INA236_DRIVER_INIT)
#undef DT_DRV_COMPAT