Commit 5a56a39b authored by Alex Qiu's avatar Alex Qiu Committed by Guenter Roeck
Browse files

hwmon: (ina2xx) Implement alert functions



Implement alert functions for INA226, INA230 and INA231. Expose 06h
Mask/Enable and 07h Alert Limit registers via alert setting and alarm
files.

Signed-off-by: default avatarAlex Qiu <xqiu@google.com>
Signed-off-by: default avatarGuenter Roeck <linux@roeck-us.net>
parent b58bd4c6
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -99,6 +99,25 @@ Sysfs entries for ina226, ina230 and ina231 only
------------------------------------------------

======================= ====================================================
in0_lcrit		Critical low shunt voltage
in0_crit		Critical high shunt voltage
in0_lcrit_alarm		Shunt voltage critical low alarm
in0_crit_alarm		Shunt voltage critical high alarm
in1_lcrit		Critical low bus voltage
in1_crit		Critical high bus voltage
in1_lcrit_alarm		Bus voltage critical low alarm
in1_crit_alarm		Bus voltage critical high alarm
power1_crit		Critical high power
power1_crit_alarm	Power critical high alarm
update_interval		data conversion time; affects number of samples used
			to average results for shunt and bus voltages.
======================= ====================================================

.. note::

   - Configure `shunt_resistor` before configure `power1_crit`, because power
     value is calculated based on `shunt_resistor` set.
   - Because of the underlying register implementation, only one `*crit` setting
     and its `alarm` can be active. Writing to one `*crit` setting clears other
     `*crit` settings and alarms. Writing 0 to any `*crit` setting clears all
     `*crit` settings and alarms.
+183 −0
Original line number Diff line number Diff line
@@ -74,6 +74,17 @@
#define INA226_READ_AVG(reg)		(((reg) & INA226_AVG_RD_MASK) >> 9)
#define INA226_SHIFT_AVG(val)		((val) << 9)

/* bit number of alert functions in Mask/Enable Register */
#define INA226_SHUNT_OVER_VOLTAGE_BIT	15
#define INA226_SHUNT_UNDER_VOLTAGE_BIT	14
#define INA226_BUS_OVER_VOLTAGE_BIT	13
#define INA226_BUS_UNDER_VOLTAGE_BIT	12
#define INA226_POWER_OVER_LIMIT_BIT	11

/* bit mask for alert config bits of Mask/Enable Register */
#define INA226_ALERT_CONFIG_MASK	0xFC00
#define INA226_ALERT_FUNCTION_FLAG	BIT(4)

/* common attrs, ina226 attrs and NULL */
#define INA2XX_MAX_ATTRIBUTE_GROUPS	3

@@ -303,6 +314,145 @@ static ssize_t ina2xx_value_show(struct device *dev,
			ina2xx_get_value(data, attr->index, regval));
}

static int ina226_reg_to_alert(struct ina2xx_data *data, u8 bit, u16 regval)
{
	int reg;

	switch (bit) {
	case INA226_SHUNT_OVER_VOLTAGE_BIT:
	case INA226_SHUNT_UNDER_VOLTAGE_BIT:
		reg = INA2XX_SHUNT_VOLTAGE;
		break;
	case INA226_BUS_OVER_VOLTAGE_BIT:
	case INA226_BUS_UNDER_VOLTAGE_BIT:
		reg = INA2XX_BUS_VOLTAGE;
		break;
	case INA226_POWER_OVER_LIMIT_BIT:
		reg = INA2XX_POWER;
		break;
	default:
		/* programmer goofed */
		WARN_ON_ONCE(1);
		return 0;
	}

	return ina2xx_get_value(data, reg, regval);
}

/*
 * Turns alert limit values into register values.
 * Opposite of the formula in ina2xx_get_value().
 */
static s16 ina226_alert_to_reg(struct ina2xx_data *data, u8 bit, int val)
{
	switch (bit) {
	case INA226_SHUNT_OVER_VOLTAGE_BIT:
	case INA226_SHUNT_UNDER_VOLTAGE_BIT:
		val *= data->config->shunt_div;
		return clamp_val(val, SHRT_MIN, SHRT_MAX);
	case INA226_BUS_OVER_VOLTAGE_BIT:
	case INA226_BUS_UNDER_VOLTAGE_BIT:
		val = (val * 1000) << data->config->bus_voltage_shift;
		val = DIV_ROUND_CLOSEST(val, data->config->bus_voltage_lsb);
		return clamp_val(val, 0, SHRT_MAX);
	case INA226_POWER_OVER_LIMIT_BIT:
		val = DIV_ROUND_CLOSEST(val, data->power_lsb_uW);
		return clamp_val(val, 0, USHRT_MAX);
	default:
		/* programmer goofed */
		WARN_ON_ONCE(1);
		return 0;
	}
}

static ssize_t ina226_alert_show(struct device *dev,
				 struct device_attribute *da, char *buf)
{
	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
	struct ina2xx_data *data = dev_get_drvdata(dev);
	int regval;
	int val = 0;
	int ret;

	mutex_lock(&data->config_lock);
	ret = regmap_read(data->regmap, INA226_MASK_ENABLE, &regval);
	if (ret)
		goto abort;

	if (regval & BIT(attr->index)) {
		ret = regmap_read(data->regmap, INA226_ALERT_LIMIT, &regval);
		if (ret)
			goto abort;
		val = ina226_reg_to_alert(data, attr->index, regval);
	}

	ret = snprintf(buf, PAGE_SIZE, "%d\n", val);
abort:
	mutex_unlock(&data->config_lock);
	return ret;
}

