Skip to content

Commit fd470f4

Browse files
jonrebmgroeck
authored andcommitted
hwmon: (ina238) Add support for INA228
Add support for the Texas Instruments INA228 Ultra-Precise Power/Energy/Charge Monitor. The INA228 is very similar to the INA238 but offers four bits of extra precision in the temperature, voltage and current measurement fields. It also supports energy and charge monitoring, the latter of which is not supported through this patch. While it seems in the datasheet that some constants such as LSB values differ between the 228 and the 238, they differ only for those registers where four bits of precision have been added and they differ by a factor of 16 (VBUS, VSHUNT, DIETEMP, CURRENT). Therefore, the INA238 constants are still applicable with regard to the bit of the same significance. Signed-off-by: Jonas Rebmann <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Guenter Roeck <[email protected]>
1 parent 8aee29f commit fd470f4

File tree

1 file changed

+101
-5
lines changed

1 file changed

+101
-5
lines changed

drivers/hwmon/ina238.c

Lines changed: 101 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* Copyright (C) 2021 Nathan Rossi <[email protected]>
77
*/
88

9+
#include <linux/bitops.h>
910
#include <linux/err.h>
1011
#include <linux/hwmon.h>
1112
#include <linux/i2c.h>
@@ -107,16 +108,18 @@
107108
#define INA238_DIE_TEMP_LSB 1250000 /* 125.0000 mC/lsb */
108109
#define SQ52206_BUS_VOLTAGE_LSB 3750 /* 3.75 mV/lsb */
109110
#define SQ52206_DIE_TEMP_LSB 78125 /* 7.8125 mC/lsb */
111+
#define INA228_DIE_TEMP_LSB 78125 /* 7.8125 mC/lsb */
110112

111113
static const struct regmap_config ina238_regmap_config = {
112114
.max_register = INA238_REGISTERS,
113115
.reg_bits = 8,
114116
.val_bits = 16,
115117
};
116118

117-
enum ina238_ids { ina238, ina237, sq52206 };
119+
enum ina238_ids { ina238, ina237, sq52206, ina228 };
118120

