]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
hwmon: Driver for NCT6683D
authorGuenter Roeck <linux@roeck-us.net>
Sun, 6 Apr 2014 15:57:20 +0000 (08:57 -0700)
committerGuenter Roeck <linux@roeck-us.net>
Wed, 21 May 2014 23:02:27 +0000 (16:02 -0700)
Nuvoton NCT6683D is an eSIO with hardware monitoring capabilities.

Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Documentation/hwmon/nct6683 [new file with mode: 0644]
drivers/hwmon/Kconfig
drivers/hwmon/Makefile
drivers/hwmon/nct6683.c [new file with mode: 0644]

diff --git a/Documentation/hwmon/nct6683 b/Documentation/hwmon/nct6683
new file mode 100644 (file)
index 0000000..c1301d4
--- /dev/null
@@ -0,0 +1,57 @@
+Kernel driver nct6683
+=====================
+
+Supported chips:
+  * Nuvoton NCT6683D
+    Prefix: 'nct6683'
+    Addresses scanned: ISA address retrieved from Super I/O registers
+    Datasheet: Available from Nuvoton upon request
+
+Authors:
+        Guenter Roeck <linux@roeck-us.net>
+
+Description
+-----------
+
+This driver implements support for the Nuvoton NCT6683D eSIO chip.
+
+The chips implement up to shared 32 temperature and voltage sensors.
+It supports up to 16 fan rotation sensors and up to 8 fan control engines.
+
+Temperatures are measured in degrees Celsius. Measurement resolution is
+0.5 degrees C.
+
+Voltage sensors (also known as IN sensors) report their values in millivolts.
+
+Fan rotation speeds are reported in RPM (rotations per minute).
+
+Usage Note
+----------
+
+Limit register locations on Intel boards with EC firmware version 1.0
+build date 04/03/13 do not match the register locations in the Nuvoton
+datasheet. Nuvoton confirms that Intel uses a special firmware version
+with different register addresses. The specification describing the Intel
+firmware is held under NDA by Nuvoton and Intel and not available
+to the public.
+
+Some of the register locations can be reverse engineered; others are too
+well hidden. Given this, writing any values from the operating system is
+considered too risky with this firmware and has been disabled. All limits
+must all be written from the BIOS.
+
+The driver has only been tested with the Intel firmware, and by default
+only instantiates on Intel boards. To enable it on non-Intel boards,
+set the 'force' module parameter to 1.
+
+Tested Boards and Firmware Versions
+-----------------------------------
+
+The driver has been reported to work with the following boards and
+firmware versions.
+
+Board          Firmware version
+---------------------------------------------------------------
+Intel DH87RL   NCT6683D EC firmware version 1.0 build 04/03/13
+Intel DH87MC   NCT6683D EC firmware version 1.0 build 04/03/13
+Intel DB85FL   NCT6683D EC firmware version 1.0 build 04/03/13
index bc196f49ec53be2e13959a9ebca9aa9c82edcfff..b7c0b830d9e433eacb969ef3e69ebe2a950c8d9f 100644 (file)
@@ -1065,6 +1065,16 @@ config SENSORS_NTC_THERMISTOR
          This driver can also be built as a module.  If so, the module
          will be called ntc-thermistor.
 
+config SENSORS_NCT6683
+       tristate "Nuvoton NCT6683D"
+       depends on !PPC
+       help
+         If you say yes here you get support for the hardware monitoring
+         functionality of the Nuvoton NCT6683D eSIO chip.
+
+         This driver can also be built as a module.  If so, the module
+         will be called nct6683.
+
 config SENSORS_NCT6775
        tristate "Nuvoton NCT6775F and compatibles"
        depends on !PPC
index c48f9873ac7367034a584d7e3aa5ad0231be9ffe..11798ad7e801bc71ee419c1a2e5bdd190b6221c2 100644 (file)
@@ -114,6 +114,7 @@ obj-$(CONFIG_SENSORS_MAX6650)       += max6650.o
 obj-$(CONFIG_SENSORS_MAX6697)  += max6697.o
 obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
 obj-$(CONFIG_SENSORS_MCP3021)  += mcp3021.o
+obj-$(CONFIG_SENSORS_NCT6683)  += nct6683.o
 obj-$(CONFIG_SENSORS_NCT6775)  += nct6775.o
 obj-$(CONFIG_SENSORS_NTC_THERMISTOR)   += ntc_thermistor.o
 obj-$(CONFIG_SENSORS_PC87360)  += pc87360.o
