summaryrefslogtreecommitdiff
path: root/drivers/hwmon/ltc2945.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hwmon/ltc2945.c')
-rw-r--r--drivers/hwmon/ltc2945.c273
1 files changed, 142 insertions, 131 deletions
diff --git a/drivers/hwmon/ltc2945.c b/drivers/hwmon/ltc2945.c
index 1b92e4f6e234..3e0e0e0687bd 100644
--- a/drivers/hwmon/ltc2945.c
+++ b/drivers/hwmon/ltc2945.c
@@ -1,17 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Linear Technology LTC2945 I2C Power Monitor
*
* Copyright (c) 2014 Guenter Roeck
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*/
#include <linux/kernel.h>
@@ -67,6 +58,22 @@
#define CONTROL_MULT_SELECT (1 << 0)
#define CONTROL_TEST_MODE (1 << 4)
+static const struct of_device_id __maybe_unused ltc2945_of_match[] = {
+ { .compatible = "adi,ltc2945" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ltc2945_of_match);
+
+/**
+ * struct ltc2945_data - LTC2945 device data
+ * @regmap: regmap device
+ * @shunt_resistor: shunt resistor value in micro ohms (1000 by default)
+ */
+struct ltc2945_data {
+ struct regmap *regmap;
+ u32 shunt_resistor;
+};
+
static inline bool is_power_reg(u8 reg)
{
return reg < LTC2945_SENSE_H;
@@ -75,7 +82,9 @@ static inline bool is_power_reg(u8 reg)
/* Return the value from the given register in uW, mV, or mA */
static long long ltc2945_reg_to_val(struct device *dev, u8 reg)
{
- struct regmap *regmap = dev_get_drvdata(dev);
+ struct ltc2945_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
+ u32 shunt_resistor = data->shunt_resistor;
unsigned int control;
u8 buf[3];
long long val;
@@ -87,10 +96,10 @@ static long long ltc2945_reg_to_val(struct device *dev, u8 reg)
return ret;
if (is_power_reg(reg)) {
- /* power */
+ /* 24-bit power */
val = (buf[0] << 16) + (buf[1] << 8) + buf[2];
} else {
- /* current, voltage */
+ /* 12-bit current, voltage */
val = (buf[0] << 4) + (buf[1] >> 4);
}
@@ -101,9 +110,7 @@ static long long ltc2945_reg_to_val(struct device *dev, u8 reg)
case LTC2945_MAX_POWER_THRES_H:
case LTC2945_MIN_POWER_THRES_H:
/*
- * Convert to uW by assuming current is measured with
- * an 1mOhm sense resistor, similar to current
- * measurements.
+ * Convert to uW
* Control register bit 0 selects if voltage at SENSE+/VDD
* or voltage at ADIN is used to measure power.
*/
@@ -117,6 +124,14 @@ static long long ltc2945_reg_to_val(struct device *dev, u8 reg)
/* 0.5 mV * 25 uV = 0.0125 uV resolution. */
val = (val * 25LL) >> 1;
}
+ val *= 1000;
+ /* Overflow check: Assuming max 24-bit power, val is at most 53 bits right now. */
+ val = DIV_ROUND_CLOSEST_ULL(val, shunt_resistor);
+ /*
+ * Overflow check: After division, depending on shunt resistor,
+ * val can still be > 32 bits so returning long long makes sense
+ */
+
break;
case LTC2945_VIN_H:
case LTC2945_MAX_VIN_H:
@@ -139,14 +154,11 @@ static long long ltc2945_reg_to_val(struct device *dev, u8 reg)
case LTC2945_MIN_SENSE_H:
case LTC2945_MAX_SENSE_THRES_H:
case LTC2945_MIN_SENSE_THRES_H:
- /*
- * 25 uV resolution. Convert to current as measured with
- * an 1 mOhm sense resistor, in mA. If a different sense
- * resistor is installed, calculate the actual current by
- * dividing the reported current by the sense resistor value
- * in mOhm.
- */
- val *= 25;
+ /* 25 uV resolution. Convert to mA. */
+ val *= 25 * 1000;
+ /* Overflow check: Assuming max 12-bit sense, val is at most 27 bits right now */
+ val = DIV_ROUND_CLOSEST_ULL(val, shunt_resistor);
+ /* Overflow check: After division, <= 27 bits */
break;
default:
return -EINVAL;
@@ -154,13 +166,18 @@ static long long ltc2945_reg_to_val(struct device *dev, u8 reg)
return val;
}
-static int ltc2945_val_to_reg(struct device *dev, u8 reg,
- unsigned long val)
+static long long ltc2945_val_to_reg(struct device *dev, u8 reg,
+ unsigned long long val)
{
- struct regmap *regmap = dev_get_drvdata(dev);
+ struct ltc2945_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
+ u32 shunt_resistor = data->shunt_resistor;
unsigned int control;
int ret;
+ /* Ensure we don't overflow */
+ val = clamp_val(val, 0, U32_MAX);
+
switch (reg) {
case LTC2945_POWER_H:
case LTC2945_MAX_POWER_H:
@@ -168,9 +185,6 @@ static int ltc2945_val_to_reg(struct device *dev, u8 reg,
case LTC2945_MAX_POWER_THRES_H:
case LTC2945_MIN_POWER_THRES_H:
/*
- * Convert to register value by assuming current is measured
- * with an 1mOhm sense resistor, similar to current
- * measurements.
* Control register bit 0 selects if voltage at SENSE+/VDD
* or voltage at ADIN is used to measure power, which in turn
* determines register calculations.
@@ -180,14 +194,16 @@ static int ltc2945_val_to_reg(struct device *dev, u8 reg,
return ret;
if (control & CONTROL_MULT_SELECT) {
/* 25 mV * 25 uV = 0.625 uV resolution. */
- val = DIV_ROUND_CLOSEST(val, 625);
+ val *= shunt_resistor;
+ /* Overflow check: Assuming 32-bit val and shunt resistor, val <= 64bits */
+ val = DIV_ROUND_CLOSEST_ULL(val, 625 * 1000);
+ /* Overflow check: val is now <= 44 bits */
} else {
- /*
- * 0.5 mV * 25 uV = 0.0125 uV resolution.
- * Divide first to avoid overflow;
- * accept loss of accuracy.
- */
- val = DIV_ROUND_CLOSEST(val, 25) * 2;
+ /* 0.5 mV * 25 uV = 0.0125 uV resolution. */
+ val *= shunt_resistor;
+ /* Overflow check: Assuming 32-bit val and shunt resistor, val <= 64bits */
+ val = DIV_ROUND_CLOSEST_ULL(val, 25 * 1000) * 2;
+ /* Overflow check: val is now <= 51 bits */
}
break;
case LTC2945_VIN_H:
@@ -196,7 +212,7 @@ static int ltc2945_val_to_reg(struct device *dev, u8 reg,
case LTC2945_MAX_VIN_THRES_H:
case LTC2945_MIN_VIN_THRES_H:
/* 25 mV resolution. */
- val /= 25;
+ val = DIV_ROUND_CLOSEST_ULL(val, 25);
break;
case LTC2945_ADIN_H:
case LTC2945_MAX_ADIN_H:
@@ -211,14 +227,11 @@ static int ltc2945_val_to_reg(struct device *dev, u8 reg,
case LTC2945_MIN_SENSE_H:
case LTC2945_MAX_SENSE_THRES_H:
case LTC2945_MIN_SENSE_THRES_H:
- /*
- * 25 uV resolution. Convert to current as measured with
- * an 1 mOhm sense resistor, in mA. If a different sense
- * resistor is installed, calculate the actual current by
- * dividing the reported current by the sense resistor value
- * in mOhm.
- */
- val = DIV_ROUND_CLOSEST(val, 25);
+ /* 25 uV resolution. Convert to mA. */
+ val *= shunt_resistor;
+ /* Overflow check: Assuming 32-bit val and 32-bit shunt resistor, val is 64bits */
+ val = DIV_ROUND_CLOSEST_ULL(val, 25 * 1000);
+ /* Overflow check: val is now <= 50 bits */
break;
default:
return -EINVAL;
@@ -226,7 +239,7 @@ static int ltc2945_val_to_reg(struct device *dev, u8 reg,
return val;
}
-static ssize_t ltc2945_show_value(struct device *dev,
+static ssize_t ltc2945_value_show(struct device *dev,
struct device_attribute *da, char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
@@ -235,28 +248,31 @@ static ssize_t ltc2945_show_value(struct device *dev,
value = ltc2945_reg_to_val(dev, attr->index);
if (value < 0)
return value;
- return snprintf(buf, PAGE_SIZE, "%lld\n", value);
+ return sysfs_emit(buf, "%lld\n", value);
}
-static ssize_t ltc2945_set_value(struct device *dev,
- struct device_attribute *da,
- const char *buf, size_t count)
+static ssize_t ltc2945_value_store(struct device *dev,
+ struct device_attribute *da,
+ const char *buf, size_t count)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
- struct regmap *regmap = dev_get_drvdata(dev);
+ struct ltc2945_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
u8 reg = attr->index;
- unsigned long val;
+ unsigned int val;
u8 regbuf[3];
int num_regs;
- int regval;
+ long long regval;
int ret;
- ret = kstrtoul(buf, 10, &val);
+ ret = kstrtouint(buf, 10, &val);
if (ret)
return ret;
/* convert to register value, then clamp and write result */
regval = ltc2945_val_to_reg(dev, reg, val);
+ if (regval < 0)
+ return regval;
if (is_power_reg(reg)) {
regval = clamp_val(regval, 0, 0xffffff);
regbuf[0] = regval >> 16;
@@ -273,12 +289,13 @@ static ssize_t ltc2945_set_value(struct device *dev,
return ret < 0 ? ret : count;
}
-static ssize_t ltc2945_reset_history(struct device *dev,
+static ssize_t ltc2945_history_store(struct device *dev,
struct device_attribute *da,
const char *buf, size_t count)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
- struct regmap *regmap = dev_get_drvdata(dev);
+ struct ltc2945_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
u8 reg = attr->index;
int num_regs = is_power_reg(reg) ? 3 : 2;
u8 buf_min[3] = { 0xff, 0xff, 0xff };
@@ -326,11 +343,12 @@ static ssize_t ltc2945_reset_history(struct device *dev,
return ret ? : count;
}
-static ssize_t ltc2945_show_bool(struct device *dev,
+static ssize_t ltc2945_bool_show(struct device *dev,
struct device_attribute *da, char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
- struct regmap *regmap = dev_get_drvdata(dev);
+ struct ltc2945_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
unsigned int fault;
int ret;
@@ -342,91 +360,70 @@ static ssize_t ltc2945_show_bool(struct device *dev,
if (fault) /* Clear reported faults in chip register */
regmap_update_bits(regmap, LTC2945_FAULT, attr->index, 0);
- return snprintf(buf, PAGE_SIZE, "%d\n", !!fault);
+ return sysfs_emit(buf, "%d\n", !!fault);
}
/* Input voltages */
-static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ltc2945_show_value, NULL,
- LTC2945_VIN_H);
-static SENSOR_DEVICE_ATTR(in1_min, S_IRUGO | S_IWUSR, ltc2945_show_value,
- ltc2945_set_value, LTC2945_MIN_VIN_THRES_H);
-static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR, ltc2945_show_value,
- ltc2945_set_value, LTC2945_MAX_VIN_THRES_H);
-static SENSOR_DEVICE_ATTR(in1_lowest, S_IRUGO, ltc2945_show_value, NULL,
- LTC2945_MIN_VIN_H);
-static SENSOR_DEVICE_ATTR(in1_highest, S_IRUGO, ltc2945_show_value, NULL,
- LTC2945_MAX_VIN_H);
-static SENSOR_DEVICE_ATTR(in1_reset_history, S_IWUSR, NULL,
- ltc2945_reset_history, LTC2945_MIN_VIN_H);
-
-static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, ltc2945_show_value, NULL,
- LTC2945_ADIN_H);
-static SENSOR_DEVICE_ATTR(in2_min, S_IRUGO | S_IWUSR, ltc2945_show_value,
- ltc2945_set_value, LTC2945_MIN_ADIN_THRES_H);
-static SENSOR_DEVICE_ATTR(in2_max, S_IRUGO | S_IWUSR, ltc2945_show_value,
- ltc2945_set_value, LTC2945_MAX_ADIN_THRES_H);
-static SENSOR_DEVICE_ATTR(in2_lowest, S_IRUGO, ltc2945_show_value, NULL,
- LTC2945_MIN_ADIN_H);
-static SENSOR_DEVICE_ATTR(in2_highest, S_IRUGO, ltc2945_show_value, NULL,
- LTC2945_MAX_ADIN_H);
-static SENSOR_DEVICE_ATTR(in2_reset_history, S_IWUSR, NULL,
- ltc2945_reset_history, LTC2945_MIN_ADIN_H);
+static SENSOR_DEVICE_ATTR_RO(in1_input, ltc2945_value, LTC2945_VIN_H);
+static SENSOR_DEVICE_ATTR_RW(in1_min, ltc2945_value, LTC2945_MIN_VIN_THRES_H);
+static SENSOR_DEVICE_ATTR_RW(in1_max, ltc2945_value, LTC2945_MAX_VIN_THRES_H);
+static SENSOR_DEVICE_ATTR_RO(in1_lowest, ltc2945_value, LTC2945_MIN_VIN_H);
+static SENSOR_DEVICE_ATTR_RO(in1_highest, ltc2945_value, LTC2945_MAX_VIN_H);
+static SENSOR_DEVICE_ATTR_WO(in1_reset_history, ltc2945_history,
+ LTC2945_MIN_VIN_H);
+
+static SENSOR_DEVICE_ATTR_RO(in2_input, ltc2945_value, LTC2945_ADIN_H);
+static SENSOR_DEVICE_ATTR_RW(in2_min, ltc2945_value, LTC2945_MIN_ADIN_THRES_H);
+static SENSOR_DEVICE_ATTR_RW(in2_max, ltc2945_value, LTC2945_MAX_ADIN_THRES_H);
+static SENSOR_DEVICE_ATTR_RO(in2_lowest, ltc2945_value, LTC2945_MIN_ADIN_H);
+static SENSOR_DEVICE_ATTR_RO(in2_highest, ltc2945_value, LTC2945_MAX_ADIN_H);
+static SENSOR_DEVICE_ATTR_WO(in2_reset_history, ltc2945_history,
+ LTC2945_MIN_ADIN_H);
/* Voltage alarms */
-static SENSOR_DEVICE_ATTR(in1_min_alarm, S_IRUGO, ltc2945_show_bool, NULL,
- FAULT_VIN_UV);
-static SENSOR_DEVICE_ATTR(in1_max_alarm, S_IRUGO, ltc2945_show_bool, NULL,
- FAULT_VIN_OV);
-static SENSOR_DEVICE_ATTR(in2_min_alarm, S_IRUGO, ltc2945_show_bool, NULL,
- FAULT_ADIN_UV);
-static SENSOR_DEVICE_ATTR(in2_max_alarm, S_IRUGO, ltc2945_show_bool, NULL,
- FAULT_ADIN_OV);
+static SENSOR_DEVICE_ATTR_RO(in1_min_alarm, ltc2945_bool, FAULT_VIN_UV);
+static SENSOR_DEVICE_ATTR_RO(in1_max_alarm, ltc2945_bool, FAULT_VIN_OV);
+static SENSOR_DEVICE_ATTR_RO(in2_min_alarm, ltc2945_bool, FAULT_ADIN_UV);
+static SENSOR_DEVICE_ATTR_RO(in2_max_alarm, ltc2945_bool, FAULT_ADIN_OV);
/* Currents (via sense resistor) */
-static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ltc2945_show_value, NULL,
- LTC2945_SENSE_H);
-static SENSOR_DEVICE_ATTR(curr1_min, S_IRUGO | S_IWUSR, ltc2945_show_value,
- ltc2945_set_value, LTC2945_MIN_SENSE_THRES_H);
-static SENSOR_DEVICE_ATTR(curr1_max, S_IRUGO | S_IWUSR, ltc2945_show_value,
- ltc2945_set_value, LTC2945_MAX_SENSE_THRES_H);
-static SENSOR_DEVICE_ATTR(curr1_lowest, S_IRUGO, ltc2945_show_value, NULL,
- LTC2945_MIN_SENSE_H);
-static SENSOR_DEVICE_ATTR(curr1_highest, S_IRUGO, ltc2945_show_value, NULL,
- LTC2945_MAX_SENSE_H);
-static SENSOR_DEVICE_ATTR(curr1_reset_history, S_IWUSR, NULL,
- ltc2945_reset_history, LTC2945_MIN_SENSE_H);
+static SENSOR_DEVICE_ATTR_RO(curr1_input, ltc2945_value, LTC2945_SENSE_H);
+static SENSOR_DEVICE_ATTR_RW(curr1_min, ltc2945_value,
+ LTC2945_MIN_SENSE_THRES_H);
+static SENSOR_DEVICE_ATTR_RW(curr1_max, ltc2945_value,
+ LTC2945_MAX_SENSE_THRES_H);
+static SENSOR_DEVICE_ATTR_RO(curr1_lowest, ltc2945_value, LTC2945_MIN_SENSE_H);
+static SENSOR_DEVICE_ATTR_RO(curr1_highest, ltc2945_value,
+ LTC2945_MAX_SENSE_H);
+static SENSOR_DEVICE_ATTR_WO(curr1_reset_history, ltc2945_history,
+ LTC2945_MIN_SENSE_H);
/* Current alarms */
-static SENSOR_DEVICE_ATTR(curr1_min_alarm, S_IRUGO, ltc2945_show_bool, NULL,
- FAULT_SENSE_UV);
-static SENSOR_DEVICE_ATTR(curr1_max_alarm, S_IRUGO, ltc2945_show_bool, NULL,
- FAULT_SENSE_OV);
+static SENSOR_DEVICE_ATTR_RO(curr1_min_alarm, ltc2945_bool, FAULT_SENSE_UV);
+static SENSOR_DEVICE_ATTR_RO(curr1_max_alarm, ltc2945_bool, FAULT_SENSE_OV);
/* Power */
-static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, ltc2945_show_value, NULL,
- LTC2945_POWER_H);
-static SENSOR_DEVICE_ATTR(power1_min, S_IRUGO | S_IWUSR, ltc2945_show_value,
- ltc2945_set_value, LTC2945_MIN_POWER_THRES_H);
-static SENSOR_DEVICE_ATTR(power1_max, S_IRUGO | S_IWUSR, ltc2945_show_value,
- ltc2945_set_value, LTC2945_MAX_POWER_THRES_H);
-static SENSOR_DEVICE_ATTR(power1_input_lowest, S_IRUGO, ltc2945_show_value,
- NULL, LTC2945_MIN_POWER_H);
-static SENSOR_DEVICE_ATTR(power1_input_highest, S_IRUGO, ltc2945_show_value,
- NULL, LTC2945_MAX_POWER_H);
-static SENSOR_DEVICE_ATTR(power1_reset_history, S_IWUSR, NULL,
- ltc2945_reset_history, LTC2945_MIN_POWER_H);
+static SENSOR_DEVICE_ATTR_RO(power1_input, ltc2945_value, LTC2945_POWER_H);
+static SENSOR_DEVICE_ATTR_RW(power1_min, ltc2945_value,
+ LTC2945_MIN_POWER_THRES_H);
+static SENSOR_DEVICE_ATTR_RW(power1_max, ltc2945_value,
+ LTC2945_MAX_POWER_THRES_H);
+static SENSOR_DEVICE_ATTR_RO(power1_input_lowest, ltc2945_value,
+ LTC2945_MIN_POWER_H);
+static SENSOR_DEVICE_ATTR_RO(power1_input_highest, ltc2945_value,
+ LTC2945_MAX_POWER_H);
+static SENSOR_DEVICE_ATTR_WO(power1_reset_history, ltc2945_history,
+ LTC2945_MIN_POWER_H);
/* Power alarms */
-static SENSOR_DEVICE_ATTR(power1_min_alarm, S_IRUGO, ltc2945_show_bool, NULL,
- FAULT_POWER_UV);
-static SENSOR_DEVICE_ATTR(power1_max_alarm, S_IRUGO, ltc2945_show_bool, NULL,
- FAULT_POWER_OV);
+static SENSOR_DEVICE_ATTR_RO(power1_min_alarm, ltc2945_bool, FAULT_POWER_UV);
+static SENSOR_DEVICE_ATTR_RO(power1_max_alarm, ltc2945_bool, FAULT_POWER_OV);
static struct attribute *ltc2945_attrs[] = {
&sensor_dev_attr_in1_input.dev_attr.attr,
@@ -475,12 +472,17 @@ static const struct regmap_config ltc2945_regmap_config = {
.max_register = LTC2945_MIN_ADIN_THRES_L,
};
-static int ltc2945_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+static int ltc2945_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct device *hwmon_dev;
struct regmap *regmap;
+ struct ltc2945_data *data;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+ dev_set_drvdata(dev, data);
regmap = devm_regmap_init_i2c(client, &ltc2945_regmap_config);
if (IS_ERR(regmap)) {
@@ -488,17 +490,25 @@ static int ltc2945_probe(struct i2c_client *client,
return PTR_ERR(regmap);
}
+ data->regmap = regmap;
+ if (device_property_read_u32(dev, "shunt-resistor-micro-ohms",
+ &data->shunt_resistor))
+ data->shunt_resistor = 1000;
+
+ if (data->shunt_resistor == 0)
+ return -EINVAL;
+
/* Clear faults */
regmap_write(regmap, LTC2945_FAULT, 0x00);
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
- regmap,
+ data,
ltc2945_groups);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static const struct i2c_device_id ltc2945_id[] = {
- {"ltc2945", 0},
+ {"ltc2945"},
{ }
};
@@ -506,8 +516,9 @@ MODULE_DEVICE_TABLE(i2c, ltc2945_id);
static struct i2c_driver ltc2945_driver = {
.driver = {
- .name = "ltc2945",
- },
+ .name = "ltc2945",
+ .of_match_table = of_match_ptr(ltc2945_of_match),
+ },
.probe = ltc2945_probe,
.id_table = ltc2945_id,
};