119121
struct ina238_config {
122+
bool has_20bit_voltage_current; /* vshunt, vbus and current are 20-bit fields */
120123
bool has_power_highest; /* chip detection power peak */
121124
bool has_energy; /* chip detection energy */
122125
u8 temp_shift; /* fixed parameters for temp calculate */
@@ -137,6 +140,7 @@ struct ina238_data {
137140

138141
static const struct ina238_config ina238_config[] = {
139142
[ina238] = {
143+
.has_20bit_voltage_current = false,
140144
.has_energy = false,
141145
.has_power_highest = false,
142146
.temp_shift = 4,
@@ -146,6 +150,7 @@ static const struct ina238_config ina238_config[] = {
146150
.temp_lsb = INA238_DIE_TEMP_LSB,
147151
},
148152
[ina237] = {
153+
.has_20bit_voltage_current = false,
149154
.has_energy = false,
150155
.has_power_highest = false,
151156
.temp_shift = 4,
@@ -155,6 +160,7 @@ static const struct ina238_config ina238_config[] = {
155160
.temp_lsb = INA238_DIE_TEMP_LSB,
156161
},
157162
[sq52206] = {
163+
.has_20bit_voltage_current = false,
158164
.has_energy = true,
159165
.has_power_highest = true,
160166
.temp_shift = 0,
@@ -163,6 +169,16 @@ static const struct ina238_config ina238_config[] = {
163169
.bus_voltage_lsb = SQ52206_BUS_VOLTAGE_LSB,
164170
.temp_lsb = SQ52206_DIE_TEMP_LSB,
165171
},
172+
[ina228] = {
173+
.has_20bit_voltage_current = true,
174+
.has_energy = true,
175+
.has_power_highest = false,
176+
.temp_shift = 0,
177+
.power_calculate_factor = 20,
178+
.config_default = INA238_CONFIG_DEFAULT,
179+
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
180+
.temp_lsb = INA228_DIE_TEMP_LSB,
181+
},
166182
};
167183

168184
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)
199215
return 0;
200216
}
201217

218+
static int ina238_read_field_s20(const struct i2c_client *client, u8 reg, s32 *val)
219+
{
220+
u32 regval;
221+
int err;
222+
223+
err = ina238_read_reg24(client, reg, &regval);
224+
if (err)
225+
return err;
226+
227+
/* bits 3-0 Reserved, always zero */
228+
regval >>= 4;
229+
230+
*val = sign_extend32(regval, 19);
231+
232+
return 0;
233+
}
234+
235+
static int ina228_read_shunt_voltage(struct device *dev, u32 attr, int channel,
236+
long *val)
237+
{
238+
struct ina238_data *data = dev_get_drvdata(dev);
239+
int regval;
240+
int err;
241+
242+
err = ina238_read_field_s20(data->client, INA238_SHUNT_VOLTAGE, &regval);
243+
if (err)
244+
return err;
245+
246+
/*
247+
* gain of 1 -> LSB / 4
248+
* This field has 16 bit on ina238. ina228 adds another 4 bits of
249+
* precision. ina238 conversion factors can still be applied when
250+
* dividing by 16.
251+
*/
252+
*val = (regval * INA238_SHUNT_VOLTAGE_LSB) * data->gain / (1000 * 4) / 16;
253+
return 0;
254+
}
255+
256+
static int ina228_read_bus_voltage(struct device *dev, u32 attr, int channel,
257+
long *val)
258+
{
259+
struct ina238_data *data = dev_get_drvdata(dev);
260+
int regval;
261+
int err;
262+
263+
err = ina238_read_field_s20(data->client, INA238_BUS_VOLTAGE, &regval);
264+
if (err)
265+
return err;
266+
267+
/*
268+
* gain of 1 -> LSB / 4
269+
* This field has 16 bit on ina238. ina228 adds another 4 bits of
270+
* precision. ina238 conversion factors can still be applied when
271+
* dividing by 16.
272+
*/
273+
*val = (regval * data->config->bus_voltage_lsb) / 1000 / 16;
274+
return 0;
275+
}
276+
202277
static int ina238_read_in(struct device *dev, u32 attr, int channel,
203278
long *val)
204279
{
@@ -211,6 +286,8 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel,
211286
case 0:
212287
switch (attr) {
213288
case hwmon_in_input:
289+
if (data->config->has_20bit_voltage_current)
290+
return ina228_read_shunt_voltage(dev, attr, channel, val);
214291
reg = INA238_SHUNT_VOLTAGE;
215292
break;
216293
case hwmon_in_max:
@@ -234,6 +311,8 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel,
234311
case 1:
235312
switch (attr) {
236313
case hwmon_in_input:
314+
if (data->config->has_20bit_voltage_current)
315+
return ina228_read_bus_voltage(dev, attr, channel, val);
237316
reg = INA238_BUS_VOLTAGE;
238317
break;
239318
case hwmon_in_max:
@@ -341,13 +420,25 @@ static int ina238_read_current(struct device *dev, u32 attr, long *val)
341420

342421
switch (attr) {
343422
case hwmon_curr_input:
344-
err = regmap_read(data->regmap, INA238_CURRENT, &regval);
345-
if (err < 0)
346-
return err;
423+
if (data->config->has_20bit_voltage_current) {
424+
err = ina238_read_field_s20(data->client, INA238_CURRENT, &regval);
425+
if (err)
426+
return err;
427+
} else {
428+
err = regmap_read(data->regmap, INA238_CURRENT, &regval);
429+
if (err < 0)
430+
return err;
431+
/* sign-extend */
432+
regval = (s16)regval;
433+
}
347434

348435
/* Signed register, fixed 1mA current lsb. result in mA */
349-
*val = div_s64((s16)regval * INA238_FIXED_SHUNT * data->gain,
436+
*val = div_s64((s64)regval * INA238_FIXED_SHUNT * data->gain,
350437
data->rshunt * 4);
438+
439+
/* Account for 4 bit offset */
440+
if (data->config->has_20bit_voltage_current)
441+
*val /= 16;
351442
break;
352443
default:
353444
return -EOPNOTSUPP;
@@ -750,6 +841,7 @@ static int ina238_probe(struct i2c_client *client)
750841
}
751842

752843
static const struct i2c_device_id ina238_id[] = {
844+
{ "ina228", ina228 },
753845
{ "ina237", ina237 },
754846
{ "ina238", ina238 },
755847
{ "sq52206", sq52206 },
@@ -758,6 +850,10 @@ static const struct i2c_device_id ina238_id[] = {
758850
MODULE_DEVICE_TABLE(i2c, ina238_id);
759851

760852
static const struct of_device_id __maybe_unused ina238_of_match[] = {
853+
{
854+
.compatible = "ti,ina228",
855+
.data = (void *)ina228
856+
},
761857
{
762858
.compatible = "ti,ina237",
763859
.data = (void *)ina237

0 commit comments

Comments
 (0)