diff mbox series

hwmon: Add support for the INA260 chip to the INA219 and compatibles driver

Message ID ZOILXb-1ObJRB1ck@lguegan-thinkpad (mailing list archive)
State Changes Requested
Headers show
Series hwmon: Add support for the INA260 chip to the INA219 and compatibles driver | expand

Commit Message

Loic Guegan Aug. 20, 2023, 12:47 p.m. UTC
This patch allows to retrieve current, bus voltage and power
measurements from the INA260 using its default configuration.

As the INA260 provides integrated shunt, the "ishunt" configuration
parameter is introduced. As such, proper attributes can be attached
with hwmon and shunt calibration related routnies can be ignored. In
addition, the actual INA2XX current register address differ from the
INA260 as it does not provide a shunt register. Thus, a specific
register address INA2XX_CURRENT_ISHUNT for chips with integrated
shunts is proposed.

Signed-off-by: Loic Guegan <loic.guegan@mailbox.org>
---
 drivers/hwmon/Kconfig  |  2 +-
 drivers/hwmon/ina2xx.c | 77 ++++++++++++++++++++++++++++++++++--------
 2 files changed, 64 insertions(+), 15 deletions(-)

Comments

Guenter Roeck Aug. 30, 2024, 2:40 a.m. UTC | #1
Hi,

On Sun, Aug 20, 2023 at 02:47:25PM +0200, Loic Guegan wrote:
> This patch allows to retrieve current, bus voltage and power
> measurements from the INA260 using its default configuration.
> 
> As the INA260 provides integrated shunt, the "ishunt" configuration
> parameter is introduced. As such, proper attributes can be attached
> with hwmon and shunt calibration related routnies can be ignored. In
> addition, the actual INA2XX current register address differ from the
> INA260 as it does not provide a shunt register. Thus, a specific
> register address INA2XX_CURRENT_ISHUNT for chips with integrated
> shunts is proposed.
> 
> Signed-off-by: Loic Guegan <loic.guegan@mailbox.org>

This patch will no longer apply after the series at
https://lore.kernel.org/linux-hwmon/20240830010554.1462861-1-linux@roeck-us.net/T/#t
is accepted and applied. It is also less than perfect since it does not 
support current limits. Adjusting the driver to support ina260 should be
much more straightforward after the above patches are applied. I'll send a
follow-up series of patches after I was able to test it with a real chip
(I am currently waiting for an evaluation board to arrive).

In case anyone is curious, the (untested) patches necessary to support
INA260 are available in the hwmon-staging and testing branches of
git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git

Guenter
diff mbox series

Patch

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 307477b8a371..69b75a62c1cf 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -2022,7 +2022,7 @@  config SENSORS_INA2XX
 	select REGMAP_I2C
 	help
 	  If you say yes here you get support for INA219, INA220, INA226,
-	  INA230, and INA231 power monitor chips.
+	  INA230, INA231 and INA260 power monitor chips.
 
 	  The INA2xx driver is configured for the default configuration of
 	  the part as described in the datasheet.
diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c
index cfd7efef5cdf..e0b9604ae0b7 100644
--- a/drivers/hwmon/ina2xx.c
+++ b/drivers/hwmon/ina2xx.c
@@ -45,6 +45,7 @@ 
 #define INA2XX_BUS_VOLTAGE		0x02 /* readonly */
 #define INA2XX_POWER			0x03 /* readonly */
 #define INA2XX_CURRENT			0x04 /* readonly */
+#define INA2XX_CURRENT_ISHUNT		0x01 /* readonly */
 #define INA2XX_CALIBRATION		0x05
 
 /* INA226 register definitions */
@@ -55,18 +56,22 @@ 
 /* register count */
 #define INA219_REGISTERS		6
 #define INA226_REGISTERS		8
+#define INA260_REGISTERS		8
 
 #define INA2XX_MAX_REGISTERS		8
 
 /* settings - depend on use case */
 #define INA219_CONFIG_DEFAULT		0x399F	/* PGA=8 */
 #define INA226_CONFIG_DEFAULT		0x4527	/* averages=16 */
+#define INA260_CONFIG_DEFAULT		0x6127	/* default from datasheet */
 
 /* worst case is 68.10 ms (~14.6Hz, ina219) */
 #define INA2XX_CONVERSION_RATE		15
 #define INA2XX_MAX_DELAY		69 /* worst case delay in ms */
 
 #define INA2XX_RSHUNT_DEFAULT		10000
+#define INA2XX_CURRENT_LSB_uA_DEFAULT	1250
+#define INA2XX_POWER_LSB_uW_DEFAULT	10000
 
 /* bit mask for reading the averaging setting in the configuration register */
 #define INA226_AVG_RD_MASK		0x0E00
@@ -99,10 +104,11 @@  static struct regmap_config ina2xx_regmap_config = {
 	.val_bits = 16,
 };
 
