* Copyright (C) 2021 Nathan Rossi <nathan.rossi@digi.com>
*/
+#include <linux/bitops.h>
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/i2c.h>
#define INA238_DIE_TEMP_LSB 1250000 /* 125.0000 mC/lsb */
#define SQ52206_BUS_VOLTAGE_LSB 3750 /* 3.75 mV/lsb */
#define SQ52206_DIE_TEMP_LSB 78125 /* 7.8125 mC/lsb */
+#define INA228_DIE_TEMP_LSB 78125 /* 7.8125 mC/lsb */
static const struct regmap_config ina238_regmap_config = {
.max_register = INA238_REGISTERS,
.val_bits = 16,
};
-enum ina238_ids { ina238, ina237, sq52206 };
+enum ina238_ids { ina238, ina237, sq52206, ina228 };
struct ina238_config {
+ bool has_20bit_voltage_current; /* vshunt, vbus and current are 20-bit fields */
bool has_power_highest; /* chip detection power peak */
bool has_energy; /* chip detection energy */
u8 temp_shift; /* fixed parameters for temp calculate */
static const struct ina238_config ina238_config[] = {
[ina238] = {
+ .has_20bit_voltage_current = false,
.has_energy = false,
.has_power_highest = false,
.temp_shift = 4,
.temp_lsb = INA238_DIE_TEMP_LSB,
},
[ina237] = {
+ .has_20bit_voltage_current = false,
.has_energy = false,
.has_power_highest = false,
.temp_shift = 4,
.temp_lsb = INA238_DIE_TEMP_LSB,
},
[sq52206] = {
+ .has_20bit_voltage_current = false,
.has_energy = true,
.has_power_highest = true,
.temp_shift = 0,
.bus_voltage_lsb = SQ52206_BUS_VOLTAGE_LSB,
.temp_lsb = SQ52206_DIE_TEMP_LSB,
},
+ [ina228] = {
+ .has_20bit_voltage_current = true,
+ .has_energy = true,
+ .has_power_highest = false,
+ .temp_shift = 0,
+ .power_calculate_factor = 20,
+ .config_default = INA238_CONFIG_DEFAULT,
+ .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
+ .temp_lsb = INA228_DIE_TEMP_LSB,
+ },
};
static int ina238_read_reg24(const struct i2c_client *client, u8 reg, u32 *val)
return 0;
}
+static int ina238_read_field_s20(const struct i2c_client *client, u8 reg, s32 *val)
+{
+ u32 regval;
+ int err;
+
+ err = ina238_read_reg24(client, reg, ®val);
+ if (err)
+ return err;
+
+ /* bits 3-0 Reserved, always zero */
+ regval >>= 4;
+
+ *val = sign_extend32(regval, 19);
+
+ return 0;
+}
+
+static int ina228_read_shunt_voltage(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct ina238_data *data = dev_get_drvdata(dev);
+ int regval;
+ int err;
+
+ err = ina238_read_field_s20(data->client, INA238_SHUNT_VOLTAGE, ®val);
+ if (err)
+ return err;
+
+ /*
+ * gain of 1 -> LSB / 4
+ * This field has 16 bit on ina238. ina228 adds another 4 bits of
+ * precision. ina238 conversion factors can still be applied when
+ * dividing by 16.
+ */
+ *val = (regval * INA238_SHUNT_VOLTAGE_LSB) * data->gain / (1000 * 4) / 16;
+ return 0;
+}
+
+static int ina228_read_bus_voltage(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct ina238_data *data = dev_get_drvdata(dev);
+ int regval;
+ int err;
+
+ err = ina238_read_field_s20(data->client, INA238_BUS_VOLTAGE, ®val);
+ if (err)
+ return err;
+
+ /*
+ * gain of 1 -> LSB / 4
+ * This field has 16 bit on ina238. ina228 adds another 4 bits of
+ * precision. ina238 conversion factors can still be applied when
+ * dividing by 16.
+ */
+ *val = (regval * data->config->bus_voltage_lsb) / 1000 / 16;
+ return 0;
+}
+
static int ina238_read_in(struct device *dev, u32 attr, int channel,
long *val)
{
case 0:
switch (attr) {
case hwmon_in_input:
+ if (data->config->has_20bit_voltage_current)
+ return ina228_read_shunt_voltage(dev, attr, channel, val);
reg = INA238_SHUNT_VOLTAGE;
break;
case hwmon_in_max:
case 1:
switch (attr) {
case hwmon_in_input:
+ if (data->config->has_20bit_voltage_current)
+ return ina228_read_bus_voltage(dev, attr, channel, val);
reg = INA238_BUS_VOLTAGE;
break;
case hwmon_in_max:
switch (attr) {
case hwmon_curr_input:
- err = regmap_read(data->regmap, INA238_CURRENT, ®val);
- if (err < 0)
- return err;
+ if (data->config->has_20bit_voltage_current) {
+ err = ina238_read_field_s20(data->client, INA238_CURRENT, ®val);
+ if (err)
+ return err;
+ } else {
+ err = regmap_read(data->regmap, INA238_CURRENT, ®val);
+ if (err < 0)
+ return err;
+ /* sign-extend */
+ regval = (s16)regval;
+ }
/* Signed register, fixed 1mA current lsb. result in mA */
- *val = div_s64((s16)regval * INA238_FIXED_SHUNT * data->gain,
+ *val = div_s64((s64)regval * INA238_FIXED_SHUNT * data->gain,
data->rshunt * 4);
+
+ /* Account for 4 bit offset */
+ if (data->config->has_20bit_voltage_current)
+ *val /= 16;
break;
default:
return -EOPNOTSUPP;
}
static const struct i2c_device_id ina238_id[] = {
+ { "ina228", ina228 },
{ "ina237", ina237 },
{ "ina238", ina238 },
{ "sq52206", sq52206 },
MODULE_DEVICE_TABLE(i2c, ina238_id);
static const struct of_device_id __maybe_unused ina238_of_match[] = {
+ {
+ .compatible = "ti,ina228",
+ .data = (void *)ina228
+ },
{
.compatible = "ti,ina237",
.data = (void *)ina237