diff options
-rw-r--r-- | Documentation/hwmon/ina3221 | 2 | ||||
-rw-r--r-- | drivers/hwmon/ina3221.c | 235 |
2 files changed, 223 insertions, 14 deletions
diff --git a/Documentation/hwmon/ina3221 b/Documentation/hwmon/ina3221 index 0ff74854cb2e..4b82cbfb551c 100644 --- a/Documentation/hwmon/ina3221 +++ b/Documentation/hwmon/ina3221 @@ -21,6 +21,8 @@ and power are calculated host-side from these. Sysfs entries ------------- +in[123]_label Voltage channel labels +in[123]_enable Voltage channel enable controls in[123]_input Bus voltage(mV) channels curr[123]_input Current(mA) measurement channels shunt[123]_resistor Shunt resistance(uOhm) channels 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, ¤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, |