hwmon: Add driver for TI INA233 Current and Power Monitor
authorLeo Yang <leo.yang.sy0@gmail.com>
Thu, 16 Jan 2025 08:59:42 +0000 (16:59 +0800)
committerGuenter Roeck <linux@roeck-us.net>
Tue, 18 Mar 2025 15:03:37 +0000 (08:03 -0700)
Driver for Texas Instruments INA233 Current and Power Monitor
With I2C-, SMBus-, and PMBus-Compatible Interface

Signed-off-by: Leo Yang <leo.yang.sy0@gmail.com>
Link: https://lore.kernel.org/r/20250116085939.1235598-3-leo.yang.sy0@gmail.com
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Documentation/hwmon/ina233.rst [new file with mode: 0644]
Documentation/hwmon/index.rst
MAINTAINERS
drivers/hwmon/pmbus/Kconfig
drivers/hwmon/pmbus/Makefile
drivers/hwmon/pmbus/ina233.c [new file with mode: 0644]

diff --git a/Documentation/hwmon/ina233.rst b/Documentation/hwmon/ina233.rst
new file mode 100644 (file)
index 0000000..4232316
--- /dev/null
@@ -0,0 +1,75 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Kernel driver ina233
+====================
+
+Supported chips:
+
+  * TI INA233
+
+    Prefix: 'ina233'
+
+  * Datasheet
+
+    Publicly available at the TI website : https://www.ti.com/lit/ds/symlink/ina233.pdf
+
+Author: Leo Yang <leo.yang.sy0@gmail.com>
+
+Usage Notes
+-----------
+
+The shunt resistor value can be configured by a device tree property;
+see Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml for details.
+
+
+Description
+-----------
+
+This driver supports hardware monitoring for TI INA233.
+
+The driver is a client driver to the core PMBus driver. Please see
+Documentation/hwmon/pmbus.rst for details on PMBus client drivers.
+
+The driver provides the following attributes for input voltage:
+
+**in1_input**
+
+**in1_label**
+
+**in1_max**
+
+**in1_max_alarm**
+
+**in1_min**
+
+**in1_min_alarm**
+
+The driver provides the following attributes for shunt voltage:
+
+**in2_input**
+
+**in2_label**
+
+The driver provides the following attributes for output voltage:
+
+**in3_input**
+
+**in3_label**
+
+**in3_alarm**
+
+The driver provides the following attributes for output current:
+
+**curr1_input**
+
+**curr1_label**
+
+**curr1_max**
+
+**curr1_max_alarm**
+
+The driver provides the following attributes for input power:
+
+**power1_input**
+
+**power1_label**
index ef86489bb47cb5a9eeada6aadbfc59540ccc2b32..bd491bc37ba40aefc0cfdc177bcdc8c922af3f02 100644 (file)
@@ -91,6 +91,7 @@ Hardware Monitoring Kernel Drivers
    ibmpowernv
    ina209
    ina2xx
+   ina233
    ina238
    ina3221
    inspur-ipsps1
index c85ce9deb397642cfacae3d811326402da198888..af4e39a803f60ae4877fd7599556b1a667c383b5 100644 (file)
@@ -11331,6 +11331,13 @@ L:     linux-fbdev@vger.kernel.org
 S:     Orphan
 F:     drivers/video/fbdev/imsttfb.c
 
+INA233 HARDWARE MONITOR DRIVERS
+M:     Leo Yang <leo.yang.sy0@gmail.com>
+L:     linux-hwmon@vger.kernel.org
+S:     Maintained
+F:     Documentation/hwmon/ina233.rst
+F:     drivers/hwmon/pmbus/ina233.c
+
 INDEX OF FURTHER KERNEL DOCUMENTATION
 M:     Carlos Bilbao <carlos.bilbao@kernel.org>
 S:     Maintained
index 6ce68dd333690c407311e256db3f3284bbb48861..c9b3c314998234e639db1770c18c6f75fa037d25 100644 (file)
@@ -133,6 +133,15 @@ config SENSORS_DPS920AB
          This driver can also be built as a module. If so, the module will
          be called dps920ab.
 
