aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/drivers/hwmon/ina3221.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hwmon/ina3221.c')
-rw-r--r--drivers/hwmon/ina3221.c235
1 files changed, 221 insertions, 14 deletions
diff --git a/drivers/hwmon/ina3221.c b/drivers/hwmon/ina3221.c
index 1e38b4c43fbf..88e65d2b4fa5 100644
--- a/drivers/hwmon/ina3221.c
+++ b/drivers/hwmon/ina3221.c
@@ -43,6 +43,7 @@
#define INA3221_CONFIG_MODE_SHUNT BIT(0)
#define INA3221_CONFIG_MODE_BUS BIT(1)
#define INA3221_CONFIG_MODE_CONTINUOUS BIT(2)
+#define INA3221_CONFIG_CHx_EN(x) BIT(14 - (x))
#define INA3221_RSHUNT_DEFAULT 10000
@@ -77,6 +78,9 @@ enum ina3221_channels {
};
static const unsigned int register_channel[] = {
+ [INA3221_BUS1] = INA3221_CHANNEL1,
+ [INA3221_BUS2] = INA3221_CHANNEL2,
+ [INA3221_BUS3] = INA3221_CHANNEL3,
[INA3221_SHUNT1] = INA3221_CHANNEL1,
[INA3221_SHUNT2] = INA3221_CHANNEL2,
[INA3221_SHUNT3] = INA3221_CHANNEL3,
@@ -89,19 +93,88 @@ static const unsigned int register_channel[] = {
};
/**
+ * struct ina3221_input - channel input source specific information
+ * @label: label of channel input source
+ * @shunt_resistor: shunt resistor value of channel input source
+ * @disconnected: connection status of channel input source
+ */
+struct ina3221_input {
+ const char *label;
+ int shunt_resistor;
+ bool disconnected;
+};
+
+/**
* struct ina3221_data - device specific information
* @regmap: Register map of the device
* @fields: Register fields of the device
- * @shunt_resistors: Array of resistor values per channel
+ * @inputs: Array of channel input source specific structures
* @reg_config: Register value of INA3221_CONFIG
*/
struct ina3221_data {
struct regmap *regmap;
struct regmap_field *fields[F_MAX_FIELDS];
- int shunt_resistors[INA3221_NUM_CHANNELS];
+ struct ina3221_input inputs[INA3221_NUM_CHANNELS];
u32 reg_config;
};
+static inline bool ina3221_is_enabled(struct ina3221_data *ina, int channel)
+{
+ return ina->reg_config & INA3221_CONFIG_CHx_EN(channel);
+}
+
+static ssize_t ina3221_show_label(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr);
+ struct ina3221_data *ina = dev_get_drvdata(dev);
+ unsigned int channel = sd_attr->index;
+ struct ina3221_input *input = &ina->inputs[channel];
+
+ return snprintf(buf, PAGE_SIZE, "%s\n", input->label);
+}
+
+static ssize_t ina3221_show_enable(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr);
+ struct ina3221_data *ina = dev_get_drvdata(dev);
+ unsigned int channel = sd_attr->index;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n",
+ ina3221_is_enabled(ina, channel));
+}
+
+static ssize_t ina3221_set_enable(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr);
+ struct ina3221_data *ina = dev_get_drvdata(dev);
+ unsigned int channel = sd_attr->index;
+ u16 config, mask = INA3221_CONFIG_CHx_EN(channel);
+ bool enable;
+ int ret;
+
+ ret = kstrtobool(buf, &enable);
+ if (ret)
+ return ret;
+
+ config = enable ? mask : 0;
+
+ /* Enable or disable the channel */
+ ret = regmap_update_bits(ina->regmap, INA3221_CONFIG, mask, config);
+ if (ret)
+ return ret;
+
+ /* Cache the latest config register value */
+ ret = regmap_read(ina->regmap, INA3221_CONFIG, &ina->reg_config);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
static int ina3221_read_value(struct ina3221_data *ina, unsigned int reg,
int *val)
{
@@ -124,8 +197,13 @@ static ssize_t ina3221_show_bus_voltage(struct device *dev,
struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr);
struct ina3221_data *ina = dev_get_drvdata(dev);
unsigned int reg = sd_attr->index;
+ unsigned int channel = register_channel[reg];
int val, voltage_mv, ret;
+ /* No data for read-only attribute if channel is disabled */
+ if (!attr->store && !ina3221_is_enabled(ina, channel))
+ return -ENODATA;
+
ret = ina3221_read_value(ina, reg, &val);
if (ret)
return ret;
@@ -142,8 +220,13 @@ static ssize_t ina3221_show_shunt_voltage(struct device *dev,
struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr);
struct ina3221_data *ina = dev_get_drvdata(dev);
unsigned int reg = sd_attr->index;
+ unsigned int channel = register_channel[reg];
int val, voltage_uv, ret;
+ /* No data for read-only attribute if channel is disabled */
+ if (!attr->store && !ina3221_is_enabled(ina, channel))
+ return -ENODATA;
+
ret = ina3221_read_value(ina, reg, &val);
if (ret)
return ret;
@@ -159,9 +242,14 @@ static ssize_t ina3221_show_current(struct device *dev,
struct ina3221_data *ina = dev_get_drvdata(dev);
unsigned int reg = sd_attr->index;
unsigned int channel = register_channel[reg];
- int resistance_uo = ina->shunt_resistors[channel];
+ struct ina3221_input *input = &ina->inputs[channel];
+ int resistance_uo = input->shunt_resistor;
int val, current_ma, voltage_nv, ret;
+ /* No data for read-only attribute if channel is disabled */
+ if (!attr->store && !ina3221_is_enabled(ina, channel))
+ return -ENODATA;
+
ret = ina3221_read_value(ina, reg, &val);
if (ret)
return ret;
@@ -180,7 +268,8 @@ static ssize_t ina3221_set_current(struct device *dev,
struct ina3221_data *ina = dev_get_drvdata(dev);
unsigned int reg = sd_attr->index;
unsigned int channel = register_channel[reg];
- int resistance_uo = ina->shunt_resistors[channel];
+ struct ina3221_input *input = &ina->inputs[channel];
+ int resistance_uo = input->shunt_resistor;
int val, current_ma, voltage_uv, ret;
ret = kstrtoint(buf, 0, &current_ma);
@@ -213,11 +302,9 @@ static ssize_t ina3221_show_shunt(struct device *dev,
struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr);
struct ina3221_data *ina = dev_get_drvdata(dev);
unsigned int channel = sd_attr->index;
- unsigned int resistance_uo;
-
- resistance_uo = ina->shunt_resistors[channel];
+ struct ina3221_input *input = &ina->inputs[channel];
- return snprintf(buf, PAGE_SIZE, "%d\n", resistance_uo);
+ return snprintf(buf, PAGE_SIZE, "%d\n", input->shunt_resistor);
}
static ssize_t ina3221_set_shunt(struct device *dev,
@@ -227,6 +314,7 @@ static ssize_t ina3221_set_shunt(struct device *dev,
struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr);
struct ina3221_data *ina = dev_get_drvdata(dev);
unsigned int channel = sd_attr->index;
+ struct ina3221_input *input = &ina->inputs[channel];
int val;
int ret;
@@ -236,7 +324,7 @@ static ssize_t ina3221_set_shunt(struct device *dev,
val = clamp_val(val, 1, INT_MAX);
- ina->shunt_resistors[channel] = val;
+ input->shunt_resistor = val;
return count;
}
@@ -257,6 +345,22 @@ static ssize_t ina3221_show_alert(struct device *dev,
return snprintf(buf, PAGE_SIZE, "%d\n", regval);
}
+/* input channel label */
+static SENSOR_DEVICE_ATTR(in1_label, 0444,
+ ina3221_show_label, NULL, INA3221_CHANNEL1);
+static SENSOR_DEVICE_ATTR(in2_label, 0444,
+ ina3221_show_label, NULL, INA3221_CHANNEL2);
+static SENSOR_DEVICE_ATTR(in3_label, 0444,
+ ina3221_show_label, NULL, INA3221_CHANNEL3);
+
+/* voltage channel enable */
+static SENSOR_DEVICE_ATTR(in1_enable, 0644,
+ ina3221_show_enable, ina3221_set_enable, INA3221_CHANNEL1);
+static SENSOR_DEVICE_ATTR(in2_enable, 0644,
+ ina3221_show_enable, ina3221_set_enable, INA3221_CHANNEL2);
+static SENSOR_DEVICE_ATTR(in3_enable, 0644,
+ ina3221_show_enable, ina3221_set_enable, INA3221_CHANNEL3);
+
/* bus voltage */
static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO,
ina3221_show_bus_voltage, NULL, INA3221_BUS1);
@@ -322,7 +426,9 @@ static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO,
ina3221_show_shunt_voltage, NULL, INA3221_SHUNT3);
static struct attribute *ina3221_attrs[] = {
- /* channel 1 */
+ /* channel 1 -- make sure label at first */
+ &sensor_dev_attr_in1_label.dev_attr.attr,
+ &sensor_dev_attr_in1_enable.dev_attr.attr,
&sensor_dev_attr_in1_input.dev_attr.attr,
&sensor_dev_attr_curr1_input.dev_attr.attr,
&sensor_dev_attr_shunt1_resistor.dev_attr.attr,
@@ -332,7 +438,9 @@ static struct attribute *ina3221_attrs[] = {
&sensor_dev_attr_curr1_max_alarm.dev_attr.attr,
&sensor_dev_attr_in4_input.dev_attr.attr,
- /* channel 2 */
+ /* channel 2 -- make sure label at first */
+ &sensor_dev_attr_in2_label.dev_attr.attr,
+ &sensor_dev_attr_in2_enable.dev_attr.attr,
&sensor_dev_attr_in2_input.dev_attr.attr,
&sensor_dev_attr_curr2_input.dev_attr.attr,
&sensor_dev_attr_shunt2_resistor.dev_attr.attr,
@@ -342,7 +450,9 @@ static struct attribute *ina3221_attrs[] = {
&sensor_dev_attr_curr2_max_alarm.dev_attr.attr,
&sensor_dev_attr_in5_input.dev_attr.attr,
- /* channel 3 */
+ /* channel 3 -- make sure label at first */
+ &sensor_dev_attr_in3_label.dev_attr.attr,
+ &sensor_dev_attr_in3_enable.dev_attr.attr,
&sensor_dev_attr_in3_input.dev_attr.attr,
&sensor_dev_attr_curr3_input.dev_attr.attr,
&sensor_dev_attr_shunt3_resistor.dev_attr.attr,
@@ -354,7 +464,30 @@ static struct attribute *ina3221_attrs[] = {
NULL,
};
-ATTRIBUTE_GROUPS(ina3221);
+
+static umode_t ina3221_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ const int max_attrs = ARRAY_SIZE(ina3221_attrs) - 1;
+ const int num_attrs = max_attrs / INA3221_NUM_CHANNELS;
+ struct device *dev = kobj_to_dev(kobj);
+ struct ina3221_data *ina = dev_get_drvdata(dev);
+ enum ina3221_channels channel = n / num_attrs;
+ struct ina3221_input *input = &ina->inputs[channel];
+ int index = n % num_attrs;
+
+ /* Hide label node if label is not provided */
+ if (index == 0 && !input->label)
+ return 0;
+
+ return attr->mode;
+}
+
+static const struct attribute_group ina3221_group = {
+ .is_visible = ina3221_attr_is_visible,
+ .attrs = ina3221_attrs,
+};
+__ATTRIBUTE_GROUPS(ina3221);
static const struct regmap_range ina3221_yes_ranges[] = {
regmap_reg_range(INA3221_CONFIG, INA3221_BUS3),
@@ -374,6 +507,60 @@ static const struct regmap_config ina3221_regmap_config = {
.volatile_table = &ina3221_volatile_table,
};
+static int ina3221_probe_child_from_dt(struct device *dev,
+ struct device_node *child,
+ struct ina3221_data *ina)
+{
+ struct ina3221_input *input;
+ u32 val;
+ int ret;
+
+ ret = of_property_read_u32(child, "reg", &val);
+ if (ret) {
+ dev_err(dev, "missing reg property of %s\n", child->name);
+ return ret;
+ } else if (val > INA3221_CHANNEL3) {
+ dev_err(dev, "invalid reg %d of %s\n", val, child->name);
+ return ret;
+ }
+
+ input = &ina->inputs[val];
+
+ /* Log the disconnected channel input */
+ if (!of_device_is_available(child)) {
+ input->disconnected = true;
+ return 0;
+ }
+
+ /* Save the connected input label if available */
+ of_property_read_string(child, "label", &input->label);
+
+ /* Overwrite default shunt resistor value optionally */
+ if (!of_property_read_u32(child, "shunt-resistor-micro-ohms", &val))
+ input->shunt_resistor = val;
+
+ return 0;
+}
+
+static int ina3221_probe_from_dt(struct device *dev, struct ina3221_data *ina)
+{
+ const struct device_node *np = dev->of_node;
+ struct device_node *child;
+ int ret;
+
+ /* Compatible with non-DT platforms */
+ if (!np)
+ return 0;
+
+ for_each_child_of_node(np, child) {
+ ret = ina3221_probe_child_from_dt(dev, child, ina);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
static int ina3221_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
@@ -403,7 +590,13 @@ static int ina3221_probe(struct i2c_client *client,
}
for (i = 0; i < INA3221_NUM_CHANNELS; i++)
- ina->shunt_resistors[i] = INA3221_RSHUNT_DEFAULT;
+ ina->inputs[i].shunt_resistor = INA3221_RSHUNT_DEFAULT;
+
+ ret = ina3221_probe_from_dt(dev, ina);
+ if (ret) {
+ dev_err(dev, "Unable to probe from device tree\n");
+ return ret;
+ }
ret = regmap_field_write(ina->fields[F_RST], true);
if (ret) {
@@ -411,6 +604,20 @@ static int ina3221_probe(struct i2c_client *client,
return ret;
}
+ /* Sync config register after reset */
+ ret = regmap_read(ina->regmap, INA3221_CONFIG, &ina->reg_config);
+ if (ret)
+ return ret;
+
+ /* Disable channels if their inputs are disconnected */
+ for (i = 0; i < INA3221_NUM_CHANNELS; i++) {
+ if (ina->inputs[i].disconnected)
+ ina->reg_config &= ~INA3221_CONFIG_CHx_EN(i);
+ }
+ ret = regmap_write(ina->regmap, INA3221_CONFIG, ina->reg_config);
+ if (ret)
+ return ret;
+
dev_set_drvdata(dev, ina);
hwmon_dev = devm_hwmon_device_register_with_groups(dev,