|
@@ -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,
|
|
@@ -88,20 +92,89 @@ static const unsigned int register_channel[] = {
|
|
|
[INA3221_WARN3] = INA3221_CHANNEL3,
|
|
|
};
|
|
|
|
|
|
+/**
|
|
|
+ * 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, ¤t_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,
|