]> git.sur5r.net Git - groeck-nct6775/blobdiff - nct6775.c
Add basic support for NCT6795D
[groeck-nct6775] / nct6775.c
index 79633230369ad0fdce8a8ea0514c1ba795ca015b..20b17a4f8bec14472782e3887b0df238bd76847d 100644 (file)
--- a/nct6775.c
+++ b/nct6775.c
@@ -5,7 +5,7 @@
  * Copyright (C) 2012  Guenter Roeck <linux@roeck-us.net>
  *
  * Derived from w83627ehf driver
- * Copyright (C) 2005-2012  Jean Delvare <khali@linux-fr.org>
+ * Copyright (C) 2005-2012  Jean Delvare <jdelvare@suse.de>
  * Copyright (C) 2006  Yuan Mu (Winbond),
  *                    Rudolf Marek <r.marek@assembler.cz>
  *                    David Hubbard <david.c.hubbard@gmail.com>
@@ -38,7 +38,9 @@
  * nct6776f     9      5       3       6+3    0xc330 0xc1    0x5ca3
  * nct6779d    15      5       5       2+6    0xc560 0xc1    0x5ca3
  * nct6791d    15      6       6       2+6    0xc800 0xc1    0x5ca3
- * nct6792d    15      6       6       2+6    0xc911 0xc1    0x5ca3
+ * nct6792d    15      6       6       2+6    0xc910 0xc1    0x5ca3
+ * nct6793d    15      6       6       2+6    0xd120 0xc1    0x5ca3
+ * nct6795d    15?     6?      6?      2+6?   0xd350 0xc1    0x5ca3
  *
  * #temp lists the number of monitored temperature sources (first value) plus
  * the number of directly connectable temperature sensors (second value).
 #include <linux/err.h>
 #include <linux/mutex.h>
 #include <linux/acpi.h>
+#include <linux/dmi.h>
 #include <linux/io.h>
 #include "lm75.h"
 #include "compat.h"
 
 #define USE_ALTERNATE
 
