hwmon: (ina2xx) Implement alert functions
authorAlex Qiu <xqiu@google.com>
Tue, 5 May 2020 00:59:45 +0000 (17:59 -0700)
committerGuenter Roeck <linux@roeck-us.net>
Fri, 22 May 2020 13:28:38 +0000 (06:28 -0700)
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: Alex Qiu <xqiu@google.com>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Documentation/hwmon/ina2xx.rst
drivers/hwmon/ina2xx.c

index 94b9a260c5185579b30c67f80c3feb2d6ebfef92..ed81f541633181637742961f11cc8437acda5143 100644 (file)
@@ -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.
index e9e78c0b7212441c188dd6aae2c22e89bd15ee2e..55d474ec7c35fea15e4ffa00ad92b6871687636e 100644 (file)
 #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,
 };