hwmon: (nct6775) Add support for fan speed attributes
authorGuenter Roeck <linux@roeck-us.net>
Tue, 4 Dec 2012 15:56:24 +0000 (07:56 -0800)
committerGuenter Roeck <linux@roeck-us.net>
Mon, 8 Apr 2013 04:16:39 +0000 (21:16 -0700)
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Documentation/hwmon/nct6775
drivers/hwmon/nct6775.c

index ccfd5cc..dcd56a3 100644 (file)
@@ -53,8 +53,9 @@ triggered if the rotation speed has dropped below a programmable limit. On
 NCT6775F, fan readings can be divided by a programmable divider (1, 2, 4, 8,
 16, 32, 64 or 128) to give the readings more range or accuracy; the other chips
 do not have a fan speed divider. The driver sets the most suitable fan divisor
-itself; specifically, it doubles the divider value each time a fan speed reading
-returns an invalid value. Some fans might not be present because they share pins
+itself; specifically, it increases the divider value each time a fan speed
+reading returns an invalid value, and it reduces it if the fan speed reading
+is lower than optimal. Some fans might not be present because they share pins
 with other functions.
 
 Voltage sensors (also known as IN sensors) report their values in millivolts.
index fd0dd15..bafcae5 100644 (file)
@@ -180,6 +180,9 @@ static const u16 NCT6775_REG_IN[] = {
 #define NCT6775_REG_VBAT               0x5D
 #define NCT6775_REG_DIODE              0x5E
 
+#define NCT6775_REG_FANDIV1            0x506
+#define NCT6775_REG_FANDIV2            0x507
+
 static const u16 NCT6775_REG_ALARM[NUM_REG_ALARM] = { 0x459, 0x45A, 0x45B };
 
 /* 0..15 voltages, 16..23 fans, 24..31 temperatures */
@@ -193,12 +196,16 @@ static const s8 NCT6775_ALARM_BITS[] = {
        4, 5, 13, -1, -1, -1,           /* temp1..temp6 */
        12, -1 };                       /* intrusion0, intrusion1 */
 
+#define FAN_ALARM_BASE         16
 #define TEMP_ALARM_BASE                24
 #define INTRUSION_ALARM_BASE   30
 
 static const u8 NCT6775_REG_CR_CASEOPEN_CLR[] = { 0xe6, 0xee };
 static const u8 NCT6775_CR_CASEOPEN_CLR_MASK[] = { 0x20, 0x01 };
 
+static const u16 NCT6775_REG_FAN[] = { 0x630, 0x632, 0x634, 0x636, 0x638 };
+static const u16 NCT6775_REG_FAN_MIN[] = { 0x3b, 0x3c, 0x3d };
+
 static const u16 NCT6775_REG_TEMP[] = {
        0x27, 0x150, 0x250, 0x62b, 0x62c, 0x62d };
 
@@ -256,6 +263,8 @@ static const s8 NCT6776_ALARM_BITS[] = {
        4, 5, 13, -1, -1, -1,           /* temp1..temp6 */
        12, 9 };                        /* intrusion0, intrusion1 */
 
+static const u16 NCT6776_REG_FAN_MIN[] = { 0x63a, 0x63c, 0x63e, 0x640, 0x642 };
+
 static const u16 NCT6776_REG_TEMP_CONFIG[ARRAY_SIZE(NCT6775_REG_TEMP)] = {
        0x18, 0x152, 0x252, 0x628, 0x629, 0x62A };
 
@@ -309,6 +318,8 @@ static const s8 NCT6779_ALARM_BITS[] = {
        4, 5, 13, -1, -1, -1,           /* temp1..temp6 */
        12, 9 };                        /* intrusion0, intrusion1 */
 
+static const u16 NCT6779_REG_FAN[] = { 0x4b0, 0x4b2, 0x4b4, 0x4b6, 0x4b8 };
+
 static const u16 NCT6779_REG_TEMP[] = { 0x27, 0x150 };
 static const u16 NCT6779_REG_TEMP_CONFIG[ARRAY_SIZE(NCT6779_REG_TEMP)] = {
        0x18, 0x152 };
@@ -363,6 +374,44 @@ static const u16 NCT6779_REG_TEMP_CRIT[ARRAY_SIZE(nct6779_temp_label) - 1]
  * Conversions
  */
 
+static unsigned int fan_from_reg8(u16 reg, unsigned int divreg)
+{
+       if (reg == 0 || reg == 255)
+               return 0;
+       return 1350000U / (reg << divreg);
+}
+
+static unsigned int fan_from_reg13(u16 reg, unsigned int divreg)
+{
+       if ((reg & 0xff1f) == 0xff1f)
+               return 0;
+
+       reg = (reg & 0x1f) | ((reg & 0xff00) >> 3);
+
+       if (reg == 0)
+               return 0;
+
+       return 1350000U / reg;
+}
+
+static unsigned int fan_from_reg16(u16 reg, unsigned int divreg)
+{
+       if (reg == 0 || reg == 0xffff)
+               return 0;
+
+       /*
+        * Even though the registers are 16 bit wide, the fan divisor
+        * still applies.
+        */
+       return 1350000U / (reg << divreg);
+}
+
+static inline unsigned int
+div_from_reg(u8 reg)
+{
+       return 1 << reg;
+}
+
 /*
  * Some of the voltage inputs have internal scaling, the tables below
  * contain 8 (the ADC LSB in mV) * scaling factor * 100
@@ -411,12 +460,17 @@ struct nct6775_data {
        const u16 *REG_VIN;
        const u16 *REG_IN_MINMAX[2];
 
-       const u16 *REG_TEMP_SOURCE;     /* temp register sources */
+       const u16 *REG_FAN;
+       const u16 *REG_FAN_MIN;
 
+       const u16 *REG_TEMP_SOURCE;     /* temp register sources */
        const u16 *REG_TEMP_OFFSET;
 
        const u16 *REG_ALARM;
 
+       unsigned int (*fan_from_reg)(u16 reg, unsigned int divreg);
+       unsigned int (*fan_from_reg_min)(u16 reg, unsigned int divreg);
+
        struct mutex update_lock;
        bool valid;             /* true if following fields are valid */
        unsigned long last_updated;     /* In jiffies */
@@ -425,6 +479,12 @@ struct nct6775_data {
        u8 bank;                /* current register bank */
        u8 in_num;              /* number of in inputs we have */
        u8 in[15][3];           /* [0]=in, [1]=in_max, [2]=in_min */
+       unsigned int rpm[5];
+       u16 fan_min[5];
+       u8 fan_div[5];
+       u8 has_fan;             /* some fan inputs can be disabled */
+       u8 has_fan_min;         /* some fans don't have min register */
+       bool has_fan_div;
 
        u8 temp_fixed_num;      /* 3 or 6 */
        u8 temp_type[NUM_TEMP_FIXED];
@@ -556,6 +616,153 @@ static int nct6775_write_temp(struct nct6775_data *data, u16 reg, u16 value)
        return nct6775_write_value(data, reg, value);
 }
 
+/* This function assumes that the caller holds data->update_lock */
+static void nct6775_write_fan_div(struct nct6775_data *data, int nr)
+{
+       u8 reg;
+
+       switch (nr) {
+       case 0:
+               reg = (nct6775_read_value(data, NCT6775_REG_FANDIV1) & 0x70)
+                   | (data->fan_div[0] & 0x7);
+               nct6775_write_value(data, NCT6775_REG_FANDIV1, reg);
+               break;
+       case 1:
+               reg = (nct6775_read_value(data, NCT6775_REG_FANDIV1) & 0x7)
+                   | ((data->fan_div[1] << 4) & 0x70);
+               nct6775_write_value(data, NCT6775_REG_FANDIV1, reg);
+               break;
+       case 2:
+               reg = (nct6775_read_value(data, NCT6775_REG_FANDIV2) & 0x70)
+                   | (data->fan_div[2] & 0x7);
+               nct6775_write_value(data, NCT6775_REG_FANDIV2, reg);
+               break;
+       case 3:
+               reg = (nct6775_read_value(data, NCT6775_REG_FANDIV2) & 0x7)
+                   | ((data->fan_div[3] << 4) & 0x70);
+               nct6775_write_value(data, NCT6775_REG_FANDIV2, reg);
+               break;
+       }
+}
+
+static void nct6775_write_fan_div_common(struct nct6775_data *data, int nr)
+{
+       if (data->kind == nct6775)
+               nct6775_write_fan_div(data, nr);
+}
+
+static void nct6775_update_fan_div(struct nct6775_data *data)
+{
+       u8 i;
+
+       i = nct6775_read_value(data, NCT6775_REG_FANDIV1);
+       data->fan_div[0] = i & 0x7;
+       data->fan_div[1] = (i & 0x70) >> 4;
+       i = nct6775_read_value(data, NCT6775_REG_FANDIV2);
+       data->fan_div[2] = i & 0x7;
+       if (data->has_fan & (1<<3))
+               data->fan_div[3] = (i & 0x70) >> 4;
+}
+
+static void nct6775_update_fan_div_common(struct nct6775_data *data)
+{
+       if (data->kind == nct6775)
+               nct6775_update_fan_div(data);
+}
+
+static void nct6775_init_fan_div(struct nct6775_data *data)
+{
+       int i;
+
+       nct6775_update_fan_div_common(data);
+       /*
+        * For all fans, start with highest divider value if the divider
+        * register is not initialized. This ensures that we get a
+        * reading from the fan count register, even if it is not optimal.
+        * We'll compute a better divider later on.
+        */
+       for (i = 0; i < 3; i++) {
+               if (!(data->has_fan & (1 << i)))
+                       continue;
+               if (data->fan_div[i] == 0) {
+                       data->fan_div[i] = 7;
+                       nct6775_write_fan_div_common(data, i);
+               }
+       }
+}
+
+static void nct6775_init_fan_common(struct device *dev,
+                                   struct nct6775_data *data)
+{
+       int i;
+       u8 reg;
+
+       if (data->has_fan_div)
+               nct6775_init_fan_div(data);
+
+       /*
+        * If fan_min is not set (0), set it to 0xff to disable it. This
+        * prevents the unnecessary warning when fanX_min is reported as 0.
+        */
+       for (i = 0; i < 5; i++) {
+               if (data->has_fan_min & (1 << i)) {
+                       reg = nct6775_read_value(data, data->REG_FAN_MIN[i]);
+                       if (!reg)
+                               nct6775_write_value(data, data->REG_FAN_MIN[i],
+                                                   data->has_fan_div ? 0xff
+                                                                     : 0xff1f);
+               }
+       }
+}
+
+static void nct6775_select_fan_div(struct device *dev,
+                                  struct nct6775_data *data, int nr, u16 reg)
+{
+       u8 fan_div = data->fan_div[nr];
+       u16 fan_min;
+
+       if (!data->has_fan_div)
+               return;
+
+       /*
+        * If we failed to measure the fan speed, or the reported value is not
+        * in the optimal range, and the clock divider can be modified,
+        * let's try that for next time.
+        */
+       if (reg == 0x00 && fan_div < 0x07)
+               fan_div++;
+       else if (reg != 0x00 && reg < 0x30 && fan_div > 0)
+               fan_div--;
+
+       if (fan_div != data->fan_div[nr]) {
+               dev_dbg(dev, "Modifying fan%d clock divider from %u to %u\n",
+                       nr + 1, div_from_reg(data->fan_div[nr]),
+                       div_from_reg(fan_div));
+
+               /* Preserve min limit if possible */
+               if (data->has_fan_min & (1 << nr)) {
+                       fan_min = data->fan_min[nr];
+                       if (fan_div > data->fan_div[nr]) {
+                               if (fan_min != 255 && fan_min > 1)
+                                       fan_min >>= 1;
+                       } else {
+                               if (fan_min != 255) {
+                                       fan_min <<= 1;
+                                       if (fan_min > 254)
+                                               fan_min = 254;
+                               }
+                       }
+                       if (fan_min != data->fan_min[nr]) {
+                               data->fan_min[nr] = fan_min;
+                               nct6775_write_value(data, data->REG_FAN_MIN[nr],
+                                                   fan_min);
+                       }
+               }
+               data->fan_div[nr] = fan_div;
+               nct6775_write_fan_div_common(data, nr);
+       }
+}
+
 static struct nct6775_data *nct6775_update_device(struct device *dev)
 {
        struct nct6775_data *data = dev_get_drvdata(dev);
@@ -565,6 +772,9 @@ static struct nct6775_data *nct6775_update_device(struct device *dev)
 
        if (time_after(jiffies, data->last_updated + HZ + HZ/2)
            || !data->valid) {
+               /* Fan clock dividers */
+               nct6775_update_fan_div_common(data);
+
                /* Measured voltages and limits */
                for (i = 0; i < data->in_num; i++) {
                        if (!(data->have_in & (1 << i)))
@@ -578,6 +788,24 @@ static struct nct6775_data *nct6775_update_device(struct device *dev)
                                          data->REG_IN_MINMAX[1][i]);
                }
 
+               /* Measured fan speeds and limits */
+               for (i = 0; i < 5; i++) {
+                       u16 reg;
+
+                       if (!(data->has_fan & (1 << i)))
+                               continue;
+
+                       reg = nct6775_read_value(data, data->REG_FAN[i]);
+                       data->rpm[i] = data->fan_from_reg(reg,
+                                                         data->fan_div[i]);
+
+                       if (data->has_fan_min & (1 << i))
+                               data->fan_min[i] = nct6775_read_value(data,
+                                          data->REG_FAN_MIN[i]);
+
+                       nct6775_select_fan_div(dev, data, i, reg);
+               }
+
                /* Measured temperatures and limits */
                for (i = 0; i < NUM_TEMP; i++) {
                        if (!(data->have_temp & (1 << i)))
@@ -874,6 +1102,166 @@ static const struct attribute_group nct6775_group_in[15] = {
        { .attrs = nct6775_attributes_in[14] },
 };
 
+static ssize_t
+show_fan(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct nct6775_data *data = nct6775_update_device(dev);
+       struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+       int nr = sattr->index;
+       return sprintf(buf, "%d\n", data->rpm[nr]);
+}
+
+static ssize_t
+show_fan_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct nct6775_data *data = nct6775_update_device(dev);
+       struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+       int nr = sattr->index;
+       return sprintf(buf, "%d\n",
+                      data->fan_from_reg_min(data->fan_min[nr],
+                                             data->fan_div[nr]));
+}
+
+static ssize_t
+show_fan_div(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct nct6775_data *data = nct6775_update_device(dev);
+       struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+       int nr = sattr->index;
+       return sprintf(buf, "%u\n", div_from_reg(data->fan_div[nr]));
+}
+
+static ssize_t
+store_fan_min(struct device *dev, struct device_attribute *attr,
+             const char *buf, size_t count)
+{
+       struct nct6775_data *data = dev_get_drvdata(dev);
+       struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+       int nr = sattr->index;
+       unsigned long val;
+       int err;
+       unsigned int reg;
+       u8 new_div;
+
+       err = kstrtoul(buf, 10, &val);
+       if (err < 0)
+               return err;
+
+       mutex_lock(&data->update_lock);
+       if (!data->has_fan_div) {
+               /* NCT6776F or NCT6779D; we know this is a 13 bit register */
+               if (!val) {
+                       val = 0xff1f;
+               } else {
+                       if (val > 1350000U)
+                               val = 135000U;
+                       val = 1350000U / val;
+                       val = (val & 0x1f) | ((val << 3) & 0xff00);
+               }
+               data->fan_min[nr] = val;
+               goto write_min; /* Leave fan divider alone */
+       }
+       if (!val) {
+               /* No min limit, alarm disabled */
+               data->fan_min[nr] = 255;
+               new_div = data->fan_div[nr]; /* No change */
+               dev_info(dev, "fan%u low limit and alarm disabled\n", nr + 1);
+               goto write_div;
+       }
+       reg = 1350000U / val;
+       if (reg >= 128 * 255) {
+               /*
+                * Speed below this value cannot possibly be represented,
+                * even with the highest divider (128)
+                */
+               data->fan_min[nr] = 254;
+               new_div = 7; /* 128 == (1 << 7) */
+               dev_warn(dev,
+                        "fan%u low limit %lu below minimum %u, set to minimum\n",
+                        nr + 1, val, data->fan_from_reg_min(254, 7));
+       } else if (!reg) {
+               /*
+                * Speed above this value cannot possibly be represented,
+                * even with the lowest divider (1)
+                */
+               data->fan_min[nr] = 1;
+               new_div = 0; /* 1 == (1 << 0) */
+               dev_warn(dev,
+                        "fan%u low limit %lu above maximum %u, set to maximum\n",
+                        nr + 1, val, data->fan_from_reg_min(1, 0));
+       } else {
+               /*
+                * Automatically pick the best divider, i.e. the one such
+                * that the min limit will correspond to a register value
+                * in the 96..192 range
+                */
+               new_div = 0;
+               while (reg > 192 && new_div < 7) {
+                       reg >>= 1;
+                       new_div++;
+               }
+               data->fan_min[nr] = reg;
+       }
+
+write_div:
+       /*
+        * Write both the fan clock divider (if it changed) and the new
+        * fan min (unconditionally)
+        */
+       if (new_div != data->fan_div[nr]) {
+               dev_dbg(dev, "fan%u clock divider changed from %u to %u\n",
+                       nr + 1, div_from_reg(data->fan_div[nr]),
+                       div_from_reg(new_div));
+               data->fan_div[nr] = new_div;
+               nct6775_write_fan_div_common(data, nr);
+               /* Give the chip time to sample a new speed value */
+               data->last_updated = jiffies;
+       }
+
+write_min:
+       nct6775_write_value(data, data->REG_FAN_MIN[nr], data->fan_min[nr]);
+       mutex_unlock(&data->update_lock);
+
+       return count;
+}
+
+static struct sensor_device_attribute sda_fan_input[] = {
+       SENSOR_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0),
+       SENSOR_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1),
+       SENSOR_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2),
+       SENSOR_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3),
+       SENSOR_ATTR(fan5_input, S_IRUGO, show_fan, NULL, 4),
+};
+
+static struct sensor_device_attribute sda_fan_alarm[] = {
+       SENSOR_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, FAN_ALARM_BASE),
+       SENSOR_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, FAN_ALARM_BASE + 1),
+       SENSOR_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, FAN_ALARM_BASE + 2),
+       SENSOR_ATTR(fan4_alarm, S_IRUGO, show_alarm, NULL, FAN_ALARM_BASE + 3),
+       SENSOR_ATTR(fan5_alarm, S_IRUGO, show_alarm, NULL, FAN_ALARM_BASE + 4),
+};
+
+static struct sensor_device_attribute sda_fan_min[] = {
+       SENSOR_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan_min,
+                   store_fan_min, 0),
+       SENSOR_ATTR(fan2_min, S_IWUSR | S_IRUGO, show_fan_min,
+                   store_fan_min, 1),
+       SENSOR_ATTR(fan3_min, S_IWUSR | S_IRUGO, show_fan_min,
+                   store_fan_min, 2),
+       SENSOR_ATTR(fan4_min, S_IWUSR | S_IRUGO, show_fan_min,
+                   store_fan_min, 3),
+       SENSOR_ATTR(fan5_min, S_IWUSR | S_IRUGO, show_fan_min,
+                   store_fan_min, 4),
+};
+
+static struct sensor_device_attribute sda_fan_div[] = {
+       SENSOR_ATTR(fan1_div, S_IRUGO, show_fan_div, NULL, 0),
+       SENSOR_ATTR(fan2_div, S_IRUGO, show_fan_div, NULL, 1),
+       SENSOR_ATTR(fan3_div, S_IRUGO, show_fan_div, NULL, 2),
+       SENSOR_ATTR(fan4_div, S_IRUGO, show_fan_div, NULL, 3),
+       SENSOR_ATTR(fan5_div, S_IRUGO, show_fan_div, NULL, 4),
+};
+
 static ssize_t
 show_temp_label(struct device *dev, struct device_attribute *attr, char *buf)
 {
@@ -1228,6 +1616,12 @@ static void nct6775_device_remove_files(struct device *dev)
        for (i = 0; i < data->in_num; i++)
                sysfs_remove_group(&dev->kobj, &nct6775_group_in[i]);
 
+       for (i = 0; i < 5; i++) {
+               device_remove_file(dev, &sda_fan_input[i].dev_attr);
+               device_remove_file(dev, &sda_fan_alarm[i].dev_attr);
+               device_remove_file(dev, &sda_fan_div[i].dev_attr);
+               device_remove_file(dev, &sda_fan_min[i].dev_attr);
+       }
        for (i = 0; i < NUM_TEMP; i++) {
                if (!(data->have_temp & (1 << i)))
                        continue;
@@ -1294,6 +1688,75 @@ static inline void nct6775_init_device(struct nct6775_data *data)
        }
 }
 
