#include <linux/module.h>
#include <linux/of.h>
#include <linux/regmap.h>
+#include <linux/util_macros.h>
/* INA238 register definitions */
#define INA238_CONFIG 0x0
#define INA238_DIAG_ALERT_BUSUL BIT(3)
#define INA238_DIAG_ALERT_POL BIT(2)
+/* INA238_ADC_CONFIG register field masks and shifts */
+#define INA238_ADC_CONFIG_MODE_MASK GENMASK(15, 12)
+#define INA238_ADC_CONFIG_VBUSCT_SHIFT 9
+#define INA238_ADC_CONFIG_VBUSCT_MASK GENMASK(11, 9)
+#define INA238_ADC_CONFIG_VSHCT_SHIFT 6
+#define INA238_ADC_CONFIG_VSHCT_MASK GENMASK(8, 6)
+#define INA238_ADC_CONFIG_VTCT_SHIFT 3
+#define INA238_ADC_CONFIG_VTCT_MASK GENMASK(5, 3)
+#define INA238_ADC_CONFIG_AVG_SHIFT 0
+#define INA238_ADC_CONFIG_AVG_MASK GENMASK(2, 0)
+
#define INA238_REGISTERS 0x20
#define INA238_RSHUNT_DEFAULT 2500 /* uOhm */
.val_bits = 16,
};
+/* Lookup table for conversion times in usec for INA238 family */
+static const u16 ina238_conv_time[] = {
+ 50, 84, 150, 280, 540, 1052, 2074, 4120,
+};
+
+/* Lookup table for conversion times in usec for SQ52206 */
+static const u16 sq52206_conv_time[] = {
+ 66, 118, 310, 566, 1070, 2090, 4140, 8230,
+};
+
+/* Lookup table for number of samples used in averaging mode */
+static const int ina238_avg_samples[] = {
+ 1, 4, 16, 64, 128, 256, 512, 1024,
+};
+
enum ina238_ids { ina228, ina237, ina238, ina700, ina780, sq52206 };
struct ina238_config {
u32 power_calculate_factor; /* fixed parameter for power calculation, from datasheet */
u32 bus_voltage_lsb; /* bus voltage LSB, in nV */
int current_lsb; /* current LSB, in uA */
+ const u16 *conv_time; /* conversion time lookup table */
};
struct ina238_data {
int current_lsb; /* current LSB, in uA */
int power_lsb; /* power LSB, in uW */
int energy_lsb; /* energy LSB, in uJ */
+ u16 adc_config; /* cached ADC_CONFIG register value */
};
static const struct ina238_config ina238_config[] = {
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 16,
+ .conv_time = ina238_conv_time,
},
[ina237] = {
.has_20bit_voltage_current = false,
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 12,
+ .conv_time = ina238_conv_time,
},
[ina238] = {
.has_20bit_voltage_current = false,
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 12,
+ .conv_time = ina238_conv_time,
},
[ina700] = {
.has_20bit_voltage_current = false,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 12,
.current_lsb = 480,
+ .conv_time = ina238_conv_time,
},
[ina780] = {
.has_20bit_voltage_current = false,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 12,
.current_lsb = 2400,
+ .conv_time = ina238_conv_time,
},
[sq52206] = {
.has_20bit_voltage_current = false,
.config_default = SQ52206_CONFIG_DEFAULT,
.bus_voltage_lsb = SQ52206_BUS_VOLTAGE_LSB,
.temp_resolution = 16,
+ .conv_time = sq52206_conv_time,
},
};
return 0;
}
+/* Converting ADC_CONFIG register value to update_interval in usec */
+static inline u32 ina238_reg_to_interval_us(struct ina238_data *data)
+{
+ const u16 *ct = data->config->conv_time;
+ u32 vbusct = ct[(data->adc_config & INA238_ADC_CONFIG_VBUSCT_MASK) >>
+ INA238_ADC_CONFIG_VBUSCT_SHIFT];
+ u32 vshct = ct[(data->adc_config & INA238_ADC_CONFIG_VSHCT_MASK) >>
+ INA238_ADC_CONFIG_VSHCT_SHIFT];
+ u32 vtct = ct[(data->adc_config & INA238_ADC_CONFIG_VTCT_MASK) >>
+ INA238_ADC_CONFIG_VTCT_SHIFT];
+
+ return vbusct + vshct + vtct;
+}
+
+static inline u32 ina238_samples(struct ina238_data *data)
+{
+ return ina238_avg_samples[(data->adc_config & INA238_ADC_CONFIG_AVG_MASK) >>
+ INA238_ADC_CONFIG_AVG_SHIFT];
+}
+
+/* Converting update_interval in msec to a single conversion time in usec */
+static inline u32 ina238_interval_ms_to_conv_time(long interval, u32 samples)
+{
+ u64 interval_us;
+
+ interval = clamp_val(interval, 0, INT_MAX / 1000);
+ interval_us = (u64)interval * 1000;
+
+ /*
+ * update_interval reports the ADC cycle time including averaging.
+ * The target per-field conversion time is interval_us / (samples * 3).
+ */
+ return DIV_ROUND_CLOSEST_ULL(interval_us, samples * 3);
+}
+
+static int ina238_read_chip(struct device *dev, u32 attr, long *val)
+{
+ struct ina238_data *data = dev_get_drvdata(dev);
+
+ switch (attr) {
+ case hwmon_chip_samples:
+ *val = ina238_samples(data);
+ return 0;
+ case hwmon_chip_update_interval:
+ /* Return in msec */
+ *val = DIV_ROUND_CLOSEST(ina238_reg_to_interval_us(data) *
+ ina238_samples(data), 1000);
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ina238_write_chip(struct device *dev, u32 attr, long val)
+{
+ struct ina238_data *data = dev_get_drvdata(dev);
+ u16 adc_config;
+ int idx, ret;
+
+ switch (attr) {
+ case hwmon_chip_samples:
+ idx = find_closest(val, ina238_avg_samples,
+ ARRAY_SIZE(ina238_avg_samples));
+ adc_config = (data->adc_config & ~INA238_ADC_CONFIG_AVG_MASK) |
+ (idx << INA238_ADC_CONFIG_AVG_SHIFT);
+ ret = regmap_write(data->regmap, INA238_ADC_CONFIG, adc_config);
+ if (ret)
+ return ret;
+ data->adc_config = adc_config;
+ return 0;
+ case hwmon_chip_update_interval:
+ val = ina238_interval_ms_to_conv_time(val, ina238_samples(data));
+ idx = find_closest(val, data->config->conv_time,
+ ARRAY_SIZE(ina238_conv_time));
+ adc_config = (data->adc_config &
+ ~(INA238_ADC_CONFIG_VBUSCT_MASK |
+ INA238_ADC_CONFIG_VSHCT_MASK |
+ INA238_ADC_CONFIG_VTCT_MASK)) |
+ ((u16)idx << INA238_ADC_CONFIG_VBUSCT_SHIFT) |
+ ((u16)idx << INA238_ADC_CONFIG_VSHCT_SHIFT) |
+ ((u16)idx << INA238_ADC_CONFIG_VTCT_SHIFT);
+ ret = regmap_write(data->regmap, INA238_ADC_CONFIG, adc_config);
+ if (ret)
+ return ret;
+ data->adc_config = adc_config;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
static int ina238_read_in(struct device *dev, u32 attr, int channel,
long *val)
{
u32 attr, int channel, long *val)
{
switch (type) {
+ case hwmon_chip:
+ return ina238_read_chip(dev, attr, val);
case hwmon_in:
return ina238_read_in(dev, attr, channel, val);
case hwmon_curr:
u32 attr, int channel, long val)
{
switch (type) {
+ case hwmon_chip:
+ return ina238_write_chip(dev, attr, val);
case hwmon_in:
return ina238_write_in(dev, attr, channel, val);
case hwmon_curr:
bool has_energy = data->config->has_energy;
switch (type) {
+ case hwmon_chip:
+ switch (attr) {
+ case hwmon_chip_samples:
+ case hwmon_chip_update_interval:
+ return 0644;
+ default:
+ return 0;
+ }
case hwmon_in:
switch (attr) {
case hwmon_in_input:
HWMON_I_MIN | HWMON_I_MIN_ALARM)
static const struct hwmon_channel_info * const ina238_info[] = {
+ HWMON_CHANNEL_INFO(chip,
+ HWMON_C_SAMPLES | HWMON_C_UPDATE_INTERVAL),
HWMON_CHANNEL_INFO(in,
/* 0: shunt voltage */
INA238_HWMON_IN_CONFIG,
}
/* Setup ADC_CONFIG register */
- ret = regmap_write(data->regmap, INA238_ADC_CONFIG,
- INA238_ADC_CONFIG_DEFAULT);
+ data->adc_config = INA238_ADC_CONFIG_DEFAULT;
+ ret = regmap_write(data->regmap, INA238_ADC_CONFIG, data->adc_config);
if (ret < 0) {
dev_err(dev, "error configuring the device: %d\n", ret);
return -ENODEV;