diff --git a/drivers/hwmon/nct6683.c b/drivers/hwmon/nct6683.c
new file mode 100644 (file)
index 0000000..540c81c
--- /dev/null
@@ -0,0 +1,1455 @@
+/*
+ * nct6683 - Driver for the hardware monitoring functionality of
+ *          Nuvoton NCT6683D eSIO
+ *
+ * Copyright (C) 2013  Guenter Roeck <linux@roeck-us.net>
+ *
+ * Derived from nct6775 driver
+ * Copyright (C) 2012, 2013  Guenter Roeck <linux@roeck-us.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * Supports the following chips:
+ *
+ * Chip        #vin    #fan    #pwm    #temp  chip ID
+ * nct6683d     21(1)   16      8       32(1) 0xc730
+ *
+ * Notes:
+ *     (1) Total number of vin and temp inputs is 32.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/jiffies.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+enum kinds { nct6683 };
+
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Set to one to enable detection on non-Intel boards");
+
+static const char * const nct6683_device_names[] = {
+       "nct6683",
+};
+
+static const char * const nct6683_chip_names[] = {
+       "NCT6683D",
+};
+
+#define DRVNAME "nct6683"
+
+/*
+ * Super-I/O constants and functions
+ */
+
+#define NCT6683_LD_ACPI                0x0a
+#define NCT6683_LD_HWM         0x0b
+#define NCT6683_LD_VID         0x0d
+
+#define SIO_REG_LDSEL          0x07    /* Logical device select */
+#define SIO_REG_DEVID          0x20    /* Device ID (2 bytes) */
+#define SIO_REG_ENABLE         0x30    /* Logical device enable */
+#define SIO_REG_ADDR           0x60    /* Logical device address (2 bytes) */
+
+#define SIO_NCT6681_ID         0xb270  /* for later */
+#define SIO_NCT6683_ID         0xc730
+#define SIO_ID_MASK            0xFFF0
+
+static inline void
+superio_outb(int ioreg, int reg, int val)
+{
+       outb(reg, ioreg);
+       outb(val, ioreg + 1);
+}
+
+static inline int
+superio_inb(int ioreg, int reg)
+{
+       outb(reg, ioreg);
+       return inb(ioreg + 1);
+}
+
+static inline void
+superio_select(int ioreg, int ld)
+{
+       outb(SIO_REG_LDSEL, ioreg);
+       outb(ld, ioreg + 1);
+}
+
+static inline int
+superio_enter(int ioreg)
+{
+       /*
+        * Try to reserve <ioreg> and <ioreg + 1> for exclusive access.
+        */
+       if (!request_muxed_region(ioreg, 2, DRVNAME))
+               return -EBUSY;
+
+       outb(0x87, ioreg);
+       outb(0x87, ioreg);
+
+       return 0;
+}
+
+static inline void
+superio_exit(int ioreg)
+{
+       outb(0xaa, ioreg);
+       outb(0x02, ioreg);
+       outb(0x02, ioreg + 1);
+       release_region(ioreg, 2);
+}
+
+/*
+ * ISA constants
+ */
+
+#define IOREGION_ALIGNMENT     (~7)
+#define IOREGION_OFFSET                4       /* Use EC port 1 */
+#define IOREGION_LENGTH                4
+
+#define EC_PAGE_REG            0
+#define EC_INDEX_REG           1
+#define EC_DATA_REG            2
+#define EC_EVENT_REG           3
+
+/* Common and NCT6683 specific data */
+
+#define NCT6683_NUM_REG_MON            32
+#define NCT6683_NUM_REG_FAN            16
+#define NCT6683_NUM_REG_PWM            8
+
+#define NCT6683_REG_MON(x)             (0x100 + (x) * 2)
+#define NCT6683_REG_FAN_RPM(x)         (0x140 + (x) * 2)
+#define NCT6683_REG_PWM(x)             (0x160 + (x))
+
+#define NCT6683_REG_MON_STS(x)         (0x174 + (x))
+#define NCT6683_REG_IDLE(x)            (0x178 + (x))
+
+#define NCT6683_REG_FAN_STS(x)         (0x17c + (x))
+#define NCT6683_REG_FAN_ERRSTS         0x17e
+#define NCT6683_REG_FAN_INITSTS                0x17f
+
+#define NCT6683_HWM_CFG                        0x180
+
+#define NCT6683_REG_MON_CFG(x)         (0x1a0 + (x))
+#define NCT6683_REG_FANIN_CFG(x)       (0x1c0 + (x))
+#define NCT6683_REG_FANOUT_CFG(x)      (0x1d0 + (x))
+
+#define NCT6683_REG_INTEL_TEMP_MAX(x)  (0x901 + (x) * 16)
+#define NCT6683_REG_INTEL_TEMP_CRIT(x) (0x90d + (x) * 16)
+
+#define NCT6683_REG_TEMP_HYST(x)       (0x330 + (x))           /* 8 bit */
+#define NCT6683_REG_TEMP_MAX(x)                (0x350 + (x))           /* 8 bit */
+#define NCT6683_REG_MON_HIGH(x)                (0x370 + (x) * 2)       /* 8 bit */
+#define NCT6683_REG_MON_LOW(x)         (0x371 + (x) * 2)       /* 8 bit */
+
+#define NCT6683_REG_FAN_MIN(x)         (0x3b8 + (x) * 2)       /* 16 bit */
+
+#define NCT6683_REG_CUSTOMER_ID                0x602
+#define NCT6683_CUSTOMER_ID_INTEL      0x805
+
+#define NCT6683_REG_BUILD_YEAR         0x604
+#define NCT6683_REG_BUILD_MONTH                0x605
+#define NCT6683_REG_BUILD_DAY          0x606
+#define NCT6683_REG_SERIAL             0x607
+#define NCT6683_REG_VERSION_HI         0x608
+#define NCT6683_REG_VERSION_LO         0x609
+
+#define NCT6683_REG_CR_CASEOPEN                0xe8
+#define NCT6683_CR_CASEOPEN_MASK       (1 << 7)
+
+#define NCT6683_REG_CR_BEEP            0xe0
+#define NCT6683_CR_BEEP_MASK           (1 << 6)
+
+static const char *const nct6683_mon_label[] = {
+       NULL,   /* disabled */
+       "Local",
+       "Diode 0 (curr)",
+       "Diode 1 (curr)",
+       "Diode 2 (curr)",
+       "Diode 0 (volt)",
+       "Diode 1 (volt)",
+       "Diode 2 (volt)",
+       "Thermistor 14",
+       "Thermistor 15",
+       "Thermistor 16",
+       "Thermistor 0",
+       "Thermistor 1",
+       "Thermistor 2",
+       "Thermistor 3",
+       "Thermistor 4",
+       "Thermistor 5",         /* 0x10 */
+       "Thermistor 6",
+       "Thermistor 7",
+       "Thermistor 8",
+       "Thermistor 9",
+       "Thermistor 10",
+       "Thermistor 11",
+       "Thermistor 12",
+       "Thermistor 13",
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+       "PECI 0.0",             /* 0x20 */
+       "PECI 1.0",
+       "PECI 2.0",
+       "PECI 3.0",
+       "PECI 0.1",
+       "PECI 1.1",
+       "PECI 2.1",
+       "PECI 3.1",
+       "PECI DIMM 0",
+       "PECI DIMM 1",
+       "PECI DIMM 2",
+       "PECI DIMM 3",
+       NULL, NULL, NULL, NULL,
+       "PCH CPU",              /* 0x30 */
+       "PCH CHIP",
+       "PCH CHIP CPU MAX",
+       "PCH MCH",
+       "PCH DIMM 0",
+       "PCH DIMM 1",
+       "PCH DIMM 2",
+       "PCH DIMM 3",
+       "SMBus 0",
+       "SMBus 1",
+       "SMBus 2",
+       "SMBus 3",
+       "SMBus 4",
+       "SMBus 5",
+       "DIMM 0",
+       "DIMM 1",
+       "DIMM 2",               /* 0x40 */
+       "DIMM 3",
+       "AMD TSI Addr 90h",
+       "AMD TSI Addr 92h",
+       "AMD TSI Addr 94h",
+       "AMD TSI Addr 96h",
+       "AMD TSI Addr 98h",
+       "AMD TSI Addr 9ah",
+       "AMD TSI Addr 9ch",
+       "AMD TSI Addr 9dh",
+       NULL, NULL, NULL, NULL, NULL, NULL,
+       "Virtual 0",            /* 0x50 */
+       "Virtual 1",
+       "Virtual 2",
+       "Virtual 3",
+       "Virtual 4",
+       "Virtual 5",
+       "Virtual 6",
+       "Virtual 7",
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+       "VCC",                  /* 0x60 voltage sensors */
+       "VSB",
+       "AVSB",
+       "VTT",
+       "VBAT",
+       "VREF",
+       "VIN0",
+       "VIN1",
+       "VIN2",
+       "VIN3",
+       "VIN4",
+       "VIN5",
+       "VIN6",
+       "VIN7",
+       "VIN8",
+       "VIN9",
+       "VIN10",
+       "VIN11",
+       "VIN12",
+       "VIN13",
+       "VIN14",
+       "VIN15",
+       "VIN16",
+};
+
+#define NUM_MON_LABELS         ARRAY_SIZE(nct6683_mon_label)
+#define MON_VOLTAGE_START      0x60
+
+/* ------------------------------------------------------- */
+
+struct nct6683_data {
+       int addr;               /* IO base of EC space */
+       int sioreg;             /* SIO register */
+       enum kinds kind;
+       u16 customer_id;
+
+       struct device *hwmon_dev;
+       const struct attribute_group *groups[6];
+
+       int temp_num;                   /* number of temperature attributes */
+       u8 temp_index[NCT6683_NUM_REG_MON];
+       u8 temp_src[NCT6683_NUM_REG_MON];
+
+       u8 in_num;                      /* number of voltage attributes */
+       u8 in_index[NCT6683_NUM_REG_MON];
+       u8 in_src[NCT6683_NUM_REG_MON];
+
+       struct mutex update_lock;       /* used to protect sensor updates */
+       bool valid;                     /* true if following fields are valid */
+       unsigned long last_updated;     /* In jiffies */
+
+       /* Voltage attribute values */
+       u8 in[3][NCT6683_NUM_REG_MON];  /* [0]=in, [1]=in_max, [2]=in_min */
+
+       /* Temperature attribute values */
+       s16 temp_in[NCT6683_NUM_REG_MON];
+       s8 temp[4][NCT6683_NUM_REG_MON];/* [0]=min, [1]=max, [2]=hyst,
+                                        * [3]=crit
+                                        */
+
+       /* Fan attribute values */
+       unsigned int rpm[NCT6683_NUM_REG_FAN];
+       u16 fan_min[NCT6683_NUM_REG_FAN];
+       u8 fanin_cfg[NCT6683_NUM_REG_FAN];
+       u8 fanout_cfg[NCT6683_NUM_REG_FAN];
+       u16 have_fan;                   /* some fan inputs can be disabled */
+
+       u8 have_pwm;
+       u8 pwm[NCT6683_NUM_REG_PWM];
+
+#ifdef CONFIG_PM
+       /* Remember extra register values over suspend/resume */
+       u8 hwm_cfg;
+#endif
+};
+
+struct nct6683_sio_data {
+       int sioreg;
+       enum kinds kind;
+};
+
+struct sensor_device_template {
+       struct device_attribute dev_attr;
+       union {
+               struct {
+                       u8 nr;
+                       u8 index;
+               } s;
+               int index;
+       } u;
+       bool s2;        /* true if both index and nr are used */
+};
+
+struct sensor_device_attr_u {
+       union {
+               struct sensor_device_attribute a1;
+               struct sensor_device_attribute_2 a2;
+       } u;
+       char name[32];
+};
+
+#define __TEMPLATE_ATTR(_template, _mode, _show, _store) {     \
+       .attr = {.name = _template, .mode = _mode },            \
+       .show   = _show,                                        \
+       .store  = _store,                                       \
+}
+
+#define SENSOR_DEVICE_TEMPLATE(_template, _mode, _show, _store, _index)        \
+       { .dev_attr = __TEMPLATE_ATTR(_template, _mode, _show, _store), \
+         .u.index = _index,                                            \
+         .s2 = false }
+
+#define SENSOR_DEVICE_TEMPLATE_2(_template, _mode, _show, _store,      \
+                                _nr, _index)                           \
+       { .dev_attr = __TEMPLATE_ATTR(_template, _mode, _show, _store), \
+         .u.s.index = _index,                                          \
+         .u.s.nr = _nr,                                                \
+         .s2 = true }
+
+#define SENSOR_TEMPLATE(_name, _template, _mode, _show, _store, _index)        \
+static struct sensor_device_template sensor_dev_template_##_name       \
+       = SENSOR_DEVICE_TEMPLATE(_template, _mode, _show, _store,       \
+                                _index)
+
+#define SENSOR_TEMPLATE_2(_name, _template, _mode, _show, _store,      \
+                         _nr, _index)                                  \
+static struct sensor_device_template sensor_dev_template_##_name       \
+       = SENSOR_DEVICE_TEMPLATE_2(_template, _mode, _show, _store,     \
+                                _nr, _index)
+
+struct sensor_template_group {
+       struct sensor_device_template **templates;
+       umode_t (*is_visible)(struct kobject *, struct attribute *, int);
+       int base;
+};
+
+static struct attribute_group *
+nct6683_create_attr_group(struct device *dev, struct sensor_template_group *tg,
+                         int repeat)
+{
+       struct sensor_device_attribute_2 *a2;
+       struct sensor_device_attribute *a;
+       struct sensor_device_template **t;
+       struct sensor_device_attr_u *su;
+       struct attribute_group *group;
+       struct attribute **attrs;
+       int i, j, count;
+
+       if (repeat <= 0)
+               return ERR_PTR(-EINVAL);
+
+       t = tg->templates;
+       for (count = 0; *t; t++, count++)
+               ;
+
+       if (count == 0)
+               return ERR_PTR(-EINVAL);
+
+       group = devm_kzalloc(dev, sizeof(*group), GFP_KERNEL);
+       if (group == NULL)
+               return ERR_PTR(-ENOMEM);
+
+       attrs = devm_kzalloc(dev, sizeof(*attrs) * (repeat * count + 1),
+                            GFP_KERNEL);
+       if (attrs == NULL)
+               return ERR_PTR(-ENOMEM);
+
+       su = devm_kzalloc(dev, sizeof(*su) * repeat * count,
+                         GFP_KERNEL);
+       if (su == NULL)
+               return ERR_PTR(-ENOMEM);
+
+       group->attrs = attrs;
+       group->is_visible = tg->is_visible;
+
+       for (i = 0; i < repeat; i++) {
+               t = tg->templates;
+               for (j = 0; *t != NULL; j++) {
+                       snprintf(su->name, sizeof(su->name),
+                                (*t)->dev_attr.attr.name, tg->base + i);
+                       if ((*t)->s2) {
+                               a2 = &su->u.a2;
+                               a2->dev_attr.attr.name = su->name;
+                               a2->nr = (*t)->u.s.nr + i;
+                               a2->index = (*t)->u.s.index;
+                               a2->dev_attr.attr.mode =
+                                 (*t)->dev_attr.attr.mode;
+                               a2->dev_attr.show = (*t)->dev_attr.show;
+                               a2->dev_attr.store = (*t)->dev_attr.store;
+                               *attrs = &a2->dev_attr.attr;
+                       } else {
+                               a = &su->u.a1;
+                               a->dev_attr.attr.name = su->name;
+                               a->index = (*t)->u.index + i;
+                               a->dev_attr.attr.mode =
+                                 (*t)->dev_attr.attr.mode;
+                               a->dev_attr.show = (*t)->dev_attr.show;
+                               a->dev_attr.store = (*t)->dev_attr.store;
+                               *attrs = &a->dev_attr.attr;
+                       }
+                       attrs++;
+                       su++;
+                       t++;
+               }
+       }
+
+       return group;
+}
+
+/* LSB is 16 mV, except for the following sources, where it is 32 mV */
+#define MON_SRC_VCC    0x60
+#define MON_SRC_VSB    0x61
+#define MON_SRC_AVSB   0x62
+#define MON_SRC_VBAT   0x64
+
+static inline long in_from_reg(u16 reg, u8 src)
+{
+       int scale = 16;
+
+       if (src == MON_SRC_VCC || src == MON_SRC_VSB || src == MON_SRC_AVSB ||
+           src == MON_SRC_VBAT)
+               scale <<= 1;
+       return reg * scale;
+}
+
+static inline u16 in_to_reg(u32 val, u8 src)
+{
+       int scale = 16;
+
+       if (src == MON_SRC_VCC || src == MON_SRC_VSB || src == MON_SRC_AVSB ||
+           src == MON_SRC_VBAT)
+               scale <<= 1;
+
+       return clamp_val(DIV_ROUND_CLOSEST(val, scale), 0, 127);
+}
+
+static u16 nct6683_read(struct nct6683_data *data, u16 reg)
+{
+       int res;
+
+       outb_p(0xff, data->addr + EC_PAGE_REG);         /* unlock */
+       outb_p(reg >> 8, data->addr + EC_PAGE_REG);
+       outb_p(reg & 0xff, data->addr + EC_INDEX_REG);
+       res = inb_p(data->addr + EC_DATA_REG);
+       return res;
+}
+
+static u16 nct6683_read16(struct nct6683_data *data, u16 reg)
+{
+       return (nct6683_read(data, reg) << 8) | nct6683_read(data, reg + 1);
+}
+
+static void nct6683_write(struct nct6683_data *data, u16 reg, u16 value)
+{
+       outb_p(0xff, data->addr + EC_PAGE_REG);         /* unlock */
+       outb_p(reg >> 8, data->addr + EC_PAGE_REG);
+       outb_p(reg & 0xff, data->addr + EC_INDEX_REG);
+       outb_p(value & 0xff, data->addr + EC_DATA_REG);
+}
+
+static int get_in_reg(struct nct6683_data *data, int nr, int index)
+{
+       int ch = data->in_index[index];
+       int reg = -EINVAL;
+
+       switch (nr) {
+       case 0:
+               reg = NCT6683_REG_MON(ch);
+               break;
+       case 1:
+               if (data->customer_id != NCT6683_CUSTOMER_ID_INTEL)
+                       reg = NCT6683_REG_MON_LOW(ch);
+               break;
+       case 2:
+               if (data->customer_id != NCT6683_CUSTOMER_ID_INTEL)
+                       reg = NCT6683_REG_MON_HIGH(ch);
+               break;
+       default:
+               break;
+       }
+       return reg;
+}
+
+static int get_temp_reg(struct nct6683_data *data, int nr, int index)
+{
+       int ch = data->temp_index[index];
+       int reg = -EINVAL;
+
+       switch (data->customer_id) {
+       case NCT6683_CUSTOMER_ID_INTEL:
+               switch (nr) {
+               default:
+               case 1: /* max */
+                       reg = NCT6683_REG_INTEL_TEMP_MAX(ch);
+                       break;
+               case 3: /* crit */
+                       reg = NCT6683_REG_INTEL_TEMP_CRIT(ch);
+                       break;
+               }
+               break;
+       default:
+               switch (nr) {
+               default:
+               case 0: /* min */
+                       reg = NCT6683_REG_MON_LOW(ch);
+                       break;
+               case 1: /* max */
+                       reg = NCT6683_REG_TEMP_MAX(ch);
+                       break;
+               case 2: /* hyst */
+                       reg = NCT6683_REG_TEMP_HYST(ch);
+                       break;
+               case 3: /* crit */
+                       reg = NCT6683_REG_MON_HIGH(ch);
+                       break;
+               }
+               break;
+       }
+       return reg;
+}
+
+static void nct6683_update_pwm(struct device *dev)
+{
+       struct nct6683_data *data = dev_get_drvdata(dev);
+       int i;
+
+       for (i = 0; i < NCT6683_NUM_REG_PWM; i++) {
+               if (!(data->have_pwm & (1 << i)))
+                       continue;
+               data->pwm[i] = nct6683_read(data, NCT6683_REG_PWM(i));
+       }
+}
+
+static struct nct6683_data *nct6683_update_device(struct device *dev)
+{
+       struct nct6683_data *data = dev_get_drvdata(dev);
+       int i, j;
+
+       mutex_lock(&data->update_lock);
+
+       if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
+               /* Measured voltages and limits */
+               for (i = 0; i < data->in_num; i++) {
+                       for (j = 0; j < 3; j++) {
+                               int reg = get_in_reg(data, j, i);
+
+                               if (reg >= 0)
+                                       data->in[j][i] =
+                                               nct6683_read(data, reg);
+                       }
+               }
+
+               /* Measured temperatures and limits */
+               for (i = 0; i < data->temp_num; i++) {
+                       u8 ch = data->temp_index[i];
+
+                       data->temp_in[i] = nct6683_read16(data,
+                                                         NCT6683_REG_MON(ch));
+                       for (j = 0; j < 4; j++) {
+                               int reg = get_temp_reg(data, j, i);
+
+                               if (reg >= 0)
+                                       data->temp[j][i] =
+                                               nct6683_read(data, reg);
+                       }
+               }
+
+               /* Measured fan speeds and limits */
+               for (i = 0; i < ARRAY_SIZE(data->rpm); i++) {
+                       if (!(data->have_fan & (1 << i)))
+                               continue;
+
+                       data->rpm[i] = nct6683_read16(data,
+                                               NCT6683_REG_FAN_RPM(i));
+                       data->fan_min[i] = nct6683_read16(data,
+                                               NCT6683_REG_FAN_MIN(i));
+               }
+
+               nct6683_update_pwm(dev);
+
+               data->last_updated = jiffies;
+               data->valid = true;
+       }
+
+       mutex_unlock(&data->update_lock);
+       return data;
+}
+
+/*
+ * Sysfs callback functions
+ */
+static ssize_t
+show_in_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+       struct nct6683_data *data = nct6683_update_device(dev);
+       int nr = sattr->index;
+
+       return sprintf(buf, "%s\n", nct6683_mon_label[data->in_src[nr]]);
+}
+
+static ssize_t
+show_in_reg(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+       struct nct6683_data *data = nct6683_update_device(dev);
+       int index = sattr->index;
+       int nr = sattr->nr;
+
+       return sprintf(buf, "%ld\n",
+                      in_from_reg(data->in[index][nr], data->in_index[index]));
+}
+
+static umode_t nct6683_in_is_visible(struct kobject *kobj,
+                                    struct attribute *attr, int index)
+{
+       struct device *dev = container_of(kobj, struct device, kobj);
+       struct nct6683_data *data = dev_get_drvdata(dev);
+       int nr = index % 4;     /* attribute */
+
+       /*
+        * Voltage limits exist for Intel boards,
+        * but register location and encoding is unknown
+        */
+       if ((nr == 2 || nr == 3) &&
+           data->customer_id == NCT6683_CUSTOMER_ID_INTEL)
+               return 0;
+
+       return attr->mode;
+}
+
+SENSOR_TEMPLATE(in_label, "in%d_label", S_IRUGO, show_in_label, NULL, 0);
+SENSOR_TEMPLATE_2(in_input, "in%d_input", S_IRUGO, show_in_reg, NULL, 0, 0);
+SENSOR_TEMPLATE_2(in_min, "in%d_min", S_IRUGO, show_in_reg, NULL, 0, 1);
+SENSOR_TEMPLATE_2(in_max, "in%d_max", S_IRUGO, show_in_reg, NULL, 0, 2);
+
+static struct sensor_device_template *nct6683_attributes_in_template[] = {
+       &sensor_dev_template_in_label,
+       &sensor_dev_template_in_input,
+       &sensor_dev_template_in_min,
+       &sensor_dev_template_in_max,
+       NULL
+};
+
+static struct sensor_template_group nct6683_in_template_group = {
+       .templates = nct6683_attributes_in_template,
+       .is_visible = nct6683_in_is_visible,
+};
+
+static ssize_t
+show_fan(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+       struct nct6683_data *data = nct6683_update_device(dev);
+
+       return sprintf(buf, "%d\n", data->rpm[sattr->index]);
+}
+
+static ssize_t
+show_fan_min(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct nct6683_data *data = nct6683_update_device(dev);
+       struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+       int nr = sattr->index;
+
+       return sprintf(buf, "%d\n", data->fan_min[nr]);
+}
+
+static ssize_t
+show_fan_pulses(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+       struct nct6683_data *data = nct6683_update_device(dev);
+
+       return sprintf(buf, "%d\n",
+                      ((data->fanin_cfg[sattr->index] >> 5) & 0x03) + 1);
+}
+
+static umode_t nct6683_fan_is_visible(struct kobject *kobj,
+                                     struct attribute *attr, int index)
+{
+       struct device *dev = container_of(kobj, struct device, kobj);
+       struct nct6683_data *data = dev_get_drvdata(dev);
+       int fan = index / 3;    /* fan index */
+       int nr = index % 3;     /* attribute index */
+
+       if (!(data->have_fan & (1 << fan)))
+               return 0;
+
+       /*
+        * Intel may have minimum fan speed limits,
+        * but register location and encoding are unknown.
+        */
+       if (nr == 2 && data->customer_id == NCT6683_CUSTOMER_ID_INTEL)
+               return 0;
+
+       return attr->mode;
+}
+
+SENSOR_TEMPLATE(fan_input, "fan%d_input", S_IRUGO, show_fan, NULL, 0);
+SENSOR_TEMPLATE(fan_pulses, "fan%d_pulses", S_IRUGO, show_fan_pulses, NULL, 0);
+SENSOR_TEMPLATE(fan_min, "fan%d_min", S_IRUGO, show_fan_min, NULL, 0);
+
+/*
+ * nct6683_fan_is_visible uses the index into the following array
+ * to determine if attributes should be created or not.
+ * Any change in order or content must be matched.
+ */
+static struct sensor_device_template *nct6683_attributes_fan_template[] = {
+       &sensor_dev_template_fan_input,
+       &sensor_dev_template_fan_pulses,
+       &sensor_dev_template_fan_min,
+       NULL
+};
+
+static struct sensor_template_group nct6683_fan_template_group = {
+       .templates = nct6683_attributes_fan_template,
+       .is_visible = nct6683_fan_is_visible,
+       .base = 1,
+};
+
+static ssize_t
+show_temp_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+       struct nct6683_data *data = nct6683_update_device(dev);
+       int nr = sattr->index;
+
+       return sprintf(buf, "%s\n", nct6683_mon_label[data->temp_src[nr]]);
+}
+
+static ssize_t
+show_temp8(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+       struct nct6683_data *data = nct6683_update_device(dev);
+       int index = sattr->index;
+       int nr = sattr->nr;
+
+       return sprintf(buf, "%d\n", data->temp[index][nr] * 1000);
+}
+
+static ssize_t
+show_temp_hyst(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+       struct nct6683_data *data = nct6683_update_device(dev);
+       int nr = sattr->index;
+       int temp = data->temp[1][nr] - data->temp[2][nr];
+
+       return sprintf(buf, "%d\n", temp * 1000);
+}
+
+static ssize_t
+show_temp16(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+       struct nct6683_data *data = nct6683_update_device(dev);
+       int index = sattr->index;
+
+       return sprintf(buf, "%d\n", (data->temp_in[index] / 128) * 500);
+}
+
+/*
+ * Temperature sensor type is determined by temperature source
+ * and can not be modified.
+ * 0x02..0x07: Thermal diode
+ * 0x08..0x18: Thermistor
+ * 0x20..0x2b: Intel PECI
+ * 0x42..0x49: AMD TSI
+ * Others are unspecified (not visible)
+ */
+
+static int get_temp_type(u8 src)
+{
+       if (src >= 0x02 && src <= 0x07)
+               return 3;       /* thermal diode */
+       else if (src >= 0x08 && src <= 0x18)
+               return 4;       /* thermistor */
+       else if (src >= 0x20 && src <= 0x2b)
+               return 6;       /* PECI */
+       else if (src >= 0x42 && src <= 0x49)
+               return 5;
+
+       return 0;
+}
+
+static ssize_t
+show_temp_type(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct nct6683_data *data = nct6683_update_device(dev);
+       struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+       int nr = sattr->index;
+       return sprintf(buf, "%d\n", get_temp_type(data->temp_src[nr]));
+}
+
+static umode_t nct6683_temp_is_visible(struct kobject *kobj,
+                                      struct attribute *attr, int index)
+{
+       struct device *dev = container_of(kobj, struct device, kobj);
+       struct nct6683_data *data = dev_get_drvdata(dev);
+       int temp = index / 7;   /* temp index */
+       int nr = index % 7;     /* attribute index */
+
+       /*
+        * Intel does not have low temperature limits or temperature hysteresis
+        * registers, or at least register location and encoding is unknown.
+        */
+       if ((nr == 2 || nr == 4) &&
+           data->customer_id == NCT6683_CUSTOMER_ID_INTEL)
+               return 0;
+
+       if (nr == 6 && get_temp_type(data->temp_src[temp]) == 0)
+               return 0;                               /* type */
+
+       return attr->mode;
+}
+
+SENSOR_TEMPLATE(temp_input, "temp%d_input", S_IRUGO, show_temp16, NULL, 0);
+SENSOR_TEMPLATE(temp_label, "temp%d_label", S_IRUGO, show_temp_label, NULL, 0);
+SENSOR_TEMPLATE_2(temp_min, "temp%d_min", S_IRUGO, show_temp8, NULL, 0, 0);
+SENSOR_TEMPLATE_2(temp_max, "temp%d_max", S_IRUGO, show_temp8, NULL, 0, 1);
+SENSOR_TEMPLATE(temp_max_hyst, "temp%d_max_hyst", S_IRUGO, show_temp_hyst, NULL,
+               0);
+SENSOR_TEMPLATE_2(temp_crit, "temp%d_crit", S_IRUGO, show_temp8, NULL, 0, 3);
+SENSOR_TEMPLATE(temp_type, "temp%d_type", S_IRUGO, show_temp_type, NULL, 0);
+
+/*
+ * nct6683_temp_is_visible uses the index into the following array
+ * to determine if attributes should be created or not.
+ * Any change in order or content must be matched.
+ */
+static struct sensor_device_template *nct6683_attributes_temp_template[] = {
+       &sensor_dev_template_temp_input,
+       &sensor_dev_template_temp_label,
+       &sensor_dev_template_temp_min,          /* 2 */
+       &sensor_dev_template_temp_max,          /* 3 */
+       &sensor_dev_template_temp_max_hyst,     /* 4 */
+       &sensor_dev_template_temp_crit,         /* 5 */
+       &sensor_dev_template_temp_type,         /* 6 */
+       NULL
+};
+
+static struct sensor_template_group nct6683_temp_template_group = {
+       .templates = nct6683_attributes_temp_template,
+       .is_visible = nct6683_temp_is_visible,
+       .base = 1,
+};
+
+static ssize_t
+show_pwm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct nct6683_data *data = nct6683_update_device(dev);
+       struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+       int index = sattr->index;
+
+       return sprintf(buf, "%d\n", data->pwm[index]);
+}
+
+SENSOR_TEMPLATE(pwm, "pwm%d", S_IRUGO, show_pwm, NULL, 0);
+
+static umode_t nct6683_pwm_is_visible(struct kobject *kobj,
+                                     struct attribute *attr, int index)
+{
+       struct device *dev = container_of(kobj, struct device, kobj);
+       struct nct6683_data *data = dev_get_drvdata(dev);
+       int pwm = index;        /* pwm index */
+
+       if (!(data->have_pwm & (1 << pwm)))
+               return 0;
+
+       return attr->mode;
+}
+
+static struct sensor_device_template *nct6683_attributes_pwm_template[] = {
+       &sensor_dev_template_pwm,
+       NULL
+};
+
+static struct sensor_template_group nct6683_pwm_template_group = {
+       .templates = nct6683_attributes_pwm_template,
+       .is_visible = nct6683_pwm_is_visible,
+       .base = 1,
+};
+
+static ssize_t
+show_global_beep(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct nct6683_data *data = dev_get_drvdata(dev);
+       int ret;
+       u8 reg;
+
+       mutex_lock(&data->update_lock);
+
+       ret = superio_enter(data->sioreg);
+       if (ret)
+               goto error;
+       superio_select(data->sioreg, NCT6683_LD_HWM);
+       reg = superio_inb(data->sioreg, NCT6683_REG_CR_BEEP);
+       superio_exit(data->sioreg);
+
+       mutex_unlock(&data->update_lock);
+
+       return sprintf(buf, "%u\n", !!(reg & NCT6683_CR_BEEP_MASK));
+
+error:
+       mutex_unlock(&data->update_lock);
+       return ret;
+}
+
+static ssize_t
+store_global_beep(struct device *dev, struct device_attribute *attr,
+                 const char *buf, size_t count)
+{
+       struct nct6683_data *data = dev_get_drvdata(dev);
+       unsigned long val;
+       u8 reg;
+       int ret;
+
+       if (kstrtoul(buf, 10, &val) || (val != 0 && val != 1))
+               return -EINVAL;
+
+       mutex_lock(&data->update_lock);
+
+       ret = superio_enter(data->sioreg);
+       if (ret) {
+               count = ret;
+               goto error;
+       }
+
+       superio_select(data->sioreg, NCT6683_LD_HWM);
+       reg = superio_inb(data->sioreg, NCT6683_REG_CR_BEEP);
+       if (val)
+               reg |= NCT6683_CR_BEEP_MASK;
+       else
+               reg &= ~NCT6683_CR_BEEP_MASK;
+       superio_outb(data->sioreg, NCT6683_REG_CR_BEEP, reg);
+       superio_exit(data->sioreg);
+error:
+       mutex_unlock(&data->update_lock);
+       return count;
+}
+
+/* Case open detection */
+
+static ssize_t
+show_caseopen(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct nct6683_data *data = dev_get_drvdata(dev);
+       int ret;
+       u8 reg;
+
+       mutex_lock(&data->update_lock);
+
+       ret = superio_enter(data->sioreg);
+       if (ret)
+               goto error;
+       superio_select(data->sioreg, NCT6683_LD_ACPI);
+       reg = superio_inb(data->sioreg, NCT6683_REG_CR_CASEOPEN);
+       superio_exit(data->sioreg);
+
+       mutex_unlock(&data->update_lock);
+
+       return sprintf(buf, "%u\n", !(reg & NCT6683_CR_CASEOPEN_MASK));
+
+error:
+       mutex_unlock(&data->update_lock);
+       return ret;
+}
+
+static ssize_t
+clear_caseopen(struct device *dev, struct device_attribute *attr,
+              const char *buf, size_t count)
+{
+       struct nct6683_data *data = dev_get_drvdata(dev);
+       unsigned long val;
+       u8 reg;
+       int ret;
+
+       if (kstrtoul(buf, 10, &val) || val != 0)
+               return -EINVAL;
+
+       mutex_lock(&data->update_lock);
+
+       /*
+        * Use CR registers to clear caseopen status.
+        * Caseopen is activ low, clear by writing 1 into the register.
+        */
+
+       ret = superio_enter(data->sioreg);
+       if (ret) {
+               count = ret;
+               goto error;
+       }
+
+       superio_select(data->sioreg, NCT6683_LD_ACPI);
+       reg = superio_inb(data->sioreg, NCT6683_REG_CR_CASEOPEN);
+       reg |= NCT6683_CR_CASEOPEN_MASK;
+       superio_outb(data->sioreg, NCT6683_REG_CR_CASEOPEN, reg);
+       reg &= ~NCT6683_CR_CASEOPEN_MASK;
+       superio_outb(data->sioreg, NCT6683_REG_CR_CASEOPEN, reg);
+       superio_exit(data->sioreg);
+
+       data->valid = false;    /* Force cache refresh */
+error:
+       mutex_unlock(&data->update_lock);
+       return count;
+}
+
+static DEVICE_ATTR(intrusion0_alarm, S_IWUSR | S_IRUGO, show_caseopen,
+                  clear_caseopen);
+static DEVICE_ATTR(beep_enable, S_IWUSR | S_IRUGO, show_global_beep,
+                  store_global_beep);
+
+static struct attribute *nct6683_attributes_other[] = {
+       &dev_attr_intrusion0_alarm.attr,
+       &dev_attr_beep_enable.attr,
+       NULL
+};
+
+static const struct attribute_group nct6683_group_other = {
+       .attrs = nct6683_attributes_other,
+};
+
+/* Get the monitoring functions started */
+static inline void nct6683_init_device(struct nct6683_data *data)
+{
+       u8 tmp;
+
+       /* Start hardware monitoring if needed */
+       tmp = nct6683_read(data, NCT6683_HWM_CFG);
+       if (!(tmp & 0x80))
+               nct6683_write(data, NCT6683_HWM_CFG, tmp | 0x80);
+}
+
+/*
+ * There are a total of 24 fan inputs. Each can be configured as input
+ * or as output. A maximum of 16 inputs and 8 outputs is configurable.
+ */
+static void
+nct6683_setup_fans(struct nct6683_data *data)
+{
+       int i;
+       u8 reg;
+
+       for (i = 0; i < NCT6683_NUM_REG_FAN; i++) {
+               reg = nct6683_read(data, NCT6683_REG_FANIN_CFG(i));
+               if (reg & 0x80)
+                       data->have_fan |= 1 << i;
+               data->fanin_cfg[i] = reg;
+       }
+       for (i = 0; i < NCT6683_NUM_REG_PWM; i++) {
+               reg = nct6683_read(data, NCT6683_REG_FANOUT_CFG(i));
+               if (reg & 0x80)
+                       data->have_pwm |= 1 << i;
+               data->fanout_cfg[i] = reg;
+       }
+}
+
+/*
+ * Translation from monitoring register to temperature and voltage attributes
+ * ==========================================================================
+ *
+ * There are a total of 32 monitoring registers. Each can be assigned to either
+ * a temperature or voltage monitoring source.
+ * NCT6683_REG_MON_CFG(x) defines assignment for each monitoring source.
+ *
+ * Temperature and voltage attribute mapping is determined by walking through
+ * the NCT6683_REG_MON_CFG registers. If the assigned source is
+ * a temperature, temp_index[n] is set to the monitor register index, and
+ * temp_src[n] is set to the temperature source. If the assigned source is
+ * a voltage, the respective values are stored in in_index[] and in_src[],
+ * respectively.
+ */
+
+static void nct6683_setup_sensors(struct nct6683_data *data)
+{
+       u8 reg;
+       int i;
+
+       data->temp_num = 0;
+       data->in_num = 0;
+       for (i = 0; i < NCT6683_NUM_REG_MON; i++) {
+               reg = nct6683_read(data, NCT6683_REG_MON_CFG(i)) & 0x7f;
+               /* Ignore invalid assignments */
+               if (reg >= NUM_MON_LABELS)
+                       continue;
+               /* Skip if disabled or reserved */
+               if (nct6683_mon_label[reg] == NULL)
+                       continue;
+               if (reg < MON_VOLTAGE_START) {
+                       data->temp_index[data->temp_num] = i;
+                       data->temp_src[data->temp_num] = reg;
+                       data->temp_num++;
+               } else {
+                       data->in_index[data->in_num] = i;
+                       data->in_src[data->in_num] = reg;
+                       data->in_num++;
+               }
+       }
+}
+
+static int nct6683_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct nct6683_sio_data *sio_data = dev->platform_data;
+       struct attribute_group *group;
+       struct nct6683_data *data;
+       struct device *hwmon_dev;
+       struct resource *res;
+       int groups = 0;
+
+       res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+       if (!devm_request_region(dev, res->start, IOREGION_LENGTH, DRVNAME))
+               return -EBUSY;
+
+       data = devm_kzalloc(dev, sizeof(struct nct6683_data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       data->kind = sio_data->kind;
+       data->sioreg = sio_data->sioreg;
+       data->addr = res->start;
+       mutex_init(&data->update_lock);
+       platform_set_drvdata(pdev, data);
+
+       data->customer_id = nct6683_read16(data, NCT6683_REG_CUSTOMER_ID);
+
+       nct6683_init_device(data);
+       nct6683_setup_fans(data);
+       nct6683_setup_sensors(data);
+
+       /* Register sysfs hooks */
+
+       if (data->have_pwm) {
+               group = nct6683_create_attr_group(dev,
+                                                 &nct6683_pwm_template_group,
+                                                 fls(data->have_pwm));
+               if (IS_ERR(group))
+                       return PTR_ERR(group);
+               data->groups[groups++] = group;
+       }
+
+       if (data->in_num) {
+               group = nct6683_create_attr_group(dev,
+                                                 &nct6683_in_template_group,
+                                                 data->in_num);
+               if (IS_ERR(group))
+                       return PTR_ERR(group);
+               data->groups[groups++] = group;
+       }
+
+       if (data->have_fan) {
+               group = nct6683_create_attr_group(dev,
+                                                 &nct6683_fan_template_group,
+                                                 fls(data->have_fan));
+               if (IS_ERR(group))
+                       return PTR_ERR(group);
+               data->groups[groups++] = group;
+       }
+
+       if (data->temp_num) {
+               group = nct6683_create_attr_group(dev,
+                                                 &nct6683_temp_template_group,
+                                                 data->temp_num);
+               if (IS_ERR(group))
+                       return PTR_ERR(group);
+               data->groups[groups++] = group;
+       }
+       data->groups[groups++] = &nct6683_group_other;
+
+       dev_info(dev, "%s EC firmware version %d.%d build %02x/%02x/%02x\n",
+                nct6683_chip_names[data->kind],
+                nct6683_read(data, NCT6683_REG_VERSION_HI),
+                nct6683_read(data, NCT6683_REG_VERSION_LO),
+                nct6683_read(data, NCT6683_REG_BUILD_MONTH),
+                nct6683_read(data, NCT6683_REG_BUILD_DAY),
+                nct6683_read(data, NCT6683_REG_BUILD_YEAR));
+
+       hwmon_dev = devm_hwmon_device_register_with_groups(dev,
+                       nct6683_device_names[data->kind], data, data->groups);
+       return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+#ifdef CONFIG_PM
+static int nct6683_suspend(struct device *dev)
+{
+       struct nct6683_data *data = nct6683_update_device(dev);
+
+       mutex_lock(&data->update_lock);
+       data->hwm_cfg = nct6683_read(data, NCT6683_HWM_CFG);
+       mutex_unlock(&data->update_lock);
+
+       return 0;
+}
+
+static int nct6683_resume(struct device *dev)
+{
+       struct nct6683_data *data = dev_get_drvdata(dev);
+
+       mutex_lock(&data->update_lock);
+
+       nct6683_write(data, NCT6683_HWM_CFG, data->hwm_cfg);
+
+       /* Force re-reading all values */
+       data->valid = false;
+       mutex_unlock(&data->update_lock);
+
+       return 0;
+}
+
+static const struct dev_pm_ops nct6683_dev_pm_ops = {
+       .suspend = nct6683_suspend,
+       .resume = nct6683_resume,
+       .freeze = nct6683_suspend,
+       .restore = nct6683_resume,
+};
+
+#define NCT6683_DEV_PM_OPS     (&nct6683_dev_pm_ops)
+#else
+#define NCT6683_DEV_PM_OPS     NULL
+#endif /* CONFIG_PM */
+
+static struct platform_driver nct6683_driver = {
+       .driver = {
+               .owner  = THIS_MODULE,
+               .name   = DRVNAME,
+               .pm     = NCT6683_DEV_PM_OPS,
+       },
+       .probe          = nct6683_probe,
+};
+
+static int __init nct6683_find(int sioaddr, struct nct6683_sio_data *sio_data)
+{
+       const char *board_vendor;
+       int addr;
+       u16 val;
+       int err;
+
+       /*
+        * Only run on Intel boards unless the 'force' module parameter is set
+        */
+       if (!force) {
+               board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR);
+               if (!board_vendor || strcmp(board_vendor, "Intel Corporation"))
+                       return -ENODEV;
+       }
+
+       err = superio_enter(sioaddr);
+       if (err)
+               return err;
+
+       val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8)
+              | superio_inb(sioaddr, SIO_REG_DEVID + 1);
+
+       switch (val & SIO_ID_MASK) {
+       case SIO_NCT6683_ID:
+               sio_data->kind = nct6683;
+               break;
+       default:
+               if (val != 0xffff)
+                       pr_debug("unsupported chip ID: 0x%04x\n", val);
+               goto fail;
+       }
+
+       /* We have a known chip, find the HWM I/O address */
+       superio_select(sioaddr, NCT6683_LD_HWM);
+       val = (superio_inb(sioaddr, SIO_REG_ADDR) << 8)
+           | superio_inb(sioaddr, SIO_REG_ADDR + 1);
+       addr = val & IOREGION_ALIGNMENT;
+       if (addr == 0) {
+               pr_err("EC base I/O port unconfigured\n");
+               goto fail;
+       }
+
+       /* Activate logical device if needed */
+       val = superio_inb(sioaddr, SIO_REG_ENABLE);
+       if (!(val & 0x01)) {
+               pr_err("EC is disabled\n");
+               goto fail;
+       }
+
+       superio_exit(sioaddr);
+       pr_info("Found %s or compatible chip at %#x:%#x\n",
+               nct6683_chip_names[sio_data->kind], sioaddr, addr);
+       sio_data->sioreg = sioaddr;
+
+       return addr;
+
+fail:
+       superio_exit(sioaddr);
+       return -ENODEV;
+}
+
+/*
+ * when Super-I/O functions move to a separate file, the Super-I/O
+ * bus will manage the lifetime of the device and this module will only keep
+ * track of the nct6683 driver. But since we use platform_device_alloc(), we
+ * must keep track of the device
+ */
+static struct platform_device *pdev[2];
+
+static int __init sensors_nct6683_init(void)
+{
+       struct nct6683_sio_data sio_data;
+       int sioaddr[2] = { 0x2e, 0x4e };
+       struct resource res;
+       bool found = false;
+       int address;
+       int i, err;
+
+       err = platform_driver_register(&nct6683_driver);
+       if (err)
+               return err;
+
+       /*
+        * initialize sio_data->kind and sio_data->sioreg.
+        *
+        * when Super-I/O functions move to a separate file, the Super-I/O
+        * driver will probe 0x2e and 0x4e and auto-detect the presence of a
+        * nct6683 hardware monitor, and call probe()
+        */
+       for (i = 0; i < ARRAY_SIZE(pdev); i++) {
+               address = nct6683_find(sioaddr[i], &sio_data);
+               if (address <= 0)
+                       continue;
+
+               found = true;
+
+               pdev[i] = platform_device_alloc(DRVNAME, address);
+               if (!pdev[i]) {
+                       err = -ENOMEM;
+                       goto exit_device_put;
+               }
+
+               err = platform_device_add_data(pdev[i], &sio_data,
+                                              sizeof(struct nct6683_sio_data));
+               if (err)
+                       goto exit_device_put;
+
+               memset(&res, 0, sizeof(res));
+               res.name = DRVNAME;
+               res.start = address + IOREGION_OFFSET;
+               res.end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1;
+               res.flags = IORESOURCE_IO;
+
+               err = acpi_check_resource_conflict(&res);
+               if (err) {
+                       platform_device_put(pdev[i]);
+                       pdev[i] = NULL;
+                       continue;
+               }
+
+               err = platform_device_add_resources(pdev[i], &res, 1);
+               if (err)
+                       goto exit_device_put;
+
+               /* platform_device_add calls probe() */
+               err = platform_device_add(pdev[i]);
+               if (err)
+                       goto exit_device_put;
+       }
+       if (!found) {
+               err = -ENODEV;
+               goto exit_unregister;
+       }
+
+       return 0;
+
+exit_device_put:
+       for (i = 0; i < ARRAY_SIZE(pdev); i++) {
+               if (pdev[i])
+                       platform_device_put(pdev[i]);
+       }
+exit_unregister:
+       platform_driver_unregister(&nct6683_driver);
+       return err;
+}
+
+static void __exit sensors_nct6683_exit(void)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(pdev); i++) {
+               if (pdev[i])
+                       platform_device_unregister(pdev[i]);
+       }
+       platform_driver_unregister(&nct6683_driver);
+}
+
+MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>");
+MODULE_DESCRIPTION("NCT6683D driver");
+MODULE_LICENSE("GPL");
+
+module_init(sensors_nct6683_init);
+module_exit(sensors_nct6683_exit);