hwmon: (pmbus) Introduce infrastructure to detect sensors and limit registers

This commit replaces hard-coded sensor and limit register detection code
with table based sensor detection. This change eliminates code block repetition
and reduces code size.

Signed-off-by: Guenter Roeck <guenter.roeck@ericsson.com>
Reviewed-by: Tom Grennan <tom.grennan@ericsson.com>
diff --git a/drivers/hwmon/pmbus_core.c b/drivers/hwmon/pmbus_core.c
index 196ffaf..4e42a29 100644
--- a/drivers/hwmon/pmbus_core.c
+++ b/drivers/hwmon/pmbus_core.c
@@ -793,53 +793,6 @@
 	data->num_labels++;
 }
 
-static const int pmbus_temp_registers[] = {
-	PMBUS_READ_TEMPERATURE_1,
-	PMBUS_READ_TEMPERATURE_2,
-	PMBUS_READ_TEMPERATURE_3
-};
-
-static const int pmbus_temp_flags[] = {
-	PMBUS_HAVE_TEMP,
-	PMBUS_HAVE_TEMP2,
-	PMBUS_HAVE_TEMP3
-};
-
-static const int pmbus_fan_registers[] = {
-	PMBUS_READ_FAN_SPEED_1,
-	PMBUS_READ_FAN_SPEED_2,
-	PMBUS_READ_FAN_SPEED_3,
-	PMBUS_READ_FAN_SPEED_4
-};
-
-static const int pmbus_fan_config_registers[] = {
-	PMBUS_FAN_CONFIG_12,
-	PMBUS_FAN_CONFIG_12,
-	PMBUS_FAN_CONFIG_34,
-	PMBUS_FAN_CONFIG_34
-};
-
-static const int pmbus_fan_status_registers[] = {
-	PMBUS_STATUS_FAN_12,
-	PMBUS_STATUS_FAN_12,
-	PMBUS_STATUS_FAN_34,
-	PMBUS_STATUS_FAN_34
-};
-
-static const u32 pmbus_fan_flags[] = {
-	PMBUS_HAVE_FAN12,
-	PMBUS_HAVE_FAN12,
-	PMBUS_HAVE_FAN34,
-	PMBUS_HAVE_FAN34
-};
-
-static const u32 pmbus_fan_status_flags[] = {
-	PMBUS_HAVE_STATUS_FAN12,
-	PMBUS_HAVE_STATUS_FAN12,
-	PMBUS_HAVE_STATUS_FAN34,
-	PMBUS_HAVE_STATUS_FAN34
-};
-
 /*
  * Determine maximum number of sensors, booleans, and labels.
  * To keep things simple, only make a rough high estimate.
@@ -900,499 +853,431 @@
 /*
  * Search for attributes. Allocate sensors, booleans, and labels as needed.
  */
