diff options
| -rw-r--r-- | drivers/hwmon/ina238.c | 106 |
1 files changed, 101 insertions, 5 deletions
diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c index 5e43bbe8817d..5a394eeff676 100644 --- a/drivers/hwmon/ina238.c +++ b/drivers/hwmon/ina238.c @@ -6,6 +6,7 @@ * 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> @@ -107,6 +108,7 @@ #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, @@ -114,9 +116,10 @@ static const struct regmap_config ina238_regmap_config = { .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 */ @@ -137,6 +140,7 @@ struct ina238_data { static const struct ina238_config ina238_config[] = { [ina238] = { + .has_20bit_voltage_current = false, .has_energy = false, .has_power_highest = false, .temp_shift = 4, @@ -146,6 +150,7 @@ static const struct ina238_config ina238_config[] = { .temp_lsb = INA238_DIE_TEMP_LSB, }, [ina237] = { + .has_20bit_voltage_current = false, .has_energy = false, .has_power_highest = false, .temp_shift = 4, @@ -155,6 +160,7 @@ static const struct ina238_config ina238_config[] = { .temp_lsb = INA238_DIE_TEMP_LSB, }, [sq52206] = { + .has_20bit_voltage_current = false, .has_energy = true, .has_power_highest = true, .temp_shift = 0, @@ -163,6 +169,16 @@ static const struct ina238_config ina238_config[] = { .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) @@ -199,6 +215,65 @@ static int ina238_read_reg40(const struct i2c_client *client, u8 reg, u64 *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) { @@ -211,6 +286,8 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel, 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: @@ -234,6 +311,8 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel, 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: @@ -341,13 +420,25 @@ static int ina238_read_current(struct device *dev, u32 attr, long *val) 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; @@ -750,6 +841,7 @@ static int ina238_probe(struct i2c_client *client) } static const struct i2c_device_id ina238_id[] = { + { "ina228", ina228 }, { "ina237", ina237 }, { "ina238", ina238 }, { "sq52206", sq52206 }, @@ -759,6 +851,10 @@ 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 }, |