static ssize_t ina226_alert_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 ina2xx_data *data = dev_get_drvdata(dev);
	unsigned long val;
	int ret;

	ret = kstrtoul(buf, 10, &val);
	if (ret < 0)
		return ret;

	/*
	 * Clear all alerts first to avoid accidentally triggering ALERT pin
	 * due to register write sequence. Then, only enable the alert
	 * if the value is non-zero.
	 */
	mutex_lock(&data->config_lock);
	ret = regmap_update_bits(data->regmap, INA226_MASK_ENABLE,
				 INA226_ALERT_CONFIG_MASK, 0);
	if (ret < 0)
		goto abort;

	ret = regmap_write(data->regmap, INA226_ALERT_LIMIT,
			   ina226_alert_to_reg(data, attr->index, val));
	if (ret < 0)
		goto abort;

	if (val != 0) {
		ret = regmap_update_bits(data->regmap, INA226_MASK_ENABLE,
					 INA226_ALERT_CONFIG_MASK,
					 BIT(attr->index));
		if (ret < 0)
			goto abort;
	}

	ret = count;
abort:
	mutex_unlock(&data->config_lock);
	return ret;
}

static ssize_t ina226_alarm_show(struct device *dev,
				 struct device_attribute *da, char *buf)
{
	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
	struct ina2xx_data *data = dev_get_drvdata(dev);
	int regval;
	int alarm = 0;
	int ret;

	ret = regmap_read(data->regmap, INA226_MASK_ENABLE, &regval);
	if (ret)
		return ret;

	alarm = (regval & BIT(attr->index)) &&
		(regval & INA226_ALERT_FUNCTION_FLAG);
	return snprintf(buf, PAGE_SIZE, "%d\n", alarm);
}

/*
 * In order to keep calibration register value fixed, the product
 * of current_lsb and shunt_resistor should also be fixed and equal
@@ -392,15 +542,38 @@ static ssize_t ina226_interval_show(struct device *dev,

/* shunt voltage */
static SENSOR_DEVICE_ATTR_RO(in0_input, ina2xx_value, INA2XX_SHUNT_VOLTAGE);
/* shunt voltage over/under voltage alert setting and alarm */
static SENSOR_DEVICE_ATTR_RW(in0_crit, ina226_alert,
			     INA226_SHUNT_OVER_VOLTAGE_BIT);
static SENSOR_DEVICE_ATTR_RW(in0_lcrit, ina226_alert,
			     INA226_SHUNT_UNDER_VOLTAGE_BIT);
static SENSOR_DEVICE_ATTR_RO(in0_crit_alarm, ina226_alarm,
			     INA226_SHUNT_OVER_VOLTAGE_BIT);
static SENSOR_DEVICE_ATTR_RO(in0_lcrit_alarm, ina226_alarm,
			     INA226_SHUNT_UNDER_VOLTAGE_BIT);

/* bus voltage */
static SENSOR_DEVICE_ATTR_RO(in1_input, ina2xx_value, INA2XX_BUS_VOLTAGE);
/* bus voltage over/under voltage alert setting and alarm */
static SENSOR_DEVICE_ATTR_RW(in1_crit, ina226_alert,
			     INA226_BUS_OVER_VOLTAGE_BIT);
static SENSOR_DEVICE_ATTR_RW(in1_lcrit, ina226_alert,
			     INA226_BUS_UNDER_VOLTAGE_BIT);
static SENSOR_DEVICE_ATTR_RO(in1_crit_alarm, ina226_alarm,
			     INA226_BUS_OVER_VOLTAGE_BIT);
static SENSOR_DEVICE_ATTR_RO(in1_lcrit_alarm, ina226_alarm,
			     INA226_BUS_UNDER_VOLTAGE_BIT);

/* calculated current */
static SENSOR_DEVICE_ATTR_RO(curr1_input, ina2xx_value, INA2XX_CURRENT);

/* calculated power */
static SENSOR_DEVICE_ATTR_RO(power1_input, ina2xx_value, INA2XX_POWER);
/* over-limit power alert setting and alarm */
static SENSOR_DEVICE_ATTR_RW(power1_crit, ina226_alert,
			     INA226_POWER_OVER_LIMIT_BIT);
static SENSOR_DEVICE_ATTR_RO(power1_crit_alarm, ina226_alarm,
			     INA226_POWER_OVER_LIMIT_BIT);

/* shunt resistance */
static SENSOR_DEVICE_ATTR_RW(shunt_resistor, ina2xx_shunt, INA2XX_CALIBRATION);
@@ -423,6 +596,16 @@ static const struct attribute_group ina2xx_group = {
};

static struct attribute *ina226_attrs[] = {
	&sensor_dev_attr_in0_crit.dev_attr.attr,
	&sensor_dev_attr_in0_lcrit.dev_attr.attr,
	&sensor_dev_attr_in0_crit_alarm.dev_attr.attr,
	&sensor_dev_attr_in0_lcrit_alarm.dev_attr.attr,
	&sensor_dev_attr_in1_crit.dev_attr.attr,
	&sensor_dev_attr_in1_lcrit.dev_attr.attr,
	&sensor_dev_attr_in1_crit_alarm.dev_attr.attr,
	&sensor_dev_attr_in1_lcrit_alarm.dev_attr.attr,
	&sensor_dev_attr_power1_crit.dev_attr.attr,
	&sensor_dev_attr_power1_crit_alarm.dev_attr.attr,
	&sensor_dev_attr_update_interval.dev_attr.attr,
	NULL,
};