-static void pmbus_find_attributes(struct i2c_client *client,
-				  struct pmbus_data *data)
+
+/*
+ * The pmbus_limit_attr structure describes a single limit attribute
+ * and its associated alarm attribute.
+ */
+struct pmbus_limit_attr {
+	u8 reg;			/* Limit register */
+	const char *attr;	/* Attribute name */
+	const char *alarm;	/* Alarm attribute name */
+	u32 sbit;		/* Alarm attribute status bit */
+};
+
+/*
+ * The pmbus_sensor_attr structure describes one sensor attribute. This
+ * description includes a reference to the associated limit attributes.
+ */
+struct pmbus_sensor_attr {
+	u8 reg;				/* sensor register */
+	enum pmbus_sensor_classes class;/* sensor class */
+	const char *label;		/* sensor label */
+	bool paged;			/* true if paged sensor */
+	bool update;			/* true if update needed */
+	bool compare;			/* true if compare function needed */
+	u32 func;			/* sensor mask */
+	u32 sfunc;			/* sensor status mask */
+	int sbase;			/* status base register */
+	u32 gbit;			/* generic status bit */
+	const struct pmbus_limit_attr *limit;/* limit registers */
+	int nlimit;			/* # of limit registers */
+};
+
+/*
+ * Add a set of limit attributes and, if supported, the associated
+ * alarm attributes.
+ */
+static bool pmbus_add_limit_attrs(struct i2c_client *client,
+				  struct pmbus_data *data,
+				  const struct pmbus_driver_info *info,
+				  const char *name, int index, int page,
+				  int cbase,
+				  const struct pmbus_sensor_attr *attr)
+{
+	const struct pmbus_limit_attr *l = attr->limit;
+	int nlimit = attr->nlimit;
+	bool have_alarm = false;
+	int i, cindex;
+
+	for (i = 0; i < nlimit; i++) {
+		if (pmbus_check_word_register(client, page, l->reg)) {
+			cindex = data->num_sensors;
+			pmbus_add_sensor(data, name, l->attr, index, page,
+					 l->reg, attr->class, attr->update,
+					 false);
+			if (info->func[page] & attr->sfunc) {
+				if (attr->compare) {
+					pmbus_add_boolean_cmp(data, name,
+						l->alarm, index,
+						cbase, cindex,
+						attr->sbase + page, l->sbit);
+				} else {
+					pmbus_add_boolean_reg(data, name,
+						l->alarm, index,
+						attr->sbase + page, l->sbit);
+				}
+				have_alarm = true;
+			}
+		}
+		l++;
+	}
+	return have_alarm;
+}
+
+static void pmbus_add_sensor_attrs_one(struct i2c_client *client,
+				       struct pmbus_data *data,
+				       const struct pmbus_driver_info *info,
+				       const char *name,
+				       int index, int page,
+				       const struct pmbus_sensor_attr *attr)
+{
+	bool have_alarm;
+	int cbase = data->num_sensors;
+
+	if (attr->label)
+		pmbus_add_label(data, name, index, attr->label,
+				attr->paged ? page + 1 : 0);
+	pmbus_add_sensor(data, name, "input", index, page, attr->reg,
+			 attr->class, true, true);
+	if (attr->sfunc) {
+		have_alarm = pmbus_add_limit_attrs(client, data, info, name,
+						   index, page, cbase, attr);
+		/*
+		 * Add generic alarm attribute only if there are no individual
+		 * alarm attributes, and if there is a global alarm bit.
+		 */
+		if (!have_alarm && attr->gbit)
+			pmbus_add_boolean_reg(data, name, "alarm", index,
+					      PB_STATUS_BASE + page,
+					      attr->gbit);
+	}
+}
+
+static void pmbus_add_sensor_attrs(struct i2c_client *client,
+				   struct pmbus_data *data,
+				   const char *name,
+				   const struct pmbus_sensor_attr *attrs,
+				   int nattrs)
 {
 	const struct pmbus_driver_info *info = data->info;
-	int page, i0, i1, in_index;
+	int index, i;
 
-	/*
-	 * Input voltage sensors
-	 */
-	in_index = 1;
-	if (info->func[0] & PMBUS_HAVE_VIN) {
-		bool have_alarm = false;
+	index = 1;
+	for (i = 0; i < nattrs; i++) {
+		int page, pages;
 
-		i0 = data->num_sensors;
-		pmbus_add_label(data, "in", in_index, "vin", 0);
-		pmbus_add_sensor(data, "in", "input", in_index, 0,
-				 PMBUS_READ_VIN, PSC_VOLTAGE_IN, true, true);
-		if (pmbus_check_word_register(client, 0,
-					      PMBUS_VIN_UV_WARN_LIMIT)) {
-			i1 = data->num_sensors;
-			pmbus_add_sensor(data, "in", "min", in_index,
-					 0, PMBUS_VIN_UV_WARN_LIMIT,
-					 PSC_VOLTAGE_IN, false, false);
-			if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) {
-				pmbus_add_boolean_reg(data, "in", "min_alarm",
-						      in_index,
-						      PB_STATUS_INPUT_BASE,
-						      PB_VOLTAGE_UV_WARNING);
-				have_alarm = true;
-			}
-		}
-		if (pmbus_check_word_register(client, 0,
-					      PMBUS_VIN_UV_FAULT_LIMIT)) {
-			i1 = data->num_sensors;
-			pmbus_add_sensor(data, "in", "lcrit", in_index,
-					 0, PMBUS_VIN_UV_FAULT_LIMIT,
-					 PSC_VOLTAGE_IN, false, false);
-			if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) {
-				pmbus_add_boolean_reg(data, "in", "lcrit_alarm",
-						      in_index,
-						      PB_STATUS_INPUT_BASE,
-						      PB_VOLTAGE_UV_FAULT);
-				have_alarm = true;
-			}
-		}
-		if (pmbus_check_word_register(client, 0,
-					      PMBUS_VIN_OV_WARN_LIMIT)) {
-			i1 = data->num_sensors;
-			pmbus_add_sensor(data, "in", "max", in_index,
-					 0, PMBUS_VIN_OV_WARN_LIMIT,
-					 PSC_VOLTAGE_IN, false, false);
-			if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) {
-				pmbus_add_boolean_reg(data, "in", "max_alarm",
-						      in_index,
-						      PB_STATUS_INPUT_BASE,
-						      PB_VOLTAGE_OV_WARNING);
-				have_alarm = true;
-			}
-		}
-		if (pmbus_check_word_register(client, 0,
-					      PMBUS_VIN_OV_FAULT_LIMIT)) {
-			i1 = data->num_sensors;
-			pmbus_add_sensor(data, "in", "crit", in_index,
-					 0, PMBUS_VIN_OV_FAULT_LIMIT,
-					 PSC_VOLTAGE_IN, false, false);
-			if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) {
-				pmbus_add_boolean_reg(data, "in", "crit_alarm",
-						      in_index,
-						      PB_STATUS_INPUT_BASE,
-						      PB_VOLTAGE_OV_FAULT);
-				have_alarm = true;
-			}
-		}
-		/*
-		 * Add generic alarm attribute only if there are no individual
-		 * attributes.
-		 */
-		if (!have_alarm)
-			pmbus_add_boolean_reg(data, "in", "alarm",
-					      in_index,
-					      PB_STATUS_BASE,
-					      PB_STATUS_VIN_UV);
-		in_index++;
-	}
-	if (info->func[0] & PMBUS_HAVE_VCAP) {
-		pmbus_add_label(data, "in", in_index, "vcap", 0);
-		pmbus_add_sensor(data, "in", "input", in_index, 0,
-				 PMBUS_READ_VCAP, PSC_VOLTAGE_IN, true, true);
-		in_index++;
-	}
-
-	/*
-	 * Output voltage sensors
-	 */
-	for (page = 0; page < info->pages; page++) {
-		bool have_alarm = false;
-
-		if (!(info->func[page] & PMBUS_HAVE_VOUT))
-			continue;
-
-		i0 = data->num_sensors;
-		pmbus_add_label(data, "in", in_index, "vout", page + 1);
-		pmbus_add_sensor(data, "in", "input", in_index, page,
-				 PMBUS_READ_VOUT, PSC_VOLTAGE_OUT, true, true);
-		if (pmbus_check_word_register(client, page,
-					      PMBUS_VOUT_UV_WARN_LIMIT)) {
-			i1 = data->num_sensors;
-			pmbus_add_sensor(data, "in", "min", in_index, page,
-					 PMBUS_VOUT_UV_WARN_LIMIT,
-					 PSC_VOLTAGE_OUT, false, false);
-			if (info->func[page] & PMBUS_HAVE_STATUS_VOUT) {
-				pmbus_add_boolean_reg(data, "in", "min_alarm",
-						      in_index,
-						      PB_STATUS_VOUT_BASE +
-						      page,
-						      PB_VOLTAGE_UV_WARNING);
-				have_alarm = true;
-			}
-		}
-		if (pmbus_check_word_register(client, page,
-					      PMBUS_VOUT_UV_FAULT_LIMIT)) {
-			i1 = data->num_sensors;
-			pmbus_add_sensor(data, "in", "lcrit", in_index, page,
-					 PMBUS_VOUT_UV_FAULT_LIMIT,
-					 PSC_VOLTAGE_OUT, false, false);
-			if (info->func[page] & PMBUS_HAVE_STATUS_VOUT) {
-				pmbus_add_boolean_reg(data, "in", "lcrit_alarm",
-						      in_index,
-						      PB_STATUS_VOUT_BASE +
-						      page,
-						      PB_VOLTAGE_UV_FAULT);
-				have_alarm = true;
-			}
-		}
-		if (pmbus_check_word_register(client, page,
-					      PMBUS_VOUT_OV_WARN_LIMIT)) {
-			i1 = data->num_sensors;
-			pmbus_add_sensor(data, "in", "max", in_index, page,
-					 PMBUS_VOUT_OV_WARN_LIMIT,
-					 PSC_VOLTAGE_OUT, false, false);
-			if (info->func[page] & PMBUS_HAVE_STATUS_VOUT) {
-				pmbus_add_boolean_reg(data, "in", "max_alarm",
-						      in_index,
-						      PB_STATUS_VOUT_BASE +
-						      page,
-						      PB_VOLTAGE_OV_WARNING);
-				have_alarm = true;
-			}
-		}
-		if (pmbus_check_word_register(client, page,
-					      PMBUS_VOUT_OV_FAULT_LIMIT)) {
-			i1 = data->num_sensors;
-			pmbus_add_sensor(data, "in", "crit", in_index, page,
-					 PMBUS_VOUT_OV_FAULT_LIMIT,
-					 PSC_VOLTAGE_OUT, false, false);
-			if (info->func[page] & PMBUS_HAVE_STATUS_VOUT) {
-				pmbus_add_boolean_reg(data, "in", "crit_alarm",
-						      in_index,
-						      PB_STATUS_VOUT_BASE +
-						      page,
-						      PB_VOLTAGE_OV_FAULT);
-				have_alarm = true;
-			}
-		}
-		/*
-		 * Add generic alarm attribute only if there are no individual
-		 * attributes.
-		 */
-		if (!have_alarm)
-			pmbus_add_boolean_reg(data, "in", "alarm",
-					      in_index,
-					      PB_STATUS_BASE + page,
-					      PB_STATUS_VOUT_OV);
-		in_index++;
-	}
-
-	/*
-	 * Current sensors
-	 */
-
-	/*
-	 * Input current sensors
-	 */
-	in_index = 1;
-	if (info->func[0] & PMBUS_HAVE_IIN) {
-		i0 = data->num_sensors;
-		pmbus_add_label(data, "curr", in_index, "iin", 0);
-		pmbus_add_sensor(data, "curr", "input", in_index, 0,
-				 PMBUS_READ_IIN, PSC_CURRENT_IN, true, true);
-		if (pmbus_check_word_register(client, 0,
-					      PMBUS_IIN_OC_WARN_LIMIT)) {
-			i1 = data->num_sensors;
-			pmbus_add_sensor(data, "curr", "max", in_index,
-					 0, PMBUS_IIN_OC_WARN_LIMIT,
-					 PSC_CURRENT_IN, false, false);
-			if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) {
-				pmbus_add_boolean_reg(data, "curr", "max_alarm",
-						      in_index,
-						      PB_STATUS_INPUT_BASE,
-						      PB_IIN_OC_WARNING);
-			}
-		}
-		if (pmbus_check_word_register(client, 0,
-					      PMBUS_IIN_OC_FAULT_LIMIT)) {
-			i1 = data->num_sensors;
-			pmbus_add_sensor(data, "curr", "crit", in_index,
-					 0, PMBUS_IIN_OC_FAULT_LIMIT,
-					 PSC_CURRENT_IN, false, false);
-			if (info->func[0] & PMBUS_HAVE_STATUS_INPUT)
-				pmbus_add_boolean_reg(data, "curr",
-						      "crit_alarm",
-						      in_index,
-						      PB_STATUS_INPUT_BASE,
-						      PB_IIN_OC_FAULT);
-		}
-		in_index++;
-	}
-
-	/*
-	 * Output current sensors
-	 */
-	for (page = 0; page < info->pages; page++) {
-		bool have_alarm = false;
-
-		if (!(info->func[page] & PMBUS_HAVE_IOUT))
-			continue;
-
-		i0 = data->num_sensors;
-		pmbus_add_label(data, "curr", in_index, "iout", page + 1);
-		pmbus_add_sensor(data, "curr", "input", in_index, page,
-				 PMBUS_READ_IOUT, PSC_CURRENT_OUT, true, true);
-		if (pmbus_check_word_register(client, page,
-					      PMBUS_IOUT_OC_WARN_LIMIT)) {
-			i1 = data->num_sensors;
-			pmbus_add_sensor(data, "curr", "max", in_index, page,
-					 PMBUS_IOUT_OC_WARN_LIMIT,
-					 PSC_CURRENT_OUT, false, false);
-			if (info->func[page] & PMBUS_HAVE_STATUS_IOUT) {
-				pmbus_add_boolean_reg(data, "curr", "max_alarm",
-						      in_index,
-						      PB_STATUS_IOUT_BASE +
-						      page, PB_IOUT_OC_WARNING);
-				have_alarm = true;
-			}
-		}
-		if (pmbus_check_word_register(client, page,
-					      PMBUS_IOUT_UC_FAULT_LIMIT)) {
-			i1 = data->num_sensors;
-			pmbus_add_sensor(data, "curr", "lcrit", in_index, page,
-					 PMBUS_IOUT_UC_FAULT_LIMIT,
-					 PSC_CURRENT_OUT, false, false);
-			if (info->func[page] & PMBUS_HAVE_STATUS_IOUT) {
-				pmbus_add_boolean_reg(data, "curr",
-						      "lcrit_alarm",
-						      in_index,
-						      PB_STATUS_IOUT_BASE +
-						      page, PB_IOUT_UC_FAULT);
-				have_alarm = true;
-			}
-		}
-		if (pmbus_check_word_register(client, page,
-					      PMBUS_IOUT_OC_FAULT_LIMIT)) {
-			i1 = data->num_sensors;
-			pmbus_add_sensor(data, "curr", "crit", in_index, page,
-					 PMBUS_IOUT_OC_FAULT_LIMIT,
-					 PSC_CURRENT_OUT, false, false);
-			if (info->func[page] & PMBUS_HAVE_STATUS_IOUT) {
-				pmbus_add_boolean_reg(data, "curr",
-						      "crit_alarm",
-						      in_index,
-						      PB_STATUS_IOUT_BASE +
-						      page, PB_IOUT_OC_FAULT);
-				have_alarm = true;
-			}
-		}
-		/*
-		 * Add generic alarm attribute only if there are no individual
-		 * attributes.
-		 */
-		if (!have_alarm)
-			pmbus_add_boolean_reg(data, "curr", "alarm",
-					      in_index,
-					      PB_STATUS_BASE + page,
-					      PB_STATUS_IOUT_OC);
-		in_index++;
-	}
-
-	/*
-	 * Power sensors
-	 */
-	/*
-	 * Input Power sensors
-	 */
-	in_index = 1;
-	if (info->func[0] & PMBUS_HAVE_PIN) {
-		i0 = data->num_sensors;
-		pmbus_add_label(data, "power", in_index, "pin", 0);
-		pmbus_add_sensor(data, "power", "input", in_index,
-				 0, PMBUS_READ_PIN, PSC_POWER, true, true);
-		if (pmbus_check_word_register(client, 0,
-					      PMBUS_PIN_OP_WARN_LIMIT)) {
-			i1 = data->num_sensors;
-			pmbus_add_sensor(data, "power", "max", in_index,
-					 0, PMBUS_PIN_OP_WARN_LIMIT, PSC_POWER,
-					 false, false);
-			if (info->func[0] & PMBUS_HAVE_STATUS_INPUT)
-				pmbus_add_boolean_reg(data, "power",
-						      "alarm",
-						      in_index,
-						      PB_STATUS_INPUT_BASE,
-						      PB_PIN_OP_WARNING);
-		}
-		in_index++;
-	}
-
-	/*
-	 * Output Power sensors
-	 */
-	for (page = 0; page < info->pages; page++) {
-		bool need_alarm = false;
-
-		if (!(info->func[page] & PMBUS_HAVE_POUT))
-			continue;
-
-		i0 = data->num_sensors;
-		pmbus_add_label(data, "power", in_index, "pout", page + 1);
-		pmbus_add_sensor(data, "power", "input", in_index, page,
-				 PMBUS_READ_POUT, PSC_POWER, true, true);
-		/*
-		 * Per hwmon sysfs API, power_cap is to be used to limit output
-		 * power.
-		 * We have two registers related to maximum output power,
-		 * PMBUS_POUT_MAX and PMBUS_POUT_OP_WARN_LIMIT.
-		 * PMBUS_POUT_MAX matches the powerX_cap attribute definition.
-		 * There is no attribute in the API to match
-		 * PMBUS_POUT_OP_WARN_LIMIT. We use powerX_max for now.
-		 */
-		if (pmbus_check_word_register(client, page, PMBUS_POUT_MAX)) {
-			i1 = data->num_sensors;
-			pmbus_add_sensor(data, "power", "cap", in_index, page,
-					 PMBUS_POUT_MAX, PSC_POWER,
-					 false, false);
-			need_alarm = true;
-		}
-		if (pmbus_check_word_register(client, page,
-					      PMBUS_POUT_OP_WARN_LIMIT)) {
-			i1 = data->num_sensors;
-			pmbus_add_sensor(data, "power", "max", in_index, page,
-					 PMBUS_POUT_OP_WARN_LIMIT, PSC_POWER,
-					 false, false);
-			need_alarm = true;
-		}
-		if (need_alarm && (info->func[page] & PMBUS_HAVE_STATUS_IOUT))
-			pmbus_add_boolean_reg(data, "power", "alarm",
-					      in_index,
-					      PB_STATUS_IOUT_BASE + page,
-					      PB_POUT_OP_WARNING
-					      | PB_POWER_LIMITING);
-
-		if (pmbus_check_word_register(client, page,
-					      PMBUS_POUT_OP_FAULT_LIMIT)) {
-			i1 = data->num_sensors;
-			pmbus_add_sensor(data, "power", "crit", in_index, page,
-					 PMBUS_POUT_OP_FAULT_LIMIT, PSC_POWER,
-					 false, false);
-			if (info->func[page] & PMBUS_HAVE_STATUS_IOUT)
-				pmbus_add_boolean_reg(data, "power",
-						      "crit_alarm",
-						      in_index,
-						      PB_STATUS_IOUT_BASE
-						      + page,
-						      PB_POUT_OP_FAULT);
-		}
-		in_index++;
-	}
-
-	/*
-	 * Temperature sensors
-	 */
-	in_index = 1;
-	for (page = 0; page < info->pages; page++) {
-		int t;
-
-		for (t = 0; t < ARRAY_SIZE(pmbus_temp_registers); t++) {
-			bool have_alarm = false;
-
-			/*
-			 * A PMBus chip may support any combination of
-			 * temperature registers on any page. So we can not
-			 * abort after a failure to detect a register, but have
-			 * to continue checking for all registers on all pages.
-			 */
-			if (!(info->func[page] & pmbus_temp_flags[t]))
+		pages = attrs->paged ? info->pages : 1;
+		for (page = 0; page < pages; page++) {
+			if (!(info->func[page] & attrs->func))
 				continue;
-
-			if (!pmbus_check_word_register
-			    (client, page, pmbus_temp_registers[t]))
-				continue;
-
-			i0 = data->num_sensors;
-			pmbus_add_sensor(data, "temp", "input", in_index, page,
-					 pmbus_temp_registers[t],
-					 PSC_TEMPERATURE, true, true);
-
-			/*
-			 * PMBus provides only one status register for TEMP1-3.
-			 * Thus, we can not use the status register to determine
-			 * which of the three sensors actually caused an alarm.
-			 * Always compare current temperature against the limit
-			 * registers to determine alarm conditions for a
-			 * specific sensor.
-			 *
-			 * Since there is only one set of limit registers for
-			 * up to three temperature sensors, we need to update
-			 * all limit registers after the limit was changed for
-			 * one of the sensors. This ensures that correct limits
-			 * are reported for all temperature sensors.
-			 */
-			if (pmbus_check_word_register
-			    (client, page, PMBUS_UT_WARN_LIMIT)) {
-				i1 = data->num_sensors;
-				pmbus_add_sensor(data, "temp", "min", in_index,
-						 page, PMBUS_UT_WARN_LIMIT,
-						 PSC_TEMPERATURE, true, false);
-				if (info->func[page] & PMBUS_HAVE_STATUS_TEMP) {
-					pmbus_add_boolean_cmp(data, "temp",
-						"min_alarm", in_index, i1, i0,
-						PB_STATUS_TEMP_BASE + page,
-						PB_TEMP_UT_WARNING);
-					have_alarm = true;
-				}
-			}
-			if (pmbus_check_word_register(client, page,
-						      PMBUS_UT_FAULT_LIMIT)) {
-				i1 = data->num_sensors;
-				pmbus_add_sensor(data, "temp", "lcrit",
-						 in_index, page,
-						 PMBUS_UT_FAULT_LIMIT,
-						 PSC_TEMPERATURE, true, false);
-				if (info->func[page] & PMBUS_HAVE_STATUS_TEMP) {
-					pmbus_add_boolean_cmp(data, "temp",
-						"lcrit_alarm", in_index, i1, i0,
-						PB_STATUS_TEMP_BASE + page,
-						PB_TEMP_UT_FAULT);
-					have_alarm = true;
-				}
-			}
-			if (pmbus_check_word_register
-			    (client, page, PMBUS_OT_WARN_LIMIT)) {
-				i1 = data->num_sensors;
-				pmbus_add_sensor(data, "temp", "max", in_index,
-						 page, PMBUS_OT_WARN_LIMIT,
-						 PSC_TEMPERATURE, true, false);
-				if (info->func[page] & PMBUS_HAVE_STATUS_TEMP) {
-					pmbus_add_boolean_cmp(data, "temp",
-						"max_alarm", in_index, i0, i1,
-						PB_STATUS_TEMP_BASE + page,
-						PB_TEMP_OT_WARNING);
-					have_alarm = true;
-				}
-			}
-			if (pmbus_check_word_register(client, page,
-						      PMBUS_OT_FAULT_LIMIT)) {
-				i1 = data->num_sensors;
-				pmbus_add_sensor(data, "temp", "crit", in_index,
-						 page, PMBUS_OT_FAULT_LIMIT,
-						 PSC_TEMPERATURE, true, false);
-				if (info->func[page] & PMBUS_HAVE_STATUS_TEMP) {
-					pmbus_add_boolean_cmp(data, "temp",
-						"crit_alarm", in_index, i0, i1,
-						PB_STATUS_TEMP_BASE + page,
-						PB_TEMP_OT_FAULT);
-					have_alarm = true;
-				}
-			}
-			/*
-			 * Last resort - we were not able to create any alarm
-			 * registers. Report alarm for all sensors using the
-			 * status register temperature alarm bit.
-			 */
-			if (!have_alarm)
-				pmbus_add_boolean_reg(data, "temp", "alarm",
-						      in_index,
-						      PB_STATUS_BASE + page,
-						      PB_STATUS_TEMPERATURE);
-			in_index++;
+			pmbus_add_sensor_attrs_one(client, data, info, name,
+						   index, page, attrs);
+			index++;
 		}
+		attrs++;
 	}
+}
 