+config SENSORS_INA233
+       tristate "Texas Instruments INA233 and compatibles"
+       help
+         If you say yes here you get hardware monitoring support for Texas
+         Instruments INA233.
+
+         This driver can also be built as a module. If so, the module will
+         be called ina233.
+
 config SENSORS_INSPUR_IPSPS
        tristate "INSPUR Power System Power Supply"
        help
index c7eb7739b7f861c5c105582b57ad16937649d4cf..56f128c4653e3087ecd3c2fd859bb0bd9ff10a3d 100644 (file)
@@ -15,6 +15,7 @@ obj-$(CONFIG_SENSORS_DELTA_AHE50DC_FAN) += delta-ahe50dc-fan.o
 obj-$(CONFIG_SENSORS_FSP_3Y)   += fsp-3y.o
 obj-$(CONFIG_SENSORS_IBM_CFFPS)        += ibm-cffps.o
 obj-$(CONFIG_SENSORS_DPS920AB) += dps920ab.o
+obj-$(CONFIG_SENSORS_INA233)   += ina233.o
 obj-$(CONFIG_SENSORS_INSPUR_IPSPS) += inspur-ipsps.o
 obj-$(CONFIG_SENSORS_IR35221)  += ir35221.o
 obj-$(CONFIG_SENSORS_IR36021)  += ir36021.o
