From 5fc58123c7389c17141e9c224d5912e94efa4f53 Mon Sep 17 00:00:00 2001 From: Johnny Liu Date: Thu, 24 Apr 2025 08:50:10 +0000 Subject: [PATCH] hwmon: ina232: introduce ina232 oot driver Example of information reading from hwmon sysfs interface: generic_atv_int:/sys/class/hwmon/hwmon0 # grep "" *_input in0_input:1345 in1_input:24092 curr1_input:268 power1_input:6437376 generic_atv_int:/sys/class/hwmon/hwmon0 # grep "" shunt_resistor 5000 The unit of each node follows the ina2xx ABI specification: https://elixir.bootlin.com/linux/v6.1/source/Documentation/hwmon/ina2xx.rst Bug 5060737 Change-Id: I6b9066206c95fc32fd90506b1233d1d5feb6c854 Signed-off-by: Johnny Liu Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3348715 GVS: buildbot_gerritrpt Reviewed-by: Ankita Garg --- BUILD.bazel | 1 + drivers/hwmon/Makefile | 5 + drivers/hwmon/ina232.c | 360 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 366 insertions(+) create mode 100644 drivers/hwmon/ina232.c diff --git a/BUILD.bazel b/BUILD.bazel index 1af69d6e..e1deb57a 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -77,6 +77,7 @@ kernel_module( "drivers/gpu/host1x/host1x.ko", "drivers/gpu/host1x-nvhost/host1x-nvhost.ko", "drivers/hwmon/f75308.ko", + "drivers/hwmon/ina232.ko", "drivers/i2c/busses/i2c-nvvrs11.ko", "drivers/i2c/busses/i2c-tegra-slave-byte.ko", "drivers/media/i2c/max9295.ko", diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 2f164d40..293eb73f 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -3,4 +3,9 @@ ifdef CONFIG_I2C obj-m += f75308.o + +ifdef CONFIG_TEGRA_SYSTEM_TYPE_ACK +obj-m += ina232.o +endif + endif diff --git a/drivers/hwmon/ina232.c b/drivers/hwmon/ina232.c new file mode 100644 index 00000000..78c9af50 --- /dev/null +++ b/drivers/hwmon/ina232.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* INA232 register definitions */ +#define INA232_CONFIG 0x0 +#define INA232_SHUNT_VOLTAGE 0x1 +#define INA232_BUS_VOLTAGE 0x2 +#define INA232_POWER 0x3 +#define INA232_CURRENT 0x4 +#define INA232_CALIBRATION 0x5 +#define INA232_ENABLE 0x6 +#define INA232_ALERT_LIMIT 0x7 +#define INA232_REGISTERS 0x8 + +#define INA232_CONFIG_ADCRANGE BIT(12) + +#define INA232_RSHUNT_DEFAULT 500 /* uOhm */ +#define INA232_SHUNT_GAIN_DEFAULT 1 +#define INA232_MAX_EXPECTED_CURRENT_DEFAULT 5000000 /* 5A */ + +/* Default configuration of device on reset. */ +#define INA232_RESET_VALUE 0x8000 +#define INA232_CONFIG_DEFAULT 0x4d27 + +#define INA232_BUS_VOLTAGE_LSB 1600 /* 1.6 mV/lsb */ + +static struct regmap_config ina232_regmap_config = { + .max_register = INA232_REGISTERS, + .reg_bits = 8, + .val_bits = 16, +}; + +struct ina232_data { + struct i2c_client *client; + struct mutex config_lock; + struct regmap *regmap; + u32 max_expected_current; + u32 current_lsb; + u32 rshunt; + int gain; +}; + +/* Extra attribute groups */ +static ssize_t ina232_shunt_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ina232_data *data = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%u\n", data->rshunt); +} + +static SENSOR_DEVICE_ATTR_RO(shunt_resistor, ina232_shunt, 0); + +static struct attribute *ina232_attrs[] = { + &sensor_dev_attr_shunt_resistor.dev_attr.attr, + NULL, +}; +ATTRIBUTE_GROUPS(ina232); + +static int ina232_read_in(struct device *dev, int channel, long *val) +{ + struct ina232_data *data = dev_get_drvdata(dev); + long shunt_volt; + int regval; + int err; + + switch (channel) { + // Shunt voltage reading + case 0: + err = regmap_read(data->regmap, INA232_SHUNT_VOLTAGE, ®val); + if (err < 0) + return err; + + shunt_volt = (long)regval; + + // Handle negative value in two's complement + if (regval & BIT(15)) { + shunt_volt--; + shunt_volt = ~shunt_volt; + shunt_volt &= 0xffff; + } + + // Multiple LSB based on ADCRANGE to get nV + if (data->gain == 4) + shunt_volt *= 625; + else + shunt_volt *= 2500; + + // Handle negative value in two's complement + if (regval & BIT(15)) + *val = -(shunt_volt / 1000000); + else + *val = shunt_volt / 1000000; + + break; + // Bus voltage reading + case 1: + err = regmap_read(data->regmap, INA232_BUS_VOLTAGE, ®val); + if (err < 0) + return err; + + *val = regval * INA232_BUS_VOLTAGE_LSB; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int ina232_read_current(struct device *dev, long *val) +{ + struct ina232_data *data = dev_get_drvdata(dev); + long curr; + int regval; + int err; + + // Current reading directly from register and convert to uA + err = regmap_read(data->regmap, INA232_CURRENT, ®val); + if (err < 0) + return err; + + curr = (long)regval; + + // Handle negative value in two's complement + if (regval & BIT(15)) { + curr--; + curr = ~curr; + curr &= 0xffff; + *val = -(curr * data->current_lsb); + } else { + *val = curr * data->current_lsb; + } + + return 0; +} + +static int ina232_read_power(struct device *dev, long *val) +{ + struct ina232_data *data = dev_get_drvdata(dev); + int regval; + int err; + + // Power reading directly from register in uW + err = regmap_read(data->regmap, INA232_POWER, ®val); + if (err < 0) + return err; + + *val = 32 * regval * data->current_lsb; + + return 0; +} + +static int ina232_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_in: + return ina232_read_in(dev, channel, val); + case hwmon_curr: + return ina232_read_current(dev, val); + case hwmon_power: + return ina232_read_power(dev, val); + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int ina232_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + return -EOPNOTSUPP; +} + +static umode_t ina232_is_visible(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_input: + return 0444; + default: + return 0; + } + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + return 0444; + default: + return 0; + } + case hwmon_power: + switch (attr) { + case hwmon_power_input: + return 0444; + default: + return 0; + } + default: + return 0; + } +} + +static const struct hwmon_channel_info *ina232_info[] = { + HWMON_CHANNEL_INFO(in, + /* shunt voltage in mV */ + HWMON_I_INPUT, + /* bus voltage in mV */ + HWMON_I_INPUT), + HWMON_CHANNEL_INFO(curr, + /* current in mA */ + HWMON_C_INPUT), + HWMON_CHANNEL_INFO(power, + /* power in uW */ + HWMON_P_INPUT), + NULL +}; + +static const struct hwmon_ops ina232_hwmon_ops = { + .is_visible = ina232_is_visible, + .read = ina232_read, + .write = ina232_write, +}; + +static const struct hwmon_chip_info ina232_chip_info = { + .ops = &ina232_hwmon_ops, + .info = ina232_info, +}; + +static int ina232_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct ina232_data *data; + u32 calibration; + int config; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->config_lock); + + data->regmap = devm_regmap_init_i2c(client, &ina232_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(dev, "failed to allocate register map\n"); + return PTR_ERR(data->regmap); + } + + /* load shunt value in uOhm */ + if (device_property_read_u32(dev, "nvidia,shunt-resistor", &data->rshunt) < 0) + data->rshunt = INA232_RSHUNT_DEFAULT; + if (data->rshunt == 0) { + dev_err(dev, "invalid shunt resister value %u\n", data->rshunt); + return -EINVAL; + } + + /* load shunt gain value (either 1 or 4) */ + if (device_property_read_u32(dev, "nvidia,shunt-gain", &data->gain) < 0) + data->gain = INA232_SHUNT_GAIN_DEFAULT; /* Default of ADCRANGE = 0 */ + if (data->gain != 1 && data->gain != 4) { + dev_err(dev, "invalid shunt gain value %u\n", data->gain); + return -EINVAL; + } + + /* load maximum expected current in uA */ + if (device_property_read_u32(dev, + "nvidia,max-expected-current", + &data->max_expected_current) < 0) + data->max_expected_current = INA232_MAX_EXPECTED_CURRENT_DEFAULT; + + /* Reset the device */ + ret = regmap_write(data->regmap, INA232_CONFIG, INA232_RESET_VALUE); + if (ret < 0) { + dev_err(dev, "error resetting the device: %d\n", ret); + return -ENODEV; + } + + /* Setup CONFIG register */ + config = INA232_CONFIG_DEFAULT; + if (data->gain == 4) + config |= INA232_CONFIG_ADCRANGE; /* ADCRANGE = 1 is /4 */ + + ret = regmap_write(data->regmap, INA232_CONFIG, config); + if (ret < 0) { + dev_err(dev, "error configuring the device: %d\n", ret); + return -ENODEV; + } + + /* Setup SHUNT_CALIBRATION register */ + data->current_lsb = (data->max_expected_current >> 15) << 3; + calibration = ((5120 * 1000 / data->current_lsb) * 1000) / data->rshunt; + if (config & INA232_CONFIG_ADCRANGE) + calibration >>= 2; + + ret = regmap_write(data->regmap, INA232_CALIBRATION, calibration); + if (ret < 0) { + dev_err(dev, "error configuring the device: %d\n", ret); + return -ENODEV; + } + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, + &ina232_chip_info, + ina232_groups); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + return 0; +} + +static const struct i2c_device_id ina232_id[] = { + { "ina232", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ina232_id); + +static const struct of_device_id __maybe_unused ina232_of_match[] = { + { .compatible = "nvidia,ina232" }, + { }, +}; +MODULE_DEVICE_TABLE(of, ina232_of_match); + +static struct i2c_driver ina232_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "ina232", + .of_match_table = of_match_ptr(ina232_of_match), + }, +#if defined(NV_I2C_DRIVER_STRUCT_HAS_PROBE_NEW) + .probe_new = ina232_probe, +#else + .probe = ina232_probe, +#endif + .id_table = ina232_id, +}; + +module_i2c_driver(ina232_driver); + +MODULE_AUTHOR("Johnny Liu "); +MODULE_DESCRIPTION("ina232 driver"); +MODULE_LICENSE("GPL");