-	/*
-	 * Fans
-	 */
-	in_index = 1;
+static const struct pmbus_limit_attr vin_limit_attrs[] = {
+	{
+		.reg = PMBUS_VIN_UV_WARN_LIMIT,
+		.attr = "min",
+		.alarm = "min_alarm",
+		.sbit = PB_VOLTAGE_UV_WARNING,
+	}, {
+		.reg = PMBUS_VIN_UV_FAULT_LIMIT,
+		.attr = "lcrit",
+		.alarm = "lcrit_alarm",
+		.sbit = PB_VOLTAGE_UV_FAULT,
+	}, {
+		.reg = PMBUS_VIN_OV_WARN_LIMIT,
+		.attr = "max",
+		.alarm = "max_alarm",
+		.sbit = PB_VOLTAGE_OV_WARNING,
+	}, {
+		.reg = PMBUS_VIN_OV_FAULT_LIMIT,
+		.attr = "crit",
+		.alarm = "crit_alarm",
+		.sbit = PB_VOLTAGE_OV_FAULT,
+	},
+};
+
+static const struct pmbus_limit_attr vout_limit_attrs[] = {
+	{
+		.reg = PMBUS_VOUT_UV_WARN_LIMIT,
+		.attr = "min",
+		.alarm = "min_alarm",
+		.sbit = PB_VOLTAGE_UV_WARNING,
+	}, {
+		.reg = PMBUS_VOUT_UV_FAULT_LIMIT,
+		.attr = "lcrit",
+		.alarm = "lcrit_alarm",
+		.sbit = PB_VOLTAGE_UV_FAULT,
+	}, {
+		.reg = PMBUS_VOUT_OV_WARN_LIMIT,
+		.attr = "max",
+		.alarm = "max_alarm",
+		.sbit = PB_VOLTAGE_OV_WARNING,
+	}, {
+		.reg = PMBUS_VOUT_OV_FAULT_LIMIT,
+		.attr = "crit",
+		.alarm = "crit_alarm",
+		.sbit = PB_VOLTAGE_OV_FAULT,
+	}
+};
+
+static const struct pmbus_sensor_attr voltage_attributes[] = {
+	{
+		.reg = PMBUS_READ_VIN,
+		.class = PSC_VOLTAGE_IN,
+		.label = "vin",
+		.func = PMBUS_HAVE_VIN,
+		.sfunc = PMBUS_HAVE_STATUS_INPUT,
+		.sbase = PB_STATUS_INPUT_BASE,
+		.gbit = PB_STATUS_VIN_UV,
+		.limit = vin_limit_attrs,
+		.nlimit = ARRAY_SIZE(vin_limit_attrs),
+	}, {
+		.reg = PMBUS_READ_VCAP,
+		.class = PSC_VOLTAGE_IN,
+		.label = "vcap",
+		.func = PMBUS_HAVE_VCAP,
+	}, {
+		.reg = PMBUS_READ_VOUT,
+		.class = PSC_VOLTAGE_OUT,
+		.label = "vout",
+		.paged = true,
+		.func = PMBUS_HAVE_VOUT,
+		.sfunc = PMBUS_HAVE_STATUS_VOUT,
+		.sbase = PB_STATUS_VOUT_BASE,
+		.gbit = PB_STATUS_VOUT_OV,
+		.limit = vout_limit_attrs,
+		.nlimit = ARRAY_SIZE(vout_limit_attrs),
+	}
+};
+
+/* Current attributes */
+
+static const struct pmbus_limit_attr iin_limit_attrs[] = {
+	{
+		.reg = PMBUS_IIN_OC_WARN_LIMIT,
+		.attr = "max",
+		.alarm = "max_alarm",
+		.sbit = PB_IIN_OC_WARNING,
+	}, {
+		.reg = PMBUS_IIN_OC_FAULT_LIMIT,
+		.attr = "crit",
+		.alarm = "crit_alarm",
+		.sbit = PB_IIN_OC_FAULT,
+	}
+};
+
+static const struct pmbus_limit_attr iout_limit_attrs[] = {
+	{
+		.reg = PMBUS_IOUT_OC_WARN_LIMIT,
+		.attr = "max",
+		.alarm = "max_alarm",
+		.sbit = PB_IOUT_OC_WARNING,
+	}, {
+		.reg = PMBUS_IOUT_UC_FAULT_LIMIT,
+		.attr = "lcrit",
+		.alarm = "lcrit_alarm",
+		.sbit = PB_IOUT_UC_FAULT,
+	}, {
+		.reg = PMBUS_IOUT_OC_FAULT_LIMIT,
+		.attr = "crit",
+		.alarm = "crit_alarm",
+		.sbit = PB_IOUT_OC_FAULT,
+	}
+};
+
+static const struct pmbus_sensor_attr current_attributes[] = {
+	{
+		.reg = PMBUS_READ_IIN,
+		.class = PSC_CURRENT_IN,
+		.label = "iin",
+		.func = PMBUS_HAVE_IIN,
+		.sfunc = PMBUS_HAVE_STATUS_INPUT,
+		.sbase = PB_STATUS_INPUT_BASE,
+		.limit = iin_limit_attrs,
+		.nlimit = ARRAY_SIZE(iin_limit_attrs),
+	}, {
+		.reg = PMBUS_READ_IOUT,
+		.class = PSC_CURRENT_OUT,
+		.label = "iout",
+		.paged = true,
+		.func = PMBUS_HAVE_IOUT,
+		.sfunc = PMBUS_HAVE_STATUS_IOUT,
+		.sbase = PB_STATUS_IOUT_BASE,
+		.gbit = PB_STATUS_IOUT_OC,
+		.limit = iout_limit_attrs,
+		.nlimit = ARRAY_SIZE(iout_limit_attrs),
+	}
+};
+
+/* Power attributes */
+
+static const struct pmbus_limit_attr pin_limit_attrs[] = {
+	{
+		.reg = PMBUS_PIN_OP_WARN_LIMIT,
+		.attr = "max",
+		.alarm = "alarm",
+		.sbit = PB_PIN_OP_WARNING,
+	}
+};
+
+static const struct pmbus_limit_attr pout_limit_attrs[] = {
+	{
+		.reg = PMBUS_POUT_MAX,
+		.attr = "cap",
+		.alarm = "cap_alarm",
+		.sbit = PB_POWER_LIMITING,
+	}, {
+		.reg = PMBUS_POUT_OP_WARN_LIMIT,
+		.attr = "max",
+		.alarm = "max_alarm",
+		.sbit = PB_POUT_OP_WARNING,
+	}, {
+		.reg = PMBUS_POUT_OP_FAULT_LIMIT,
+		.attr = "crit",
+		.alarm = "crit_alarm",
+		.sbit = PB_POUT_OP_FAULT,
+	}
+};
+
+static const struct pmbus_sensor_attr power_attributes[] = {
+	{
+		.reg = PMBUS_READ_PIN,
+		.class = PSC_POWER,
+		.label = "pin",
+		.func = PMBUS_HAVE_PIN,
+		.sfunc = PMBUS_HAVE_STATUS_INPUT,
+		.sbase = PB_STATUS_INPUT_BASE,
+		.limit = pin_limit_attrs,
+		.nlimit = ARRAY_SIZE(pin_limit_attrs),
+	}, {
+		.reg = PMBUS_READ_POUT,
+		.class = PSC_POWER,
+		.label = "pout",
+		.paged = true,
+		.func = PMBUS_HAVE_POUT,
+		.sfunc = PMBUS_HAVE_STATUS_IOUT,
+		.sbase = PB_STATUS_IOUT_BASE,
+		.limit = pout_limit_attrs,
+		.nlimit = ARRAY_SIZE(pout_limit_attrs),
+	}
+};
+
+/* Temperature atributes */
+
+static const struct pmbus_limit_attr temp_limit_attrs[] = {
+	{
+		.reg = PMBUS_UT_WARN_LIMIT,
+		.attr = "min",
+		.alarm = "min_alarm",
+		.sbit = PB_TEMP_UT_WARNING,
+	}, {
+		.reg = PMBUS_UT_FAULT_LIMIT,
+		.attr = "lcrit",
+		.alarm = "lcrit_alarm",
+		.sbit = PB_TEMP_UT_FAULT,
+	}, {
+		.reg = PMBUS_OT_WARN_LIMIT,
+		.attr = "max",
+		.alarm = "max_alarm",
+		.sbit = PB_TEMP_OT_WARNING,
+	}, {
+		.reg = PMBUS_OT_FAULT_LIMIT,
+		.attr = "crit",
+		.alarm = "crit_alarm",
+		.sbit = PB_TEMP_OT_FAULT,
+	}
+};
+
+static const struct pmbus_sensor_attr temp_attributes[] = {
+	{
+		.reg = PMBUS_READ_TEMPERATURE_1,
+		.class = PSC_TEMPERATURE,
+		.paged = true,
+		.update = true,
+		.compare = true,
+		.func = PMBUS_HAVE_TEMP,
+		.sfunc = PMBUS_HAVE_STATUS_TEMP,
+		.sbase = PB_STATUS_TEMP_BASE,
+		.gbit = PB_STATUS_TEMPERATURE,
+		.limit = temp_limit_attrs,
+		.nlimit = ARRAY_SIZE(temp_limit_attrs),
+	}, {
+		.reg = PMBUS_READ_TEMPERATURE_2,
+		.class = PSC_TEMPERATURE,
+		.paged = true,
+		.update = true,
+		.compare = true,
+		.func = PMBUS_HAVE_TEMP2,
+		.sfunc = PMBUS_HAVE_STATUS_TEMP,
+		.sbase = PB_STATUS_TEMP_BASE,
+		.gbit = PB_STATUS_TEMPERATURE,
+		.limit = temp_limit_attrs,
+		.nlimit = ARRAY_SIZE(temp_limit_attrs),
+	}, {
+		.reg = PMBUS_READ_TEMPERATURE_3,
+		.class = PSC_TEMPERATURE,
+		.paged = true,
+		.update = true,
+		.compare = true,
+		.func = PMBUS_HAVE_TEMP3,
+		.sfunc = PMBUS_HAVE_STATUS_TEMP,
+		.sbase = PB_STATUS_TEMP_BASE,
+		.gbit = PB_STATUS_TEMPERATURE,
+		.limit = temp_limit_attrs,
+		.nlimit = ARRAY_SIZE(temp_limit_attrs),
+	}
+};
+
+static const int pmbus_fan_registers[] = {
+	PMBUS_READ_FAN_SPEED_1,
+	PMBUS_READ_FAN_SPEED_2,
+	PMBUS_READ_FAN_SPEED_3,
+	PMBUS_READ_FAN_SPEED_4
+};
+
+static const int pmbus_fan_config_registers[] = {
+	PMBUS_FAN_CONFIG_12,
+	PMBUS_FAN_CONFIG_12,
+	PMBUS_FAN_CONFIG_34,
+	PMBUS_FAN_CONFIG_34
+};
+
+static const int pmbus_fan_status_registers[] = {
+	PMBUS_STATUS_FAN_12,
+	PMBUS_STATUS_FAN_12,
+	PMBUS_STATUS_FAN_34,
+	PMBUS_STATUS_FAN_34
+};
+
+static const u32 pmbus_fan_flags[] = {
+	PMBUS_HAVE_FAN12,
+	PMBUS_HAVE_FAN12,
+	PMBUS_HAVE_FAN34,
+	PMBUS_HAVE_FAN34
+};
+
+static const u32 pmbus_fan_status_flags[] = {
+	PMBUS_HAVE_STATUS_FAN12,
+	PMBUS_HAVE_STATUS_FAN12,
+	PMBUS_HAVE_STATUS_FAN34,
+	PMBUS_HAVE_STATUS_FAN34
+};
+
+/* Fans */
+static void pmbus_add_fan_attributes(struct i2c_client *client,
+				     struct pmbus_data *data)
+{
+	const struct pmbus_driver_info *info = data->info;
+	int index = 1;
+	int page;
+
 	for (page = 0; page < info->pages; page++) {
 		int f;
 
@@ -1419,8 +1304,7 @@
 			    (!(regval & (PB_FAN_1_INSTALLED >> ((f & 1) * 4)))))
 				continue;
 
-			i0 = data->num_sensors;
-			pmbus_add_sensor(data, "fan", "input", in_index, page,
+			pmbus_add_sensor(data, "fan", "input", index, page,
 					 pmbus_fan_registers[f], PSC_FAN, true,
 					 true);
 
@@ -1438,17 +1322,40 @@
 				else
 					base = PB_STATUS_FAN_BASE + page;
 				pmbus_add_boolean_reg(data, "fan", "alarm",
-					in_index, base,
+					index, base,
 					PB_FAN_FAN1_WARNING >> (f & 1));
 				pmbus_add_boolean_reg(data, "fan", "fault",
-					in_index, base,
+					index, base,
 					PB_FAN_FAN1_FAULT >> (f & 1));
 			}
-			in_index++;
+			index++;
 		}
 	}
 }
 
+static void pmbus_find_attributes(struct i2c_client *client,
+				  struct pmbus_data *data)
+{
+	/* Voltage sensors */
+	pmbus_add_sensor_attrs(client, data, "in", voltage_attributes,
+			       ARRAY_SIZE(voltage_attributes));
+
+	/* Current sensors */
+	pmbus_add_sensor_attrs(client, data, "curr", current_attributes,
+			       ARRAY_SIZE(current_attributes));
+
+	/* Power sensors */
+	pmbus_add_sensor_attrs(client, data, "power", power_attributes,
+			       ARRAY_SIZE(power_attributes));
+
+	/* Temperature sensors */
+	pmbus_add_sensor_attrs(client, data, "temp", temp_attributes,
+			       ARRAY_SIZE(temp_attributes));
+
+	/* Fans */
+	pmbus_add_fan_attributes(client, data);
+}
+
 /*
  * Identify chip parameters.
  * This function is called for all chips.