+static int
+nct6775_check_fan_inputs(const struct nct6775_sio_data *sio_data,
+                        struct nct6775_data *data)
+{
+       int regval;
+       bool fan3pin, fan3min, fan4pin, fan4min, fan5pin;
+       int ret;
+
+       ret = superio_enter(sio_data->sioreg);
+       if (ret)
+               return ret;
+
+       /* fan4 and fan5 share some pins with the GPIO and serial flash */
+       if (data->kind == nct6775) {
+               regval = superio_inb(sio_data->sioreg, 0x2c);
+
+               fan3pin = regval & (1 << 6);
+               fan3min = fan3pin;
+
+               /* On NCT6775, fan4 shares pins with the fdc interface */
+               fan4pin = !(superio_inb(sio_data->sioreg, 0x2A) & 0x80);
+               fan4min = 0;
+               fan5pin = 0;
+       } else if (data->kind == nct6776) {
+               bool gpok = superio_inb(sio_data->sioreg, 0x27) & 0x80;
+
+               superio_select(sio_data->sioreg, NCT6775_LD_HWM);
+               regval = superio_inb(sio_data->sioreg, SIO_REG_ENABLE);
+
+               if (regval & 0x80)
+                       fan3pin = gpok;
+               else
+                       fan3pin = !(superio_inb(sio_data->sioreg, 0x24) & 0x40);
+
+               if (regval & 0x40)
+                       fan4pin = gpok;
+               else
+                       fan4pin = superio_inb(sio_data->sioreg, 0x1C) & 0x01;
+
+               if (regval & 0x20)
+                       fan5pin = gpok;
+               else
+                       fan5pin = superio_inb(sio_data->sioreg, 0x1C) & 0x02;
+
+               fan4min = fan4pin;
+               fan3min = fan3pin;
+       } else {        /* NCT6779D */
+               regval = superio_inb(sio_data->sioreg, 0x1c);
+
+               fan3pin = !(regval & (1 << 5));
+               fan4pin = !(regval & (1 << 6));
+               fan5pin = !(regval & (1 << 7));
+
+               fan3min = fan3pin;
+               fan4min = fan4pin;
+       }
+
+       superio_exit(sio_data->sioreg);
+
+       data->has_fan = data->has_fan_min = 0x03; /* fan1 and fan2 */
+       data->has_fan |= fan3pin << 2;
+       data->has_fan_min |= fan3min << 2;
+
+       data->has_fan |= (fan4pin << 3) | (fan5pin << 4);
+       data->has_fan_min |= (fan4min << 3) | (fan5pin << 4);
+
+       return 0;
+}
+
 static int nct6775_probe(struct platform_device *pdev)
 {
        struct device *dev = &pdev->dev;
@@ -1327,10 +1790,14 @@ static int nct6775_probe(struct platform_device *pdev)
        switch (data->kind) {
        case nct6775:
                data->in_num = 9;
+               data->has_fan_div = true;
                data->temp_fixed_num = 3;
 
                data->ALARM_BITS = NCT6775_ALARM_BITS;
 
+               data->fan_from_reg = fan_from_reg16;
+               data->fan_from_reg_min = fan_from_reg8;
+
                data->temp_label = nct6775_temp_label;
                data->temp_label_num = ARRAY_SIZE(nct6775_temp_label);
 
@@ -1340,6 +1807,8 @@ static int nct6775_probe(struct platform_device *pdev)
                data->REG_VIN = NCT6775_REG_IN;
                data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN;
                data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX;
+               data->REG_FAN = NCT6775_REG_FAN;
+               data->REG_FAN_MIN = NCT6775_REG_FAN_MIN;
                data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET;
                data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE;
                data->REG_ALARM = NCT6775_REG_ALARM;
@@ -1355,10 +1824,14 @@ static int nct6775_probe(struct platform_device *pdev)
                break;
        case nct6776:
                data->in_num = 9;
+               data->has_fan_div = false;
                data->temp_fixed_num = 3;
 
                data->ALARM_BITS = NCT6776_ALARM_BITS;
 
+               data->fan_from_reg = fan_from_reg13;
+               data->fan_from_reg_min = fan_from_reg13;
+
                data->temp_label = nct6776_temp_label;
                data->temp_label_num = ARRAY_SIZE(nct6776_temp_label);
 
@@ -1368,6 +1841,8 @@ static int nct6775_probe(struct platform_device *pdev)
                data->REG_VIN = NCT6775_REG_IN;
                data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN;
                data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX;
+               data->REG_FAN = NCT6775_REG_FAN;
+               data->REG_FAN_MIN = NCT6776_REG_FAN_MIN;
                data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET;
                data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE;
                data->REG_ALARM = NCT6775_REG_ALARM;
@@ -1383,10 +1858,14 @@ static int nct6775_probe(struct platform_device *pdev)
                break;
        case nct6779:
                data->in_num = 15;
+               data->has_fan_div = false;
                data->temp_fixed_num = 6;
 
                data->ALARM_BITS = NCT6779_ALARM_BITS;
 
+               data->fan_from_reg = fan_from_reg13;
+               data->fan_from_reg_min = fan_from_reg13;
+
                data->temp_label = nct6779_temp_label;
                data->temp_label_num = ARRAY_SIZE(nct6779_temp_label);
 
@@ -1396,6 +1875,8 @@ static int nct6775_probe(struct platform_device *pdev)
                data->REG_VIN = NCT6779_REG_IN;
                data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN;
                data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX;
+               data->REG_FAN = NCT6779_REG_FAN;
+               data->REG_FAN_MIN = NCT6776_REG_FAN_MIN;
                data->REG_TEMP_OFFSET = NCT6779_REG_TEMP_OFFSET;
                data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE;
                data->REG_ALARM = NCT6779_REG_ALARM;
@@ -1575,6 +2056,13 @@ static int nct6775_probe(struct platform_device *pdev)
        if (err)
                return err;
 
+       err = nct6775_check_fan_inputs(sio_data, data);
+       if (err)
+               goto exit_remove;
+
+       /* Read fan clock dividers immediately */
+       nct6775_init_fan_common(dev, data);
+
        for (i = 0; i < data->in_num; i++) {
                if (!(data->have_in & (1 << i)))
                        continue;
@@ -1583,6 +2071,32 @@ static int nct6775_probe(struct platform_device *pdev)
                        goto exit_remove;
        }
 
+       for (i = 0; i < 5; i++) {
+               if (data->has_fan & (1 << i)) {
+                       err = device_create_file(dev,
+                                                &sda_fan_input[i].dev_attr);
+                       if (err)
+                               goto exit_remove;
+                       err = device_create_file(dev,
+                                                &sda_fan_alarm[i].dev_attr);
+                       if (err)
+                               goto exit_remove;
+                       if (data->kind != nct6776 &&
+                           data->kind != nct6779) {
+                               err = device_create_file(dev,
+                                               &sda_fan_div[i].dev_attr);
+                               if (err)
+                                       goto exit_remove;
+                       }
+                       if (data->has_fan_min & (1 << i)) {
+                               err = device_create_file(dev,
+                                               &sda_fan_min[i].dev_attr);
+                               if (err)
+                                       goto exit_remove;
+                       }
+               }
+       }
+
        for (i = 0; i < NUM_TEMP; i++) {
                if (!(data->have_temp & (1 << i)))
                        continue;