-enum kinds { nct6106, nct6775, nct6776, nct6779, nct6791, nct6792 };
+enum kinds { nct6106, nct6775, nct6776, nct6779, nct6791, nct6792, nct6793,
+            nct6795 };
 
 /* used to set data->name = nct6775_device_names[data->sio_kind] */
 static const char * const nct6775_device_names[] = {
@@ -73,6 +77,19 @@ static const char * const nct6775_device_names[] = {
        "nct6779",
        "nct6791",
        "nct6792",
+       "nct6793",
+       "nct6795",
+};
+
+static const char * const nct6775_sio_names[] __initconst = {
+       "NCT6106D",
+       "NCT6775F",
+       "NCT6776D/F",
+       "NCT6779D",
+       "NCT6791D",
+       "NCT6792D",
+       "NCT6793D",
+       "NCT6795D",
 };
 
 static unsigned short force_id;
@@ -104,6 +121,8 @@ MODULE_PARM_DESC(fan_debounce, "Enable debouncing for fan RPM signal");
 #define SIO_NCT6779_ID         0xc560
 #define SIO_NCT6791_ID         0xc800
 #define SIO_NCT6792_ID         0xc910
+#define SIO_NCT6793_ID         0xd120
+#define SIO_NCT6795_ID         0xd350
 #define SIO_ID_MASK            0xFFF0
 
 enum pwm_enable { off, manual, thermal_cruise, speed_cruise, sf3, sf4 };
@@ -182,6 +201,8 @@ superio_exit(int ioreg)
 
 #define NUM_FAN                6
 
+#define TEMP_SOURCE_VIRTUAL    0x1f
+
 /* Common and NCT6775 specific data */
 
 /* Voltage min/max registers for nr=7..14 are in bank 5 */
@@ -354,6 +375,10 @@ static const u16 NCT6775_REG_TEMP_CRIT[ARRAY_SIZE(nct6775_temp_label) - 1]
 
 /* NCT6776 specific data */
 
+/* STEP_UP_TIME and STEP_DOWN_TIME regs are swapped for all chips but NCT6775 */
+#define NCT6776_REG_FAN_STEP_UP_TIME NCT6775_REG_FAN_STEP_DOWN_TIME
+#define NCT6776_REG_FAN_STEP_DOWN_TIME NCT6775_REG_FAN_STEP_UP_TIME
+
 static const s8 NCT6776_ALARM_BITS[] = {
        0, 1, 2, 3, 8, 21, 20, 16,      /* in0.. in7 */
        17, -1, -1, -1, -1, -1, -1,     /* in8..in14 */
@@ -498,16 +523,24 @@ static const char *const nct6779_temp_label[] = {
        "PCH_DIM1_TEMP",
        "PCH_DIM2_TEMP",
        "PCH_DIM3_TEMP",
-       "BYTE_TEMP"
+       "BYTE_TEMP",
+       "",
+       "",
+       "",
+       "",
+       "Virtual_TEMP"
 };
 
-static const u16 NCT6779_REG_TEMP_ALTERNATE[ARRAY_SIZE(nct6779_temp_label) - 1]
+#define NCT6779_NUM_LABELS     (ARRAY_SIZE(nct6779_temp_label) - 5)
+#define NCT6791_NUM_LABELS     ARRAY_SIZE(nct6779_temp_label)
+
+static const u16 NCT6779_REG_TEMP_ALTERNATE[NCT6791_NUM_LABELS - 1]
        = { 0x490, 0x491, 0x492, 0x493, 0x494, 0x495, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0,
            0, 0x400, 0x401, 0x402, 0x404, 0x405, 0x406, 0x407,
            0x408, 0 };
 
-static const u16 NCT6779_REG_TEMP_CRIT[ARRAY_SIZE(nct6779_temp_label) - 1]
+static const u16 NCT6779_REG_TEMP_CRIT[NCT6791_NUM_LABELS - 1]
        = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x709, 0x70a };
 
 /* NCT6791 specific data */
@@ -533,6 +566,82 @@ static const s8 NCT6791_ALARM_BITS[] = {
        4, 5, 13, -1, -1, -1,           /* temp1..temp6 */
        12, 9 };                        /* intrusion0, intrusion1 */
 
+/* NCT6792/NCT6793 specific data */
+
+static const u16 NCT6792_REG_TEMP_MON[] = {
+       0x73, 0x75, 0x77, 0x79, 0x7b, 0x7d };
+static const u16 NCT6792_REG_BEEP[NUM_REG_BEEP] = {
+       0xb2, 0xb3, 0xb4, 0xb5, 0xbf };
+
+static const char *const nct6792_temp_label[] = {
+       "",
+       "SYSTIN",
+       "CPUTIN",
+       "AUXTIN0",
+       "AUXTIN1",
+       "AUXTIN2",
+       "AUXTIN3",
+       "",
+       "SMBUSMASTER 0",
+       "SMBUSMASTER 1",
+       "SMBUSMASTER 2",
+       "SMBUSMASTER 3",
+       "SMBUSMASTER 4",
+       "SMBUSMASTER 5",
+       "SMBUSMASTER 6",
+       "SMBUSMASTER 7",
+       "PECI Agent 0",
+       "PECI Agent 1",
+       "PCH_CHIP_CPU_MAX_TEMP",
+       "PCH_CHIP_TEMP",
+       "PCH_CPU_TEMP",
+       "PCH_MCH_TEMP",
+       "PCH_DIM0_TEMP",
+       "PCH_DIM1_TEMP",
+       "PCH_DIM2_TEMP",
+       "PCH_DIM3_TEMP",
+       "BYTE_TEMP",
+       "PECI Agent 0 Calibration",
+       "PECI Agent 1 Calibration",
+       "",
+       "",
+       "Virtual_TEMP"
+};
+
+static const char *const nct6793_temp_label[] = {
+       "",
+       "SYSTIN",
+       "CPUTIN",
+       "AUXTIN0",
+       "AUXTIN1",
+       "AUXTIN2",
+       "AUXTIN3",
+       "",
+       "SMBUSMASTER 0",
+       "SMBUSMASTER 1",
+       "",
+       "",
+       "",
+       "",
+       "",
+       "",
+       "PECI Agent 0",
+       "PECI Agent 1",
+       "PCH_CHIP_CPU_MAX_TEMP",
+       "PCH_CHIP_TEMP",
+       "PCH_CPU_TEMP",
+       "PCH_MCH_TEMP",
+       "Agent0 Dimm0 ",
+       "Agent0 Dimm1",
+       "Agent1 Dimm0",
+       "Agent1 Dimm1",
+       "BYTE_TEMP0",
+       "BYTE_TEMP1",
+       "PECI Agent 0 Calibration",
+       "PECI Agent 1 Calibration",
+       "",
+       "Virtual_TEMP"
+};
 
 /* NCT6102D/NCT6106D specific data */
 
@@ -743,7 +852,6 @@ struct nct6775_data {
        struct device *hwmon_dev;
 #endif
 
-       int num_attr_groups;
        const struct attribute_group *groups[6];
 
        u16 reg_temp[5][NUM_TEMP]; /* 0=temp, 1=temp_over, 2=temp_hyst,
@@ -880,12 +988,12 @@ struct nct6775_data {
        u16 have_temp;
        u16 have_temp_fixed;
        u16 have_in;
-#ifdef CONFIG_PM
+
        /* Remember extra register values over suspend/resume */
        u8 vbat;
        u8 fandiv1;
        u8 fandiv2;
-#endif
+       u8 sio_reg_enable;
 };
 
 struct nct6775_sio_data {
@@ -949,7 +1057,8 @@ struct sensor_template_group {
 };
 
 static struct attribute_group *
-nct6775_create_attr_group(struct device *dev, struct sensor_template_group *tg,
+nct6775_create_attr_group(struct device *dev,
+                         const struct sensor_template_group *tg,
                          int repeat)
 {
        struct attribute_group *group;
@@ -994,6 +1103,7 @@ nct6775_create_attr_group(struct device *dev, struct sensor_template_group *tg,
                                 (*t)->dev_attr.attr.name, tg->base + i);
                        if ((*t)->s2) {
                                a2 = &su->u.a2;
+                               sysfs_attr_init(&a2->dev_attr.attr);
                                a2->dev_attr.attr.name = su->name;
                                a2->nr = (*t)->u.s.nr + i;
                                a2->index = (*t)->u.s.index;
@@ -1004,6 +1114,7 @@ nct6775_create_attr_group(struct device *dev, struct sensor_template_group *tg,
                                *attrs = &a2->dev_attr.attr;
                        } else {
                                a = &su->u.a1;
+                               sysfs_attr_init(&a->dev_attr.attr);
                                a->dev_attr.attr.name = su->name;
                                a->index = (*t)->u.index + i;
                                a->dev_attr.attr.mode =
@@ -1053,13 +1164,15 @@ static bool is_word_sized(struct nct6775_data *data, u16 reg)
        case nct6779:
        case nct6791:
        case nct6792:
+       case nct6793:
+       case nct6795:
                return reg == 0x150 || reg == 0x153 || reg == 0x155 ||
                  ((reg & 0xfff0) == 0x4b0 && (reg & 0x000f) < 0x0b) ||
                  reg == 0x402 ||
                  reg == 0x63a || reg == 0x63c || reg == 0x63e ||
                  reg == 0x640 || reg == 0x642 ||
                  reg == 0x73 || reg == 0x75 || reg == 0x77 || reg == 0x79 ||
-                 reg == 0x7b;
+                 reg == 0x7b || reg == 0x7d;
        }
        return false;
 }
@@ -1073,6 +1186,7 @@ static bool is_word_sized(struct nct6775_data *data, u16 reg)
 static inline void nct6775_set_bank(struct nct6775_data *data, u16 reg)
 {
        u8 bank = reg >> 8;
+
        if (data->bank != bank) {
                outb_p(NCT6775_REG_BANK, data->addr + ADDR_REG_OFFSET);
                outb_p(bank, data->addr + DATA_REG_OFFSET);
@@ -1310,6 +1424,7 @@ static void nct6775_update_pwm(struct device *dev)
                if (!data->target_speed_tolerance[i] ||
                    data->pwm_enable[i] == speed_cruise) {
                        u8 t = fanmodecfg & 0x0f;
+
                        if (data->REG_TOLERANCE_H) {
                                t |= (nct6775_read_value(data,
                                      data->REG_TOLERANCE_H[i]) & 0x70) >> 1;
@@ -1402,6 +1517,8 @@ static void nct6775_update_pwm_limits(struct device *dev)
                case nct6779:
                case nct6791:
                case nct6792:
+               case nct6793:
+               case nct6795:
                        reg = nct6775_read_value(data,
                                        data->REG_CRITICAL_PWM_ENABLE[i]);
                        if (reg & data->CRITICAL_PWM_ENABLE_MASK)
@@ -1484,6 +1601,7 @@ static struct nct6775_data *nct6775_update_device(struct device *dev)
                data->alarms = 0;
                for (i = 0; i < NUM_REG_ALARM; i++) {
                        u8 alarm;
+
                        if (!data->REG_ALARM[i])
                                continue;
                        alarm = nct6775_read_value(data, data->REG_ALARM[i]);
@@ -1493,6 +1611,7 @@ static struct nct6775_data *nct6775_update_device(struct device *dev)
                data->beeps = 0;
                for (i = 0; i < NUM_REG_BEEP; i++) {
                        u8 beep;
+
                        if (!data->REG_BEEP[i])
                                continue;
                        beep = nct6775_read_value(data, data->REG_BEEP[i]);
@@ -1515,8 +1634,9 @@ show_in_reg(struct device *dev, struct device_attribute *attr, char *buf)
 {
        struct nct6775_data *data = nct6775_update_device(dev);
        struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
-       int nr = sattr->nr;
        int index = sattr->index;
+       int nr = sattr->nr;
+
        return sprintf(buf, "%ld\n", in_from_reg(data->in[nr][index], nr));
 }
 
@@ -1526,10 +1646,12 @@ store_in_reg(struct device *dev, struct device_attribute *attr, const char *buf,
 {
        struct nct6775_data *data = dev_get_drvdata(dev);
        struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
-       int nr = sattr->nr;
        int index = sattr->index;
+       int nr = sattr->nr;
        unsigned long val;
-       int err = kstrtoul(buf, 10, &val);
+       int err;
+
+       err = kstrtoul(buf, 10, &val);
        if (err < 0)
                return err;
        mutex_lock(&data->update_lock);
@@ -1546,6 +1668,7 @@ show_alarm(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 = data->ALARM_BITS[sattr->index];
+
        return sprintf(buf, "%u\n",
                       (unsigned int)((data->alarms >> nr) & 0x01));
 }
@@ -1581,6 +1704,7 @@ show_temp_alarm(struct device *dev, struct device_attribute *attr, char *buf)
        nr = find_temp_source(data, sattr->index, data->num_temp_alarms);
        if (nr >= 0) {
                int bit = data->ALARM_BITS[nr + TEMP_ALARM_BASE];
+
                alarm = (data->alarms >> bit) & 0x01;
        }
        return sprintf(buf, "%u\n", alarm);
@@ -1606,8 +1730,9 @@ store_beep(struct device *dev, struct device_attribute *attr, const char *buf,
        int nr = data->BEEP_BITS[sattr->index];
        int regindex = nr >> 3;
        unsigned long val;
+       int err;
 
-       int err = kstrtoul(buf, 10, &val);
+       err = kstrtoul(buf, 10, &val);
        if (err < 0)
                return err;
        if (val > 1)
@@ -1640,6 +1765,7 @@ show_temp_beep(struct device *dev, struct device_attribute *attr, char *buf)
        nr = find_temp_source(data, sattr->index, data->num_temp_beeps);
        if (nr >= 0) {
                int bit = data->BEEP_BITS[nr + TEMP_ALARM_BASE];
+
                beep = (data->beeps >> bit) & 0x01;
        }
        return sprintf(buf, "%u\n", beep);
@@ -1653,8 +1779,9 @@ store_temp_beep(struct device *dev, struct device_attribute *attr,
        struct nct6775_data *data = dev_get_drvdata(dev);
        int nr, bit, regindex;
        unsigned long val;
+       int err;
 
-       int err = kstrtoul(buf, 10, &val);
+       err = kstrtoul(buf, 10, &val);
        if (err < 0)
                return err;
        if (val > 1)
@@ -1715,7 +1842,7 @@ static struct sensor_device_template *nct6775_attributes_in_template[] = {
        NULL
 };
 
-static struct sensor_template_group nct6775_in_template_group = {
+static const struct sensor_template_group nct6775_in_template_group = {
        .templates = nct6775_attributes_in_template,
        .is_visible = nct6775_in_is_visible,
 };
@@ -1726,6 +1853,7 @@ 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]);
 }
 
@@ -1735,6 +1863,7 @@ 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]));
@@ -1746,6 +1875,7 @@ 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]));
 }
 
@@ -1757,9 +1887,9 @@ store_fan_min(struct device *dev, struct device_attribute *attr,
        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;
+       int err;
 
        err = kstrtoul(buf, 10, &val);
        if (err < 0)
@@ -1931,7 +2061,7 @@ static struct sensor_device_template *nct6775_attributes_fan_template[] = {
        NULL
 };
 
-static struct sensor_template_group nct6775_fan_template_group = {
+static const struct sensor_template_group nct6775_fan_template_group = {
        .templates = nct6775_attributes_fan_template,
        .is_visible = nct6775_fan_is_visible,
        .base = 1,
@@ -1943,6 +2073,7 @@ show_temp_label(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, "%s\n", data->temp_label[data->temp_src[nr]]);
 }
 
@@ -2019,6 +2150,7 @@ show_temp_type(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", (int)data->temp_type[nr]);
 }
 
@@ -2138,7 +2270,7 @@ static struct sensor_device_template *nct6775_attributes_temp_template[] = {
        NULL
 };
 
-static struct sensor_template_group nct6775_temp_template_group = {
+static const struct sensor_template_group nct6775_temp_template_group = {
        .templates = nct6775_attributes_temp_template,
        .is_visible = nct6775_temp_is_visible,
        .base = 1,
@@ -2814,6 +2946,8 @@ store_auto_pwm(struct device *dev, struct device_attribute *attr,
                case nct6779:
                case nct6791:
                case nct6792:
+               case nct6793:
+               case nct6795:
                        nct6775_write_value(data, data->REG_CRITICAL_PWM[nr],
                                            val);
                        reg = nct6775_read_value(data,
@@ -3007,10 +3141,11 @@ static struct sensor_device_template *nct6775_attributes_pwm_template[] = {
        &sensor_dev_template_pwm_auto_point6_temp,
        &sensor_dev_template_pwm_auto_point7_pwm,
        &sensor_dev_template_pwm_auto_point7_temp,      /* 35 */
+
        NULL
 };
 
-static struct sensor_template_group nct6775_pwm_template_group = {
+static const struct sensor_template_group nct6775_pwm_template_group = {
        .templates = nct6775_attributes_pwm_template,
        .is_visible = nct6775_pwm_is_visible,
        .base = 1,
@@ -3020,6 +3155,7 @@ static ssize_t
 show_vid(struct device *dev, struct device_attribute *attr, char *buf)
 {
        struct nct6775_data *data = dev_get_drvdata(dev);
+
        return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm));
 }
 
@@ -3173,6 +3309,10 @@ nct6775_check_fan_inputs(struct nct6775_data *data)
        int sioreg = data->sioreg;
        int regval;
 
+       /* Store SIO_REG_ENABLE for use during resume */
+       superio_select(sioreg, NCT6775_LD_HWM);
+       data->sio_reg_enable = superio_inb(sioreg, SIO_REG_ENABLE);
+
        /* fan4 and fan5 share some pins with the GPIO and serial flash */
        if (data->kind == nct6775) {
                regval = superio_inb(sioreg, 0x2c);
@@ -3190,21 +3330,38 @@ nct6775_check_fan_inputs(struct nct6775_data *data)
                pwm6pin = false;
        } else if (data->kind == nct6776) {
                bool gpok = superio_inb(sioreg, 0x27) & 0x80;
+               const char *board_vendor, *board_name;
+
+               board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR);
+               board_name = dmi_get_system_info(DMI_BOARD_NAME);
 
-               superio_select(sioreg, NCT6775_LD_HWM);
-               regval = superio_inb(sioreg, SIO_REG_ENABLE);
+               if (board_name && board_vendor &&
+                   !strcmp(board_vendor, "ASRock")) {
+                       /*
+                        * Auxiliary fan monitoring is not enabled on ASRock
+                        * Z77 Pro4-M if booted in UEFI Ultra-FastBoot mode.
+                        * Observed with BIOS version 2.00.
+                        */
+                       if (!strcmp(board_name, "Z77 Pro4-M")) {
+                               if ((data->sio_reg_enable & 0xe0) != 0xe0) {
+                                       data->sio_reg_enable |= 0xe0;
+                                       superio_outb(sioreg, SIO_REG_ENABLE,
+                                                    data->sio_reg_enable);
+                               }
+                       }
+               }
 
-               if (regval & 0x80)
+               if (data->sio_reg_enable & 0x80)
                        fan3pin = gpok;
                else
                        fan3pin = !(superio_inb(sioreg, 0x24) & 0x40);
 
-               if (regval & 0x40)
+               if (data->sio_reg_enable & 0x40)
                        fan4pin = gpok;
                else
                        fan4pin = superio_inb(sioreg, 0x1C) & 0x01;
 
-               if (regval & 0x20)
+               if (data->sio_reg_enable & 0x20)
                        fan5pin = gpok;
                else
                        fan5pin = superio_inb(sioreg, 0x1C) & 0x02;
@@ -3227,7 +3384,7 @@ nct6775_check_fan_inputs(struct nct6775_data *data)
                pwm4pin = false;
                pwm5pin = false;
                pwm6pin = false;
-       } else {        /* NCT6779D, NCT6791D, or NCT6792D */
+       } else {        /* NCT6779D, NCT6791D, NCT6792D, NCT6793D, NCT6795D */
                regval = superio_inb(sioreg, 0x1c);
 
                fan3pin = !(regval & (1 << 5));
@@ -3240,7 +3397,8 @@ nct6775_check_fan_inputs(struct nct6775_data *data)
 
                fan4min = fan4pin;
 
-               if (data->kind == nct6791 || data->kind == nct6792) {
+               if (data->kind == nct6791 || data->kind == nct6792 ||
+                   data->kind == nct6793 || data->kind == nct6795) {
                        regval = superio_inb(sioreg, 0x2d);
                        fan6pin = (regval & (1 << 1));
                        pwm6pin = (regval & (1 << 0));
@@ -3300,6 +3458,7 @@ static int nct6775_probe(struct platform_device *pdev)
        u8 cr2a;
        struct attribute_group *group;
        struct device *hwmon_dev;
+       int num_attr_groups = 0;
 
        res = platform_get_resource(pdev, IORESOURCE_IO, 0);
        if (!devm_request_region(&pdev->dev, res->start, IOREGION_LENGTH,
@@ -3498,8 +3657,8 @@ static int nct6775_probe(struct platform_device *pdev)
                data->REG_FAN_PULSES = NCT6776_REG_FAN_PULSES;
                data->FAN_PULSE_SHIFT = NCT6775_FAN_PULSE_SHIFT;
                data->REG_FAN_TIME[0] = NCT6775_REG_FAN_STOP_TIME;
-               data->REG_FAN_TIME[1] = NCT6775_REG_FAN_STEP_UP_TIME;
-               data->REG_FAN_TIME[2] = NCT6775_REG_FAN_STEP_DOWN_TIME;
+               data->REG_FAN_TIME[1] = NCT6776_REG_FAN_STEP_UP_TIME;
+               data->REG_FAN_TIME[2] = NCT6776_REG_FAN_STEP_DOWN_TIME;
                data->REG_TOLERANCE_H = NCT6776_REG_TOLERANCE_H;
                data->REG_PWM[0] = NCT6775_REG_PWM;
                data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT;
@@ -3554,7 +3713,7 @@ static int nct6775_probe(struct platform_device *pdev)
                data->speed_tolerance_limit = 63;
 
                data->temp_label = nct6779_temp_label;
-               data->temp_label_num = ARRAY_SIZE(nct6779_temp_label);
+               data->temp_label_num = NCT6779_NUM_LABELS;
 
                data->REG_CONFIG = NCT6775_REG_CONFIG;
                data->REG_VBAT = NCT6775_REG_VBAT;
@@ -3570,8 +3729,8 @@ static int nct6775_probe(struct platform_device *pdev)
                data->REG_FAN_PULSES = NCT6779_REG_FAN_PULSES;
                data->FAN_PULSE_SHIFT = NCT6775_FAN_PULSE_SHIFT;
                data->REG_FAN_TIME[0] = NCT6775_REG_FAN_STOP_TIME;
-               data->REG_FAN_TIME[1] = NCT6775_REG_FAN_STEP_UP_TIME;
-               data->REG_FAN_TIME[2] = NCT6775_REG_FAN_STEP_DOWN_TIME;
+               data->REG_FAN_TIME[1] = NCT6776_REG_FAN_STEP_UP_TIME;
+               data->REG_FAN_TIME[2] = NCT6776_REG_FAN_STEP_DOWN_TIME;
                data->REG_TOLERANCE_H = NCT6776_REG_TOLERANCE_H;
                data->REG_PWM[0] = NCT6775_REG_PWM;
                data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT;
@@ -3613,6 +3772,8 @@ static int nct6775_probe(struct platform_device *pdev)
                break;
        case nct6791:
        case nct6792:
+       case nct6793:
+       case nct6795:
                data->in_num = 15;
                data->pwm_num = 6;
                data->auto_pwm_num = 4;
@@ -3630,8 +3791,21 @@ static int nct6775_probe(struct platform_device *pdev)
                data->tolerance_mask = 0x07;
                data->speed_tolerance_limit = 63;
 
-               data->temp_label = nct6779_temp_label;
-               data->temp_label_num = ARRAY_SIZE(nct6779_temp_label);
+               switch (data->kind) {
+               default:
+               case nct6791:
+                       data->temp_label = nct6779_temp_label;
+                       break;
+               case nct6792:
+                       data->temp_label = nct6792_temp_label;
+                       break;
+               case nct6793:
+                       data->temp_label = nct6793_temp_label;
+                       break;
+               case nct6795:   /* unknown */
+                       break;
+               }
+               data->temp_label_num = NCT6791_NUM_LABELS;
 
                data->REG_CONFIG = NCT6775_REG_CONFIG;
                data->REG_VBAT = NCT6775_REG_VBAT;
@@ -3647,8 +3821,8 @@ static int nct6775_probe(struct platform_device *pdev)
                data->REG_FAN_PULSES = NCT6779_REG_FAN_PULSES;
                data->FAN_PULSE_SHIFT = NCT6775_FAN_PULSE_SHIFT;
                data->REG_FAN_TIME[0] = NCT6775_REG_FAN_STOP_TIME;
-               data->REG_FAN_TIME[1] = NCT6775_REG_FAN_STEP_UP_TIME;
-               data->REG_FAN_TIME[2] = NCT6775_REG_FAN_STEP_DOWN_TIME;
+               data->REG_FAN_TIME[1] = NCT6776_REG_FAN_STEP_UP_TIME;
+               data->REG_FAN_TIME[2] = NCT6776_REG_FAN_STEP_DOWN_TIME;
                data->REG_TOLERANCE_H = NCT6776_REG_TOLERANCE_H;
                data->REG_PWM[0] = NCT6775_REG_PWM;
                data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT;
@@ -3675,12 +3849,20 @@ static int nct6775_probe(struct platform_device *pdev)
                data->REG_WEIGHT_TEMP[1] = NCT6791_REG_WEIGHT_TEMP_STEP_TOL;
                data->REG_WEIGHT_TEMP[2] = NCT6791_REG_WEIGHT_TEMP_BASE;
                data->REG_ALARM = NCT6791_REG_ALARM;
-               data->REG_BEEP = NCT6776_REG_BEEP;
+               if (data->kind == nct6791)
+                       data->REG_BEEP = NCT6776_REG_BEEP;
+               else
+                       data->REG_BEEP = NCT6792_REG_BEEP;
 
                reg_temp = NCT6779_REG_TEMP;
-               reg_temp_mon = NCT6779_REG_TEMP_MON;
                num_reg_temp = ARRAY_SIZE(NCT6779_REG_TEMP);
-               num_reg_temp_mon = ARRAY_SIZE(NCT6779_REG_TEMP_MON);
+               if (data->kind == nct6791) {
+                       reg_temp_mon = NCT6779_REG_TEMP_MON;
+                       num_reg_temp_mon = ARRAY_SIZE(NCT6779_REG_TEMP_MON);
+               } else {
+                       reg_temp_mon = NCT6792_REG_TEMP_MON;
+                       num_reg_temp_mon = ARRAY_SIZE(NCT6792_REG_TEMP_MON);
+               }
                reg_temp_over = NCT6779_REG_TEMP_OVER;
                reg_temp_hyst = NCT6779_REG_TEMP_HYST;
                reg_temp_config = NCT6779_REG_TEMP_CONFIG;
@@ -3790,7 +3972,7 @@ static int nct6775_probe(struct platform_device *pdev)
                        continue;
 
                src = nct6775_read_value(data, data->REG_TEMP_SEL[i]) & 0x1f;
-               if (!src || (mask & (1 << src)))
+               if (!src)
                        continue;
 
                if (src >= data->temp_label_num ||
@@ -3802,7 +3984,16 @@ static int nct6775_probe(struct platform_device *pdev)
                        continue;
                }
 
-               mask |= 1 << src;
+               /*
+                * For virtual temperature sources, the 'virtual' temperature
+                * for each fan reflects a different temperature, and there
+                * are no duplicates.
+                */
+               if (src != TEMP_SOURCE_VIRTUAL) {
+                       if (mask & (1 << src))
+                               continue;
+                       mask |= 1 << src;
+               }
 
                /* Use fixed index for SYSTIN(1), CPUTIN(2), AUXTIN(3) */
                if (src <= data->temp_fixed_num) {
@@ -3880,6 +4071,8 @@ static int nct6775_probe(struct platform_device *pdev)
        case nct6779:
        case nct6791:
        case nct6792:
+       case nct6793:
+       case nct6795:
                break;
        }
 
@@ -3912,6 +4105,8 @@ static int nct6775_probe(struct platform_device *pdev)
                        break;
                case nct6791:
                case nct6792:
+               case nct6793:
+               case nct6795:
                        tmp |= 0x7e;
                        break;
                }
@@ -3934,34 +4129,34 @@ static int nct6775_probe(struct platform_device *pdev)
        if (IS_ERR(group))
                return PTR_ERR(group);
 
-       data->groups[data->num_attr_groups++] = group;
+       data->groups[num_attr_groups++] = group;
 
        group = nct6775_create_attr_group(dev, &nct6775_in_template_group,
                                          fls(data->have_in));
        if (IS_ERR(group))
                return PTR_ERR(group);
 
-       data->groups[data->num_attr_groups++] = group;
+       data->groups[num_attr_groups++] = group;
 
        group = nct6775_create_attr_group(dev, &nct6775_fan_template_group,
                                          fls(data->has_fan));
        if (IS_ERR(group))
                return PTR_ERR(group);
 
-       data->groups[data->num_attr_groups++] = group;
+       data->groups[num_attr_groups++] = group;
 
        group = nct6775_create_attr_group(dev, &nct6775_temp_template_group,
                                          fls(data->have_temp));
        if (IS_ERR(group))
                return PTR_ERR(group);
 
-       data->groups[data->num_attr_groups++] = group;
-       data->groups[data->num_attr_groups++] = &nct6775_group_other;
+       data->groups[num_attr_groups++] = group;
+       data->groups[num_attr_groups++] = &nct6775_group_other;
 
 #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0)
        err = sysfs_create_groups(&dev->kobj, data->groups);
        if (err < 0)
-               return err;
+               return err;
        hwmon_dev = hwmon_device_register(dev);
        if (IS_ERR(hwmon_dev)) {
                sysfs_remove_groups(&dev->kobj, data->groups);
@@ -3998,8 +4193,7 @@ static void nct6791_enable_io_mapping(int sioaddr)
        }
 }
 
-#ifdef CONFIG_PM
-static int nct6775_suspend(struct device *dev)
+static int __maybe_unused nct6775_suspend(struct device *dev)
 {
        struct nct6775_data *data = nct6775_update_device(dev);
 
@@ -4014,22 +4208,30 @@ static int nct6775_suspend(struct device *dev)
        return 0;
 }
 
-static int nct6775_resume(struct device *dev)
+static int __maybe_unused nct6775_resume(struct device *dev)
 {
        struct nct6775_data *data = dev_get_drvdata(dev);
+       int sioreg = data->sioreg;
        int i, j, err = 0;
+       u8 reg;
 
        mutex_lock(&data->update_lock);
        data->bank = 0xff;              /* Force initial bank selection */
 
-       if (data->kind == nct6791 || data->kind == nct6792) {
-               err = superio_enter(data->sioreg);
-               if (err)
-                       goto abort;
+       err = superio_enter(sioreg);
+       if (err)
+               goto abort;
 
-               nct6791_enable_io_mapping(data->sioreg);
-               superio_exit(data->sioreg);
-       }
+       superio_select(sioreg, NCT6775_LD_HWM);
+       reg = superio_inb(sioreg, SIO_REG_ENABLE);
+       if (reg != data->sio_reg_enable)
+               superio_outb(sioreg, SIO_REG_ENABLE, data->sio_reg_enable);
+
+       if (data->kind == nct6791 || data->kind == nct6792 ||
+           data->kind == nct6793 || data->kind == nct6795)
+               nct6791_enable_io_mapping(sioreg);
+
+       superio_exit(sioreg);
 
        /* Restore limits */
        for (i = 0; i < data->in_num; i++) {
@@ -4075,23 +4277,13 @@ abort:
        return err;
 }
 
-static const struct dev_pm_ops nct6775_dev_pm_ops = {
-       .suspend = nct6775_suspend,
-       .resume = nct6775_resume,
-       .freeze = nct6775_suspend,
-       .restore = nct6775_resume,
-};
-
-#define NCT6775_DEV_PM_OPS     (&nct6775_dev_pm_ops)
-#else
-#define NCT6775_DEV_PM_OPS     NULL
-#endif /* CONFIG_PM */
+static SIMPLE_DEV_PM_OPS(nct6775_dev_pm_ops, nct6775_suspend, nct6775_resume);
 
 static struct platform_driver nct6775_driver = {
        .driver = {
                .owner  = THIS_MODULE,
                .name   = DRVNAME,
-               .pm     = NCT6775_DEV_PM_OPS,
+               .pm     = &nct6775_dev_pm_ops,
        },
        .probe          = nct6775_probe,
 #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0)
@@ -4099,15 +4291,6 @@ static struct platform_driver nct6775_driver = {
 #endif
 };
 
-static const char * const nct6775_sio_names[] __initconst = {
-       "NCT6106D",
-       "NCT6775F",
-       "NCT6776D/F",
-       "NCT6779D",
-       "NCT6791D",
-       "NCT6792D",
-};
-
 /* nct6775_find() looks for a '627 in the Super-I/O config space */
 static int __init nct6775_find(int sioaddr, struct nct6775_sio_data *sio_data)
 {
@@ -4119,11 +4302,11 @@ static int __init nct6775_find(int sioaddr, struct nct6775_sio_data *sio_data)
        if (err)
                return err;
 
-       if (force_id)
+       val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8) |
+               superio_inb(sioaddr, SIO_REG_DEVID + 1);
+       if (force_id && val != 0xffff)
                val = force_id;
-       else
-               val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8)
-                   | superio_inb(sioaddr, SIO_REG_DEVID + 1);
+
        switch (val & SIO_ID_MASK) {
        case SIO_NCT6106_ID:
                sio_data->kind = nct6106;
@@ -4143,6 +4326,12 @@ static int __init nct6775_find(int sioaddr, struct nct6775_sio_data *sio_data)
        case SIO_NCT6792_ID:
                sio_data->kind = nct6792;
                break;
+       case SIO_NCT6793_ID:
+               sio_data->kind = nct6793;
+               break;
+       case SIO_NCT6795_ID:
+               sio_data->kind = nct6795;
+               break;
        default:
                if (val != 0xffff)
                        pr_debug("unsupported chip ID: 0x%04x\n", val);
@@ -4168,7 +4357,8 @@ static int __init nct6775_find(int sioaddr, struct nct6775_sio_data *sio_data)
                superio_outb(sioaddr, SIO_REG_ENABLE, val | 0x01);
        }
 
-       if (sio_data->kind == nct6791 || sio_data->kind == nct6792)
+       if (sio_data->kind == nct6791 || sio_data->kind == nct6792 ||
+           sio_data->kind == nct6793 || sio_data->kind == nct6795)
                nct6791_enable_io_mapping(sioaddr);
 
        superio_exit(sioaddr);
@@ -4217,7 +4407,7 @@ static int __init sensors_nct6775_init(void)
                pdev[i] = platform_device_alloc(DRVNAME, address);
                if (!pdev[i]) {
                        err = -ENOMEM;
-                       goto exit_device_put;
+                       goto exit_device_unregister;
                }
 
                err = platform_device_add_data(pdev[i], &sio_data,
@@ -4255,9 +4445,11 @@ static int __init sensors_nct6775_init(void)
        return 0;
 
 exit_device_put:
-       for (i = 0; i < ARRAY_SIZE(pdev); i++) {
+       platform_device_put(pdev[i]);
+exit_device_unregister:
+       while (--i >= 0) {
                if (pdev[i])
-                       platform_device_put(pdev[i]);
+                       platform_device_unregister(pdev[i]);
        }
 exit_unregister:
        platform_driver_unregister(&nct6775_driver);
@@ -4276,7 +4468,7 @@ static void __exit sensors_nct6775_exit(void)
 }
 
 MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>");
-MODULE_DESCRIPTION("NCT6775F/NCT6776F/NCT6779D driver");
+MODULE_DESCRIPTION("Driver for NCT6775F and compatible chips");
 MODULE_LICENSE("GPL");
 
 module_init(sensors_nct6775_init);