diff --git a/drivers/hwmon/pmbus/ina233.c b/drivers/hwmon/pmbus/ina233.c
new file mode 100644 (file)
index 0000000..dde1e16
--- /dev/null
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for ina233
+ *
+ * Copyright (c) 2025 Leo Yang
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include "pmbus.h"
+
+#define MFR_READ_VSHUNT 0xd1
+#define MFR_CALIBRATION 0xd4
+
+#define INA233_MAX_CURRENT_DEFAULT     32768000 /* uA */
+#define INA233_RSHUNT_DEFAULT          2000 /* uOhm */
+
+#define MAX_M_VAL 32767
+
+static void calculate_coef(int *m, int *R, u32 current_lsb, int power_coef)
+{
+       u64 scaled_m;
+       int scale_factor = 0;
+       int scale_coef = 1;
+
+       /*
+        * 1000000 from Current_LSB A->uA .
+        * scale_coef is for scaling up to minimize rounding errors,
+        * If there is no decimal information, no need to scale.
+        */
+       if (1000000 % current_lsb) {
+               /* Scaling to keep integer precision */
+               scale_factor = -3;
+               scale_coef = 1000;
+       }
+
+       /*
+        * Unit Conversion (Current_LSB A->uA) and use scaling(scale_factor)
+        * to keep integer precision.
+        * Formulae referenced from spec.
+        */
+       scaled_m = div64_u64(1000000 * scale_coef, (u64)current_lsb * power_coef);
+
+       /* Maximize while keeping it bounded.*/
+       while (scaled_m > MAX_M_VAL) {
+               scaled_m = div_u64(scaled_m, 10);
+               scale_factor++;
+       }
+       /* Scale up only if fractional part exists. */
+       while (scaled_m * 10 < MAX_M_VAL && scale_coef != 1) {
+               scaled_m *= 10;
+               scale_factor--;
+       }
+
+       *m = scaled_m;
+       *R = scale_factor;
+}
+
+static int ina233_read_word_data(struct i2c_client *client, int page,
+                                int phase, int reg)
+{
+       int ret;
+
+       switch (reg) {
+       case PMBUS_VIRT_READ_VMON:
+               ret = pmbus_read_word_data(client, 0, 0xff, MFR_READ_VSHUNT);
+
+               /* Adjust returned value to match VIN coefficients */
+               /* VIN: 1.25 mV VSHUNT: 2.5 uV LSB */
+               ret = DIV_ROUND_CLOSEST(ret * 25, 12500);
+               break;
+       default:
+               ret = -ENODATA;
+               break;
+       }
+       return ret;
+}
+
+static int ina233_probe(struct i2c_client *client)
+{
+       struct device *dev = &client->dev;
+       int ret, m, R;
+       u32 rshunt;
+       u32 max_current;
+       u32 current_lsb;
+       u16 calibration;
+       struct pmbus_driver_info *info;
+
+       info = devm_kzalloc(dev, sizeof(struct pmbus_driver_info),
+                           GFP_KERNEL);
+       if (!info)
+               return -ENOMEM;
+
+       info->pages = 1;
+       info->format[PSC_VOLTAGE_IN] = direct;
+       info->format[PSC_VOLTAGE_OUT] = direct;
+       info->format[PSC_CURRENT_OUT] = direct;
+       info->format[PSC_POWER] = direct;
+       info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_INPUT
+               | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
+               | PMBUS_HAVE_POUT
+               | PMBUS_HAVE_VMON | PMBUS_HAVE_STATUS_VMON;
+       info->m[PSC_VOLTAGE_IN] = 8;
+       info->R[PSC_VOLTAGE_IN] = 2;
+       info->m[PSC_VOLTAGE_OUT] = 8;
+       info->R[PSC_VOLTAGE_OUT] = 2;
+       info->read_word_data = ina233_read_word_data;
+
+       /* If INA233 skips current/power, shunt-resistor and current-lsb aren't needed. */
+       /* read rshunt value (uOhm) */
+       ret = device_property_read_u32(dev, "shunt-resistor", &rshunt);
+       if (ret) {
+               if (ret != -EINVAL)
+                       return dev_err_probe(dev, ret, "Shunt resistor property read fail.\n");
+               rshunt = INA233_RSHUNT_DEFAULT;
+       }
+       if (!rshunt)
+               return dev_err_probe(dev, -EINVAL,
+                                    "Shunt resistor cannot be zero.\n");
+
+       /* read Maximum expected current value (uA) */
+       ret = device_property_read_u32(dev, "ti,maximum-expected-current-microamp", &max_current);
+       if (ret) {
+               if (ret != -EINVAL)
+                       return dev_err_probe(dev, ret,
+                                            "Maximum expected current property read fail.\n");
+               max_current = INA233_MAX_CURRENT_DEFAULT;
+       }
+       if (max_current < 32768)
+               return dev_err_probe(dev, -EINVAL,
+                                    "Maximum expected current cannot less then 32768.\n");
+
+       /* Calculate Current_LSB according to the spec formula */
+       current_lsb = max_current / 32768;
+
+       /* calculate current coefficient */
+       calculate_coef(&m, &R, current_lsb, 1);
+       info->m[PSC_CURRENT_OUT] = m;
+       info->R[PSC_CURRENT_OUT] = R;
+
+       /* calculate power coefficient */
+       calculate_coef(&m, &R, current_lsb, 25);
+       info->m[PSC_POWER] = m;
+       info->R[PSC_POWER] = R;
+
+       /* write MFR_CALIBRATION register, Apply formula from spec with unit scaling. */
+       calibration = div64_u64(5120000000ULL, (u64)rshunt * current_lsb);
+       if (calibration > 0x7FFF)
+               return dev_err_probe(dev, -EINVAL,
+                                    "Product of Current_LSB %u and shunt resistor %u too small, MFR_CALIBRATION reg exceeds 0x7FFF.\n",
+                                    current_lsb, rshunt);
+       ret = i2c_smbus_write_word_data(client, MFR_CALIBRATION, calibration);
+       if (ret < 0)
+               return dev_err_probe(dev, ret, "Unable to write calibration.\n");
+
+       dev_dbg(dev, "power monitor %s (Rshunt = %u uOhm, Current_LSB = %u uA/bit)\n",
+               client->name, rshunt, current_lsb);
+
+       return pmbus_do_probe(client, info);
+}
+
+static const struct i2c_device_id ina233_id[] = {
+       {"ina233", 0},
+       {}
+};
+MODULE_DEVICE_TABLE(i2c, ina233_id);
+
+static const struct of_device_id __maybe_unused ina233_of_match[] = {
+       { .compatible = "ti,ina233" },
+       {}
+};
+MODULE_DEVICE_TABLE(of, ina233_of_match);
+
+static struct i2c_driver ina233_driver = {
+       .driver = {
+                  .name = "ina233",
+                  .of_match_table = of_match_ptr(ina233_of_match),
+       },
+       .probe = ina233_probe,
+       .id_table = ina233_id,
+};
+
+module_i2c_driver(ina233_driver);
+
+MODULE_AUTHOR("Leo Yang <leo.yang.sy0@gmail.com>");
+MODULE_DESCRIPTION("PMBus driver for INA233 and compatible chips");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");