Skip to content

Commit

Permalink
hwmon: (ina3221) Read channel input source info from DT
Browse files Browse the repository at this point in the history
An ina3221 chip has three input ports. Each port is used
to measure the voltage and current of its input source.

The DT binding now has defined bindings for their input
sources, so the driver should read these information and
handle accordingly.

This patch adds a new structure of input source specific
information including input source label, shunt resistor
value and its connection status. It exposes these labels
via in[123]_label sysfs nodes upon available, and also
disables those channels where there are no input source
being connected. Meanwhile, it also adds in[123]_enable
sysfs nodes for users to get control of three channels,
and returns -ENODATA code for any sensor read according
to hwmon ABI.

Signed-off-by: Nicolin Chen <nicoleotsuka@gmail.com>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
  • Loading branch information
Nicolin Chen authored and Guenter Roeck committed Oct 11, 2018
1 parent 8b949c6 commit a9e9dd9
Show file tree
Hide file tree
Showing 2 changed files with 223 additions and 14 deletions.
2 changes: 2 additions & 0 deletions Documentation/hwmon/ina3221
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
235 changes: 221 additions & 14 deletions drivers/hwmon/ina3221.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand All @@ -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)
{
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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,
Expand All @@ -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;

Expand All @@ -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;
}
Expand All @@ -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);
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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),
Expand All @@ -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)
{
Expand Down Expand Up @@ -403,14 +590,34 @@ 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) {
dev_err(dev, "Unable to reset device\n");
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,
Expand Down

0 comments on commit a9e9dd9

Please sign in to comment.