-enum ina2xx_ids { ina219, ina226 };
+enum ina2xx_ids { ina219, ina226, ina260 };
 
 struct ina2xx_config {
 	u16 config_default;
+	u8 ishunt;				/* integrated shunt? */
 	int calibration_value;
 	int registers;
 	int shunt_div;
@@ -126,6 +132,7 @@  struct ina2xx_data {
 static const struct ina2xx_config ina2xx_config[] = {
 	[ina219] = {
 		.config_default = INA219_CONFIG_DEFAULT,
+		.ishunt = 0,
 		.calibration_value = 4096,
 		.registers = INA219_REGISTERS,
 		.shunt_div = 100,
@@ -135,6 +142,7 @@  static const struct ina2xx_config ina2xx_config[] = {
 	},
 	[ina226] = {
 		.config_default = INA226_CONFIG_DEFAULT,
+		.ishunt = 0,
 		.calibration_value = 2048,
 		.registers = INA226_REGISTERS,
 		.shunt_div = 400,
@@ -142,6 +150,14 @@  static const struct ina2xx_config ina2xx_config[] = {
 		.bus_voltage_lsb = 1250,
 		.power_lsb_factor = 25,
 	},
+	[ina260] = {
+		.config_default = INA260_CONFIG_DEFAULT,
+		.ishunt = 1,
+		.registers = INA260_REGISTERS,
+		.shunt_div = 1,
+		.bus_voltage_shift = 0,
+		.bus_voltage_lsb = 1250,
+	},
 };
 
 /*
@@ -201,7 +217,10 @@  static int ina2xx_init(struct ina2xx_data *data)
 	if (ret < 0)
 		return ret;
 
-	return ina2xx_calibrate(data);
+	if (!data->config->ishunt)
+		return ina2xx_calibrate(data);
+
+	return 0;
 }
 
 static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval)
@@ -213,6 +232,10 @@  static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval)
 
 	for (retry = 5; retry; retry--) {
 
+		/* Shunt register is used for current register when using integrated shunt */
+		if (data->config->ishunt && reg == INA2XX_CURRENT)
+			reg = INA2XX_CURRENT_ISHUNT;
+
 		ret = regmap_read(data->regmap, reg, regval);
 		if (ret < 0)
 			return ret;
@@ -227,7 +250,7 @@  static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval)
 		 * We do that extra read of the calibration register if there
 		 * is some hint of a chip reset.
 		 */
-		if (*regval == 0) {
+		if (*regval == 0 && !data->config->ishunt) {
 			unsigned int cal;
 
 			ret = regmap_read(data->regmap, INA2XX_CALIBRATION,
@@ -594,6 +617,18 @@  static const struct attribute_group ina2xx_group = {
 	.attrs = ina2xx_attrs,
 };
 
+/* integrated shunt devices attributes */
+static struct attribute *ina2xx_ishunt_attrs[] = {
+	&sensor_dev_attr_in1_input.dev_attr.attr,
+	&sensor_dev_attr_curr1_input.dev_attr.attr,
+	&sensor_dev_attr_power1_input.dev_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group ina2xx_ishunt_group = {
+	.attrs = ina2xx_ishunt_attrs,
+};
+
 static struct attribute *ina226_attrs[] = {
 	&sensor_dev_attr_in0_crit.dev_attr.attr,
 	&sensor_dev_attr_in0_lcrit.dev_attr.attr,
@@ -637,16 +672,19 @@  static int ina2xx_probe(struct i2c_client *client)
 	data->config = &ina2xx_config[chip];
 	mutex_init(&data->config_lock);
 
-	if (of_property_read_u32(dev->of_node, "shunt-resistor", &val) < 0) {
-		struct ina2xx_platform_data *pdata = dev_get_platdata(dev);
+	/* Devices with no integrated shunt */
+	if (!data->config->ishunt) {
+		if (of_property_read_u32(dev->of_node, "shunt-resistor", &val) < 0) {
+			struct ina2xx_platform_data *pdata = dev_get_platdata(dev);
 
-		if (pdata)
-			val = pdata->shunt_uohms;
-		else
-			val = INA2XX_RSHUNT_DEFAULT;
-	}
+			if (pdata)
+				val = pdata->shunt_uohms;
+			else
+				val = INA2XX_RSHUNT_DEFAULT;
+		}
 
-	ina2xx_set_shunt(data, val);
+		ina2xx_set_shunt(data, val);
+	}
 
 	ina2xx_regmap_config.max_register = data->config->registers;
 
@@ -666,9 +704,15 @@  static int ina2xx_probe(struct i2c_client *client)
 		return -ENODEV;
 	}
 
-	data->groups[group++] = &ina2xx_group;
-	if (chip == ina226)
-		data->groups[group++] = &ina226_group;
+	if (data->config->ishunt) {
+		data->groups[group++] = &ina2xx_ishunt_group;
+		data->current_lsb_uA = INA2XX_CURRENT_LSB_uA_DEFAULT;
+		data->power_lsb_uW = INA2XX_POWER_LSB_uW_DEFAULT;
+	} else {
+		data->groups[group++] = &ina2xx_group;
+		if (chip == ina226)
+			data->groups[group++] = &ina226_group;
+	}
 
 	hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
 							   data, data->groups);
@@ -687,6 +731,7 @@  static const struct i2c_device_id ina2xx_id[] = {
 	{ "ina226", ina226 },
 	{ "ina230", ina226 },
 	{ "ina231", ina226 },
+	{ "ina260", ina260 },
 	{ }
 };
 MODULE_DEVICE_TABLE(i2c, ina2xx_id);
@@ -712,6 +757,10 @@  static const struct of_device_id __maybe_unused ina2xx_of_match[] = {
 		.compatible = "ti,ina231",
 		.data = (void *)ina226
 	},
+	{
+		.compatible = "ti,ina260",
+		.data = (void *)ina260
+	},
 	{ },
 };
 MODULE_DEVICE_TABLE(of, ina2xx_of_match);