mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* Driver for Nuvoton NCT6687D eSIO chip
@ 2023-07-20 11:09 Thomas R
  2023-07-20 13:25 ` Guenter Roeck
  0 siblings, 1 reply; 2+ messages in thread
From: Thomas R @ 2023-07-20 11:09 UTC (permalink / raw)
  To: jdelvare, linux, linux-hwmon, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 328 bytes --]

To whom it may concern,

I am trying to submit a patch for driver Driver for Nuvoton NCT6687D 
eSIO chip under drivers/hwmon. This driver is confirmed to be working 
for motherboards series B550 and B650.

Credits for creating this driver should go to Frederic BOLTZ 
https://github.com/Fred78290/nct6687d

Attached is my patch

[-- Attachment #2: driver_nct6687.patch --]
[-- Type: text/x-patch, Size: 36570 bytes --]

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 5b3b76477b0e..47a1bcc365f3 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1518,6 +1518,16 @@ config SENSORS_NCT6683
 	  This driver can also be built as a module. If so, the module
 	  will be called nct6683.
 
+config SENSORS_NCT6687
+	tristate "Nuvoton NCT6687D"
+	depends on !PPC
+	help
+	  If you say yes here you get support for the hardware monitoring
+	  functionality of the Nuvoton NCT6687D eSIO chip.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called nct6687.
+
 config SENSORS_NCT6775_CORE
 	tristate
 	select REGMAP
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 88712b5031c8..fb08e00dc931 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -158,6 +158,7 @@ obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
 obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
 obj-$(CONFIG_SENSORS_MR75203)	+= mr75203.o
 obj-$(CONFIG_SENSORS_NCT6683)	+= nct6683.o
+obj-$(CONFIG_SENSORS_NCT6687)	+= nct6687.o
 obj-$(CONFIG_SENSORS_NCT6775_CORE) += nct6775-core.o
 nct6775-objs			:= nct6775-platform.o
 obj-$(CONFIG_SENSORS_NCT6775)	+= nct6775.o
diff --git a/drivers/hwmon/nct6687.c b/drivers/hwmon/nct6687.c
new file mode 100644
index 000000000000..fed2c868fec7
--- /dev/null
+++ b/drivers/hwmon/nct6687.c
@@ -0,0 +1,1272 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * nct6687 - Driver for the hardware monitoring functionality of
+ *	       Nuvoton NCT6687 Super-I/O chips
+ *
+ * Copyright (C) 2020  Frederic Boltz <frederic.boltz@gmail.com>
+ *
+ * Derived from nct6683 driver
+ * Copyright (C) 2013  Guenter Roeck <linux@roeck-us.net>
+ * 
+ * Inspired of LibreHardwareMonitor
+ * https://github.com/LibreHardwareMonitor/LibreHardwareMonitor
+ * 
+ * Supports the following chips:
+ *
+ * Chip       #voltage   #fan    #pwm    #temp  chip ID
+ * nct6683    14(1)      8       8       7      0xc732  (partial support)
+ * nct6686d   21(1)      16      8       32(1)  0xd440
+ * nct6687    14(1)      8       8       7      0xd592
+ *
+ * Notes:
+ *	(1) Total number of voltage and 9 displayed.
+ */
+// #define DEBUG 1
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/delay.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>
+
+#define MIN(a,b) (((a)<(b))?(a):(b))
+#define MAX(a,b) (((a)>(b))?(a):(b))
+
+enum kinds
+{
+	nct6683,
+	nct6686,
+	nct6687
+};
+
+static bool force;
+static bool manual;
+
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Set to one to enable support for unknown vendors");
+
+module_param(manual, bool, 0);
+MODULE_PARM_DESC(manual, "Set voltage input and voltage label configured with external sensors file");
+
+static const char *const nct6687_device_names[] = {
+	"nct6683",
+	"nct6686",
+	"nct6687",
+};
+
+static const char *const nct6687_chip_names[] = {
+	"NCT6683D",
+	"NCT6686D",
+	"NCT6687D",
+};
+
+#define DRVNAME "nct6687"
+
+/*
+ * Super-I/O constants and functions
+ */
+
+#define NCT6687_LD_ACPI 0x0a
+#define NCT6687_LD_HWM 0x0b
+#define NCT6687_LD_VID 0x0d
+
+#define SIO_REG_LDSEL 0x07		 /* Logical device select */
+#define SIO_REG_DEVID 0x20		 /* Device ID (2 bytes) */
+#define SIO_REG_DEVREVISION 0x21 /* Device ID (2 bytes) */
+#define SIO_REG_ENABLE 0x30		 /* Logical device enable */
+#define SIO_REG_ADDR 0x60		 /* Logical device address (2 bytes) */
+
+#define SIO_NCT6683D_ID 0xc732
+#define SIO_NCT6686_ID 0xd440
+#define SIO_NCT6686D_ID 0xd441
+#define SIO_NCT6687_ID 0xd451 // 0xd592
+#define SIO_NCT6687D_ID 0xd592
+
+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_OFFSET 0 /* Use EC port 1 */
+#define IOREGION_LENGTH 4
+
+/* Common and NCT6687 specific data */
+
+#define NCT6687_NUM_REG_VOLTAGE (sizeof(nct6687_voltage_definition) / sizeof(struct voltage_reg))
+#define NCT6687_NUM_REG_TEMP 7
+#define NCT6687_NUM_REG_FAN 8
+#define NCT6687_NUM_REG_PWM 8
+
+#define NCT6687_REG_TEMP(x) (0x100 + (x)*2)
+#define NCT6687_REG_VOLTAGE(x) (0x120 + (x)*2)
+#define NCT6687_REG_FAN_RPM(x) (0x140 + (x)*2)
+#define NCT6687_REG_PWM(x) (0x160 + (x))
+#define NCT6687_REG_PWM_WRITE(x) (0xa28 + (x))
+
+#define NCT6687_HWM_CFG 0x180
+
+#define NCT6687_REG_MON_CFG(x) (0x1a0 + (x))
+#define NCT6687_REG_FANIN_CFG(x) (0xA00 + (x))
+#define NCT6687_REG_FANOUT_CFG(x) (0x1d0 + (x))
+
+#define NCT6687_REG_TEMP_HYST(x) (0x330 + (x))	/* 8 bit */
+#define NCT6687_REG_TEMP_MAX(x) (0x350 + (x))	/* 8 bit */
+#define NCT6687_REG_MON_HIGH(x) (0x370 + (x)*2) /* 8 bit */
+#define NCT6687_REG_MON_LOW(x) (0x371 + (x)*2)	/* 8 bit */
+
+#define NCT6687_REG_FAN_MIN(x) (0x3b8 + (x)*2) /* 16 bit */
+
+#define NCT6687_REG_FAN_CTRL_MODE(x) 0xA00
+#define NCT6687_REG_FAN_PWM_COMMAND(x) 0xA01
+#define NCT6687_FAN_CFG_REQ 0x80
+#define NCT6687_FAN_CFG_DONE 0x40
+
+#define NCT6687_REG_BUILD_YEAR 0x604
+#define NCT6687_REG_BUILD_MONTH 0x605
+#define NCT6687_REG_BUILD_DAY 0x606
+#define NCT6687_REG_SERIAL 0x607
+#define NCT6687_REG_VERSION_HI 0x608
+#define NCT6687_REG_VERSION_LO 0x609
+
+#define NCT6687_REG_CR_CASEOPEN 0xe8
+#define NCT6687_CR_CASEOPEN_MASK (1 << 7)
+
+#define NCT6687_REG_CR_BEEP 0xe0
+#define NCT6687_CR_BEEP_MASK (1 << 6)
+
+#define EC_SPACE_PAGE_REGISTER_OFFSET 0x04
+#define EC_SPACE_INDEX_REGISTER_OFFSET 0x05
+#define EC_SPACE_DATA_REGISTER_OFFSET 0x06
+#define EC_SPACE_PAGE_SELECT 0xFF
+
+struct voltage_reg
+{
+	u16 reg;
+	u16 multiplier;
+	const char *label;
+};
+
+static struct voltage_reg nct6687_voltage_definition[] = {
+	// +12V
+	{
+		.reg = 0,
+		.multiplier = 12,
+		.label = "+12V",
+	},
+	// + 5V
+	{
+		.reg = 1,
+		.multiplier = 5,
+		.label = "+5V",
+	},
+	// +3.3V
+	{
+		.reg = 11,
+		.multiplier = 1,
+		.label = "+3.3V",
+	},
+	// CPU SOC
+	{
+		.reg = 2,
+		.multiplier = 1,
+		.label = "CPU Soc",
+	},
+	// CPU Vcore
+	{
+		.reg = 4,
+		.multiplier = 1,
+		.label = "CPU Vcore",
+	},
+	// CPU 1P8
+	{
+		.reg = 9,
+		.multiplier = 1,
+		.label = "CPU 1P8",
+	},
+	// CPU VDDP
+	{
+		.reg = 10,
+		.multiplier = 1,
+		.label = "CPU VDDP",
+	},
+	// DRAM
+	{
+		.reg = 3,
+		.multiplier = 2,
+		.label = "DRAM",
+	},
+	// Chipset
+	{
+		.reg = 5,
+		.multiplier = 1,
+		.label = "Chipset",
+	},
+
+	// CPU SA
+	{
+		.reg = 6,
+		.multiplier = 1,
+		.label = "CPU SA",
+	},
+	// Voltage #2
+	{
+		.reg = 7,
+		.multiplier = 1,
+		.label = "Voltage #2",
+	},
+	// AVCC3
+	{
+		.reg = 8,
+		.multiplier = 1,
+		.label = "AVCC3",
+	},
+	// AVSB
+	{
+		.reg = 12,
+		.multiplier = 1,
+		.label = "AVSB",
+	},
+	// VBAT
+	{
+		.reg = 13,
+		.multiplier = 1,
+		.label = "VBat",
+	},
+
+};
+
+static const char *const nct6687_temp_label[] = {
+	"CPU",
+	"System",
+	"VRM MOS",
+	"PCH",
+	"CPU Socket",
+	"PCIe x1",
+	"M2_1",
+	NULL,
+};
+
+static const char *const nct6687_fan_label[] = {
+	"CPU Fan",
+	"Pump Fan",
+	"System Fan #1",
+	"System Fan #2",
+	"System Fan #3",
+	"System Fan #4",
+	"System Fan #5",
+	"System Fan #6",
+	NULL,
+};
+
+/* ------------------------------------------------------- */
+struct nct6687_data
+{
+	int addr;	/* IO base of EC space */
+	int sioreg; /* SIO register */
+	enum kinds kind;
+
+	struct device *hwmon_dev;
+	const struct attribute_group *groups[6];
+
+	struct mutex update_lock;	/* used to protect sensor updates */
+	bool valid;					/* true if following fields are valid */
+	unsigned long last_updated; /* In jiffies */
+
+	/* Voltage values */
+	s16 voltage[3][NCT6687_NUM_REG_VOLTAGE]; // 0 = current 1 = min 2 = max
+
+	/* Temperature values */
+	s32 temperature[3][NCT6687_NUM_REG_TEMP]; // 0 = current 1 = min 2 = max
+
+	/* Fan attribute values */
+	u16 rpm[3][NCT6687_NUM_REG_FAN]; // 0 = current 1 = min 2 = max
+	u8 _initialFanControlMode[NCT6687_NUM_REG_FAN];
+	u8 _initialFanPwmCommand[NCT6687_NUM_REG_FAN];
+	bool _restoreDefaultFanControlRequired[NCT6687_NUM_REG_FAN];
+
+	u8 pwm[NCT6687_NUM_REG_PWM];
+
+	/* Remember extra register values over suspend/resume */
+	u8 hwm_cfg;
+};
+
+struct nct6687_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 void nct6687_save_fan_control(struct nct6687_data *data, int index);
+
+static const char* nct6687_voltage_label(char* buf, int index)
+{
+	if (manual)
+		sprintf(buf, "in%d", index);
+	else
+		strcpy(buf, nct6687_voltage_definition[index].label);
+
+	return buf;
+}
+
+static struct attribute_group *nct6687_create_attr_group(struct device *dev, const 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_kcalloc(dev, repeat * count + 1, sizeof(*attrs), GFP_KERNEL);
+	if (attrs == NULL)
+		return ERR_PTR(-ENOMEM);
+
+	su = devm_kzalloc(dev, array3_size(repeat, count, sizeof(*su)), 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;
+				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;
+				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;
+				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 = (*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;
+}
+
+static u16 nct6687_read(struct nct6687_data *data, u16 address)
+{
+	u8 page = (u8)(address >> 8);
+	u8 index = (u8)(address & 0xFF);
+	int res;
+
+	outb_p(EC_SPACE_PAGE_SELECT, data->addr + EC_SPACE_PAGE_REGISTER_OFFSET);
+	outb_p(page, data->addr + EC_SPACE_PAGE_REGISTER_OFFSET);
+	outb_p(index, data->addr + EC_SPACE_INDEX_REGISTER_OFFSET);
+
+	res = inb_p(data->addr + EC_SPACE_DATA_REGISTER_OFFSET);
+
+	return res;
+}
+
+static u16 nct6687_read16(struct nct6687_data *data, u16 reg)
+{
+	return (nct6687_read(data, reg) << 8) | nct6687_read(data, reg + 1);
+}
+
+static void nct6687_write(struct nct6687_data *data, u16 address, u16 value)
+{
+	u8 page = (u8)(address >> 8);
+	u8 index = (u8)(address & 0xFF);
+
+	outb_p(EC_SPACE_PAGE_SELECT, data->addr + EC_SPACE_PAGE_REGISTER_OFFSET);
+	outb_p(page, data->addr + EC_SPACE_PAGE_REGISTER_OFFSET);
+	outb_p(index, data->addr + EC_SPACE_INDEX_REGISTER_OFFSET);
+	outb_p(value, data->addr + EC_SPACE_DATA_REGISTER_OFFSET);
+}
+
+static void nct6687_update_temperatures(struct nct6687_data *data)
+{
+	int i;
+
+	for (i = 0; i < NCT6687_NUM_REG_TEMP; i++)
+	{
+		s32 value = (char)nct6687_read(data, NCT6687_REG_TEMP(i));
+		s32 half = (nct6687_read(data, NCT6687_REG_TEMP(i) + 1) >> 7) & 0x1;
+		s32 temperature = (value * 1000) + (500 * half);
+
+		data->temperature[0][i] = temperature;
+		data->temperature[1][i] = MIN(temperature, data->temperature[1][i]);
+		data->temperature[2][i] = MAX(temperature, data->temperature[2][i]);
+
+		pr_debug("nct6687_update_temperatures[%d]], addr=%04X, value=%d, half=%d, temperature=%d\n", i, NCT6687_REG_TEMP(i), value, half, temperature);
+	}
+}
+
+static void nct6687_update_voltage(struct nct6687_data *data)
+{
+	int index;
+	char buf[128];
+
+	/* Measured voltages and limits */
+	for (index = 0; index < NCT6687_NUM_REG_VOLTAGE; index++)
+	{
+		s16 reg = manual ? index : nct6687_voltage_definition[index].reg;
+		s16 high = nct6687_read(data, NCT6687_REG_VOLTAGE(reg)) * 16;
+		s16 low = ((u16)nct6687_read(data, NCT6687_REG_VOLTAGE(reg) + 1)) >> 4;
+		s16 value = low + high;
+		s16 voltage = manual ? value : value * nct6687_voltage_definition[index].multiplier;
+
+		data->voltage[0][index] = voltage;
+		data->voltage[1][index] = MIN(voltage, data->voltage[1][index]);
+		data->voltage[2][index] = MAX(voltage, data->voltage[2][index]);
+
+		pr_debug("nct6687_update_voltage[%d], %s, reg=%d, addr=0x%04x, value=%d, voltage=%d\n", index, nct6687_voltage_label(buf, index), reg, NCT6687_REG_VOLTAGE(index), value, voltage);
+	}
+
+	pr_debug("nct6687_update_voltage\n");
+}
+
+static void nct6687_update_fans(struct nct6687_data *data)
+{
+	int i;
+
+	for (i = 0; i < NCT6687_NUM_REG_FAN; i++)
+	{
+		s16 rmp = nct6687_read16(data, NCT6687_REG_FAN_RPM(i));
+
+		data->rpm[0][i] = rmp;
+		data->rpm[1][i] = MIN(rmp, data->rpm[1][i]);
+		data->rpm[2][i] = MAX(rmp, data->rpm[2][i]);
+
+		pr_debug("nct6687_update_fans[%d], rpm=%d min=%d, max=%d", i, rmp, data->rpm[1][i], data->rpm[2][i]);
+	}
+
+	for (i = 0; i < NCT6687_NUM_REG_PWM; i++)
+	{
+		data->pwm[i] = nct6687_read(data, NCT6687_REG_PWM(i));
+
+		pr_debug("nct6687_update_fans[%d], pwm=%d", i, data->pwm[i]);
+	}
+}
+
+static struct nct6687_data *nct6687_update_device(struct device *dev)
+{
+	struct nct6687_data *data = dev_get_drvdata(dev);
+
+	mutex_lock(&data->update_lock);
+
+	if (time_after(jiffies, data->last_updated + HZ) || !data->valid)
+	{
+		/* Measured voltages and limits */
+		nct6687_update_voltage(data);
+
+		/* Measured temperatures and limits */
+		nct6687_update_temperatures(data);
+
+		/* Measured fan speeds and limits */
+		nct6687_update_fans(data);
+
+		data->last_updated = jiffies;
+		data->valid = true;
+	}
+
+	mutex_unlock(&data->update_lock);
+
+	return data;
+}
+
+/*
+ * Sysfs callback functions
+ */
+static ssize_t show_voltage_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+
+	if (manual)
+		return sprintf(buf, "in%d\n", sattr->index);
+	else
+		return sprintf(buf, "%s\n", nct6687_voltage_definition[sattr->index].label);
+}
+
+static ssize_t show_voltage_value(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct nct6687_data *data = nct6687_update_device(dev);
+
+	return sprintf(buf, "%d\n", data->voltage[sattr->index][sattr->nr]);
+}
+
+static umode_t nct6687_voltage_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+	pr_debug("nct6687_voltage_is_visible[%d], attr=0x%04X\n", index, attr->mode);
+	return attr->mode;
+}
+
+SENSOR_TEMPLATE(voltage_label, "in%d_label", S_IRUGO, show_voltage_label, NULL, 0);
+SENSOR_TEMPLATE_2(voltage_input, "in%d_input", S_IRUGO, show_voltage_value, NULL, 0, 0);
+SENSOR_TEMPLATE_2(voltage_min, "in%d_min", S_IRUGO, show_voltage_value, NULL, 0, 1);
+SENSOR_TEMPLATE_2(voltage_max, "in%d_max", S_IRUGO, show_voltage_value, NULL, 0, 2);
+
+static struct sensor_device_template *nct6687_attributes_voltage_template[] = {
+	&sensor_dev_template_voltage_label,
+	&sensor_dev_template_voltage_input,
+	&sensor_dev_template_voltage_min,
+	&sensor_dev_template_voltage_max,
+	NULL,
+};
+
+static const struct sensor_template_group nct6687_voltage_template_group = {
+	.templates = nct6687_attributes_voltage_template,
+	.is_visible = nct6687_voltage_is_visible,
+};
+
+static ssize_t show_fan_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+
+	return sprintf(buf, "%s\n", nct6687_fan_label[sattr->index]);
+}
+
+static ssize_t show_fan_value(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct nct6687_data *data = nct6687_update_device(dev);
+
+	return sprintf(buf, "%d\n", data->rpm[sattr->index][sattr->nr]);
+}
+
+static umode_t nct6687_fan_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+	return attr->mode;
+}
+
+SENSOR_TEMPLATE(fan_label, "fan%d_label", S_IRUGO, show_fan_label, NULL, 0);
+SENSOR_TEMPLATE_2(fan_input, "fan%d_input", S_IRUGO, show_fan_value, NULL, 0, 0);
+SENSOR_TEMPLATE_2(fan_min, "fan%d_min", S_IRUGO, show_fan_value, NULL, 0, 1);
+SENSOR_TEMPLATE_2(fan_max, "fan%d_max", S_IRUGO, show_fan_value, NULL, 0, 2);
+
+/*
+ * nct6687_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 *nct6687_attributes_fan_template[] = {
+	&sensor_dev_template_fan_label,
+	&sensor_dev_template_fan_input,
+	&sensor_dev_template_fan_min,
+	&sensor_dev_template_fan_max,
+	NULL,
+};
+
+static const struct sensor_template_group nct6687_fan_template_group = {
+	.templates = nct6687_attributes_fan_template,
+	.is_visible = nct6687_fan_is_visible,
+	.base = 1,
+};
+
+static ssize_t show_temperature_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+
+	return sprintf(buf, "%s\n", nct6687_temp_label[sattr->index]);
+}
+
+static ssize_t show_temperature_value(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct nct6687_data *data = nct6687_update_device(dev);
+
+	return sprintf(buf, "%d\n", data->temperature[sattr->index][sattr->nr]);
+}
+
+static umode_t nct6687_temp_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+	return attr->mode;
+}
+
+SENSOR_TEMPLATE(temp_label, "temp%d_label", S_IRUGO, show_temperature_label, NULL, 0);
+SENSOR_TEMPLATE_2(temp_input, "temp%d_input", S_IRUGO, show_temperature_value, NULL, 0, 0);
+SENSOR_TEMPLATE_2(temp_min, "temp%d_min", S_IRUGO, show_temperature_value, NULL, 0, 1);
+SENSOR_TEMPLATE_2(temp_max, "temp%d_max", S_IRUGO, show_temperature_value, NULL, 0, 2);
+
+/*
+ * nct6687_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 *nct6687_attributes_temp_template[] = {
+	&sensor_dev_template_temp_input,
+	&sensor_dev_template_temp_label,
+	&sensor_dev_template_temp_min,
+	&sensor_dev_template_temp_max,
+	NULL,
+};
+
+static const struct sensor_template_group nct6687_temp_template_group = {
+	.templates = nct6687_attributes_temp_template,
+	.is_visible = nct6687_temp_is_visible,
+	.base = 1,
+};
+
+static ssize_t show_pwm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct nct6687_data *data = nct6687_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]);
+}
+
+static ssize_t store_pwm(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct nct6687_data *data = dev_get_drvdata(dev);
+	int index = sattr->index;
+	unsigned long val;
+	u16 mode;
+	u8 bitMask;
+
+	if (kstrtoul(buf, 10, &val) || val > 255 || index >= NCT6687_NUM_REG_FAN)
+		return -EINVAL;
+
+	mutex_lock(&data->update_lock);
+
+	nct6687_save_fan_control(data, index);
+
+	mode = nct6687_read(data, NCT6687_REG_FAN_CTRL_MODE(index));
+	bitMask = (u8)(0x01 << index);
+
+	mode = (u8)(mode | bitMask);
+	nct6687_write(data, NCT6687_REG_FAN_CTRL_MODE(index), mode);
+
+	nct6687_write(data, NCT6687_REG_FAN_PWM_COMMAND(index), NCT6687_FAN_CFG_REQ);
+	msleep(50);
+	nct6687_write(data, NCT6687_REG_PWM_WRITE(index), val);
+	nct6687_write(data, NCT6687_REG_FAN_PWM_COMMAND(index), NCT6687_FAN_CFG_DONE);
+	msleep(50);
+
+	mutex_unlock(&data->update_lock);
+
+	return count;
+}
+
+SENSOR_TEMPLATE(pwm, "pwm%d", S_IRUGO, show_pwm, store_pwm, 0);
+
+static void nct6687_save_fan_control(struct nct6687_data *data, int index)
+{
+	if (data->_restoreDefaultFanControlRequired[index] == false)
+	{
+		u16 reg = nct6687_read(data, NCT6687_REG_FAN_CTRL_MODE(index));
+		u16 bitMask = 0x01 << index;
+		u8 pwm = nct6687_read(data, NCT6687_REG_FAN_PWM_COMMAND(index));
+
+		data->_initialFanControlMode[index] = (u8)(reg & bitMask);
+		data->_initialFanPwmCommand[index] = pwm;
+
+		data->_restoreDefaultFanControlRequired[index] = true;
+	}
+}
+
+static void nct6687_restore_fan_control(struct nct6687_data *data, int index)
+{
+	if (data->_restoreDefaultFanControlRequired[index])
+	{
+		u8 mode = nct6687_read(data, NCT6687_REG_FAN_CTRL_MODE(index));
+		mode = (u8)(mode & ~data->_initialFanControlMode[index]);
+
+		nct6687_write(data, NCT6687_REG_FAN_CTRL_MODE(index), mode);
+
+		nct6687_write(data, NCT6687_REG_FAN_PWM_COMMAND(index), NCT6687_FAN_CFG_REQ);
+		msleep(50);
+		nct6687_write(data, NCT6687_REG_PWM_WRITE(index), data->_initialFanPwmCommand[index]);
+		nct6687_write(data, NCT6687_REG_FAN_PWM_COMMAND(index), NCT6687_FAN_CFG_DONE);
+		msleep(50);
+
+		data->_restoreDefaultFanControlRequired[index] = false;
+
+		pr_debug("nct6687_restore_fan_control[%d], addr=%04X, ctrl=%04X, _initialFanPwmCommand=%d\n", index, NCT6687_REG_FAN_PWM_COMMAND(index), NCT6687_REG_PWM_WRITE(index), data->_initialFanPwmCommand[index]);
+	}
+}
+
+static umode_t nct6687_pwm_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+	return attr->mode | S_IWUSR;
+}
+
+static struct sensor_device_template *nct6687_attributes_pwm_template[] = {
+	&sensor_dev_template_pwm,
+	NULL,
+};
+
+static const struct sensor_template_group nct6687_pwm_template_group = {
+	.templates = nct6687_attributes_pwm_template,
+	.is_visible = nct6687_pwm_is_visible,
+	.base = 1,
+};
+
+/* Get the monitoring functions started */
+static inline void nct6687_init_device(struct nct6687_data *data)
+{
+	u8 tmp;
+
+	pr_debug("nct6687_init_device\n");
+
+	/* Start hardware monitoring if needed */
+	tmp = nct6687_read(data, NCT6687_HWM_CFG);
+	if (!(tmp & 0x80))
+	{
+		pr_debug("nct6687_init_device: 0x%04x\n", tmp);
+		nct6687_write(data, NCT6687_HWM_CFG, tmp | 0x80);
+	}
+
+	// enable SIO voltage
+	nct6687_write(data, 0x1BB, 0x61);
+	nct6687_write(data, 0x1BC, 0x62);
+	nct6687_write(data, 0x1BD, 0x63);
+	nct6687_write(data, 0x1BE, 0x64);
+	nct6687_write(data, 0x1BF, 0x65);
+}
+
+/*
+ * There are a total of 8 fan inputs.
+ */
+static void nct6687_setup_fans(struct nct6687_data *data)
+{
+	int i;
+
+	for (i = 0; i < NCT6687_NUM_REG_FAN; i++)
+	{
+		u16 reg = nct6687_read(data, NCT6687_REG_FAN_CTRL_MODE(i));
+		u16 bitMask = 0x01 << i;
+		u16 rpm = nct6687_read16(data, NCT6687_REG_FAN_RPM(i));
+
+		data->rpm[0][i] = rpm;
+		data->rpm[1][i] = rpm;
+		data->rpm[2][i] = rpm;
+		data->_initialFanControlMode[i] = (u8)(reg & bitMask);
+		data->_restoreDefaultFanControlRequired[i] = false;
+
+		pr_debug("nct6687_setup_fans[%d], %s - addr=%04X, ctrl=%04X, rpm=%d, _initialFanControlMode=%d\n", i, nct6687_fan_label[i], NCT6687_REG_FAN_CTRL_MODE(i), reg, rpm, data->_initialFanControlMode[i]);
+	}
+}
+
+static void nct6687_setup_voltages(struct nct6687_data *data)
+{
+	int index;
+	char buf[64];
+
+	/* Measured voltages and limits */
+	for (index = 0; index < NCT6687_NUM_REG_VOLTAGE; index++)
+	{
+		s16 reg = manual ? index : nct6687_voltage_definition[index].reg;
+		s16 high = nct6687_read(data, NCT6687_REG_VOLTAGE(reg)) * 16;
+		s16 low = ((u16)nct6687_read(data, NCT6687_REG_VOLTAGE(reg) + 1)) >> 4;
+		s16 value = low + high;
+		s16 voltage = manual ? value : value * nct6687_voltage_definition[index].multiplier;
+
+		data->voltage[0][index] = voltage;
+		data->voltage[1][index] = voltage;
+		data->voltage[2][index] = voltage;
+
+		pr_debug("nct6687_setup_voltages[%d], %s, addr=0x%04x, value=%d, voltage=%d\n", index, nct6687_voltage_label(buf, index), NCT6687_REG_VOLTAGE(index), value, voltage);
+	}
+}
+
+static void nct6687_setup_temperatures(struct nct6687_data *data)
+{
+	int i;
+
+	for (i = 0; i < NCT6687_NUM_REG_TEMP; i++)
+	{
+		s32 value = (char)nct6687_read(data, NCT6687_REG_TEMP(i));
+		s32 half = (nct6687_read(data, NCT6687_REG_TEMP(i) + 1) >> 7) & 0x1;
+		s32 temperature = (value * 1000) + (5 * half);
+
+		data->temperature[0][i] = temperature;
+		data->temperature[1][i] = temperature;
+		data->temperature[2][i] = temperature;
+
+		pr_debug("nct6687_setup_temperatures[%d]], addr=%04X, value=%d, half=%d, temperature=%d\n", i, NCT6687_REG_TEMP(i), value, half, temperature);
+	}
+}
+
+static void nct6687_setup_pwm(struct nct6687_data *data)
+{
+	int i;
+
+	for (i = 0; i < NCT6687_NUM_REG_PWM; i++)
+	{
+		data->_initialFanPwmCommand[i] = nct6687_read(data, NCT6687_REG_FAN_PWM_COMMAND(i));
+		data->pwm[i] = nct6687_read(data, NCT6687_REG_PWM(i));
+
+		pr_debug("nct6687_setup_pwm[%d], addr=%04X, pwm=%d, _initialFanPwmCommand=%d\n", i, NCT6687_REG_FAN_PWM_COMMAND(i), data->pwm[i], data->_initialFanPwmCommand[i]);
+	}
+}
+
+static int nct6687_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct nct6687_data *data = dev_get_drvdata(dev);
+	int i;
+
+	mutex_lock(&data->update_lock);
+
+	for (i = 0; i < NCT6687_NUM_REG_FAN; i++)
+	{
+		nct6687_restore_fan_control(data, i);
+	}
+
+	mutex_unlock(&data->update_lock);
+
+	return 0;
+}
+
+static int nct6687_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct nct6687_sio_data *sio_data = dev->platform_data;
+	struct attribute_group *group;
+	struct nct6687_data *data;
+	struct device *hwmon_dev;
+	struct resource *res;
+	int groups = 0;
+	char build[16];
+
+	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 nct6687_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->kind = sio_data->kind;
+	data->sioreg = sio_data->sioreg;
+	data->addr = res->start;
+
+	pr_debug("nct6687_probe addr=0x%04X, sioreg=0x%04X\n", data->addr, data->sioreg);
+
+	mutex_init(&data->update_lock);
+
+	platform_set_drvdata(pdev, data);
+
+	nct6687_init_device(data);
+	nct6687_setup_fans(data);
+	nct6687_setup_pwm(data);
+	nct6687_setup_temperatures(data);
+	nct6687_setup_voltages(data);
+
+	/* Register sysfs hooks */
+
+	group = nct6687_create_attr_group(dev, &nct6687_pwm_template_group, NCT6687_NUM_REG_FAN);
+
+	if (IS_ERR(group))
+		return PTR_ERR(group);
+
+	data->groups[groups++] = group;
+
+	group = nct6687_create_attr_group(dev, &nct6687_voltage_template_group, NCT6687_NUM_REG_VOLTAGE);
+
+	if (IS_ERR(group))
+		return PTR_ERR(group);
+
+	data->groups[groups++] = group;
+
+	group = nct6687_create_attr_group(dev, &nct6687_fan_template_group, NCT6687_NUM_REG_FAN);
+
+	if (IS_ERR(group))
+		return PTR_ERR(group);
+
+	data->groups[groups++] = group;
+
+	group = nct6687_create_attr_group(dev, &nct6687_temp_template_group, NCT6687_NUM_REG_TEMP);
+
+	if (IS_ERR(group))
+		return PTR_ERR(group);
+
+	data->groups[groups++] = group;
+
+	scnprintf(build, sizeof(build), "%02d/%02d/%02d", nct6687_read(data, NCT6687_REG_BUILD_MONTH), nct6687_read(data, NCT6687_REG_BUILD_DAY), nct6687_read(data, NCT6687_REG_BUILD_YEAR));
+
+	dev_info(dev, "%s EC firmware version %d.%d build %s\n", nct6687_chip_names[data->kind], nct6687_read(data, NCT6687_REG_VERSION_HI), nct6687_read(data, NCT6687_REG_VERSION_LO), build);
+
+	hwmon_dev = devm_hwmon_device_register_with_groups(dev, nct6687_device_names[data->kind], data, data->groups);
+
+	return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static int nct6687_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct nct6687_data *data = nct6687_update_device(&pdev->dev);
+
+	mutex_lock(&data->update_lock);
+	data->hwm_cfg = nct6687_read(data, NCT6687_HWM_CFG);
+	mutex_unlock(&data->update_lock);
+
+	return 0;
+}
+
+static int nct6687_resume(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct nct6687_data *data = dev_get_drvdata(dev);
+
+	mutex_lock(&data->update_lock);
+
+	nct6687_write(data, NCT6687_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 nct6687_dev_pm_ops = {
+// 	.suspend = nct6687_suspend,
+// 	.resume = nct6687_resume,
+// 	.freeze = nct6687_suspend,
+// 	.restore = nct6687_resume,
+// };
+
+#define NCT6687_DEV_PM_OPS NULL
+
+static struct platform_driver nct6687_driver = {
+	.driver = {
+		.name = DRVNAME,
+		.pm = NCT6687_DEV_PM_OPS,
+	},
+	.probe = nct6687_probe,
+	.remove = nct6687_remove,
+	.suspend = nct6687_suspend,
+	.resume = nct6687_resume,
+};
+
+static int __init nct6687_find(int sioaddr, struct nct6687_sio_data *sio_data)
+{
+	u16 address;
+	u16 verify;
+	u16 val;
+	int err;
+
+	err = superio_enter(sioaddr);
+	if (err)
+		return err;
+
+	val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8) | superio_inb(sioaddr, SIO_REG_DEVREVISION);
+
+	pr_debug("found chip ID: 0x%04x\n", val);
+
+	if (val == SIO_NCT6683D_ID) {
+		sio_data->kind = nct6683;
+	} else if (val == SIO_NCT6686_ID || val == SIO_NCT6686D_ID) {
+		sio_data->kind = nct6686;
+	} else if (val == SIO_NCT6687_ID || val == SIO_NCT6687D_ID || force)
+	{
+		sio_data->kind = nct6687;
+	}
+	else
+	{
+		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, NCT6687_LD_HWM);
+	address = (superio_inb(sioaddr, SIO_REG_ADDR) << 8) | superio_inb(sioaddr, SIO_REG_ADDR + 1);
+	ssleep(1);
+	verify = (superio_inb(sioaddr, SIO_REG_ADDR) << 8) | superio_inb(sioaddr, SIO_REG_ADDR + 1);
+
+	if (address == 0 || address != verify)
+	{
+		pr_err("EC base I/O port unconfigured\n");
+		goto fail;
+	}
+
+	if ((address & 0x07) == 0x05)
+		address &= 0xFFF8;
+
+	if (address < 0x100 || (address & 0xF007) != 0)
+	{
+		pr_err("EC Invalid address: 0x%04X\n", address);
+		goto fail;
+	}
+
+	/* Activate logical device if needed */
+	val = superio_inb(sioaddr, SIO_REG_ENABLE);
+	if (!(val & 0x01))
+	{
+		pr_warn("Forcibly enabling EC access. Data may be unusable.\n");
+		superio_outb(sioaddr, SIO_REG_ENABLE, val | 0x01);
+	}
+
+	superio_exit(sioaddr);
+	pr_info("Found %s or compatible chip at 0x%04x:0x%04x\n", nct6687_chip_names[sio_data->kind], sioaddr, address);
+	sio_data->sioreg = sioaddr;
+
+	return address;
+
+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 nct6687 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_nct6687_init(void)
+{
+	struct nct6687_sio_data sio_data;
+	int sioaddr[2] = {0x2e, 0x4e};
+	struct resource res;
+	bool found = false;
+	int address;
+	int i, err;
+
+	err = platform_driver_register(&nct6687_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
+	 * nct6687 hardware monitor, and call probe()
+	 */
+	for (i = 0; i < ARRAY_SIZE(pdev); i++)
+	{
+		address = nct6687_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_unregister;
+		}
+
+		err = platform_device_add_data(pdev[i], &sio_data, sizeof(struct nct6687_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:
+	platform_device_put(pdev[i]);
+
+exit_device_unregister:
+	while (--i >= 0)
+	{
+		if (pdev[i])
+			platform_device_unregister(pdev[i]);
+	}
+
+exit_unregister:
+	platform_driver_unregister(&nct6687_driver);
+
+	return err;
+}
+
+static void __exit sensors_nct6687_exit(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(pdev); i++)
+	{
+		if (pdev[i])
+			platform_device_unregister(pdev[i]);
+	}
+
+	platform_driver_unregister(&nct6687_driver);
+}
+
+MODULE_AUTHOR("Frederic Boltz <frederic.boltz@gmail.com>");
+MODULE_DESCRIPTION("Driver for NCT6687D");
+MODULE_LICENSE("GPL");
+
+module_init(sensors_nct6687_init);
+module_exit(sensors_nct6687_exit);

[-- Attachment #3: 0001-Adding-driver-for-NCT6687D-Kernel-module.patch --]
[-- Type: text/x-patch, Size: 37095 bytes --]

From 8c49ae386d76ebdf6ca99102eaa50fc9eaba84a1 Mon Sep 17 00:00:00 2001
From: Thomas Raines <seatommyboy@gmail.com>
Date: Sun, 16 Jul 2023 07:53:30 -0500
Subject: [PATCH] Adding driver for NCT6687D Kernel module. Original author is
 Frederic BOLTZ https://github.com/Fred78290/nct6687d.

---
 drivers/hwmon/Kconfig   |   10 +
 drivers/hwmon/Makefile  |    1 +
 drivers/hwmon/nct6687.c | 1272 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 1283 insertions(+)
 create mode 100644 drivers/hwmon/nct6687.c

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index fc640201a2de..2e5a0b30b5ec 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1518,6 +1518,16 @@ config SENSORS_NCT6683
 	  This driver can also be built as a module. If so, the module
 	  will be called nct6683.
 
+config SENSORS_NCT6687
+	tristate "Nuvoton NCT6687D"
+	depends on !PPC
+	help
+	  If you say yes here you get support for the hardware monitoring
+	  functionality of the Nuvoton NCT6687D eSIO chip.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called nct6687.
+
 config SENSORS_NCT6775_CORE
 	tristate
 	select REGMAP
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index cd8c568c80a9..dfc3bbe962a2 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -158,6 +158,7 @@ obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
 obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
 obj-$(CONFIG_SENSORS_MR75203)	+= mr75203.o
 obj-$(CONFIG_SENSORS_NCT6683)	+= nct6683.o
+obj-$(CONFIG_SENSORS_NCT6687)	+= nct6687.o
 obj-$(CONFIG_SENSORS_NCT6775_CORE) += nct6775-core.o
 nct6775-objs			:= nct6775-platform.o
 obj-$(CONFIG_SENSORS_NCT6775)	+= nct6775.o
diff --git a/drivers/hwmon/nct6687.c b/drivers/hwmon/nct6687.c
new file mode 100644
index 000000000000..fed2c868fec7
--- /dev/null
+++ b/drivers/hwmon/nct6687.c
@@ -0,0 +1,1272 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * nct6687 - Driver for the hardware monitoring functionality of
+ *	       Nuvoton NCT6687 Super-I/O chips
+ *
+ * Copyright (C) 2020  Frederic Boltz <frederic.boltz@gmail.com>
+ *
+ * Derived from nct6683 driver
+ * Copyright (C) 2013  Guenter Roeck <linux@roeck-us.net>
+ * 
+ * Inspired of LibreHardwareMonitor
+ * https://github.com/LibreHardwareMonitor/LibreHardwareMonitor
+ * 
+ * Supports the following chips:
+ *
+ * Chip       #voltage   #fan    #pwm    #temp  chip ID
+ * nct6683    14(1)      8       8       7      0xc732  (partial support)
+ * nct6686d   21(1)      16      8       32(1)  0xd440
+ * nct6687    14(1)      8       8       7      0xd592
+ *
+ * Notes:
+ *	(1) Total number of voltage and 9 displayed.
+ */
+// #define DEBUG 1
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/delay.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>
+
+#define MIN(a,b) (((a)<(b))?(a):(b))
+#define MAX(a,b) (((a)>(b))?(a):(b))
+
+enum kinds
+{
+	nct6683,
+	nct6686,
+	nct6687
+};
+
+static bool force;
+static bool manual;
+
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Set to one to enable support for unknown vendors");
+
+module_param(manual, bool, 0);
+MODULE_PARM_DESC(manual, "Set voltage input and voltage label configured with external sensors file");
+
+static const char *const nct6687_device_names[] = {
+	"nct6683",
+	"nct6686",
+	"nct6687",
+};
+
+static const char *const nct6687_chip_names[] = {
+	"NCT6683D",
+	"NCT6686D",
+	"NCT6687D",
+};
+
+#define DRVNAME "nct6687"
+
+/*
+ * Super-I/O constants and functions
+ */
+
+#define NCT6687_LD_ACPI 0x0a
+#define NCT6687_LD_HWM 0x0b
+#define NCT6687_LD_VID 0x0d
+
+#define SIO_REG_LDSEL 0x07		 /* Logical device select */
+#define SIO_REG_DEVID 0x20		 /* Device ID (2 bytes) */
+#define SIO_REG_DEVREVISION 0x21 /* Device ID (2 bytes) */
+#define SIO_REG_ENABLE 0x30		 /* Logical device enable */
+#define SIO_REG_ADDR 0x60		 /* Logical device address (2 bytes) */
+
+#define SIO_NCT6683D_ID 0xc732
+#define SIO_NCT6686_ID 0xd440
+#define SIO_NCT6686D_ID 0xd441
+#define SIO_NCT6687_ID 0xd451 // 0xd592
+#define SIO_NCT6687D_ID 0xd592
+
+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_OFFSET 0 /* Use EC port 1 */
+#define IOREGION_LENGTH 4
+
+/* Common and NCT6687 specific data */
+
+#define NCT6687_NUM_REG_VOLTAGE (sizeof(nct6687_voltage_definition) / sizeof(struct voltage_reg))
+#define NCT6687_NUM_REG_TEMP 7
+#define NCT6687_NUM_REG_FAN 8
+#define NCT6687_NUM_REG_PWM 8
+
+#define NCT6687_REG_TEMP(x) (0x100 + (x)*2)
+#define NCT6687_REG_VOLTAGE(x) (0x120 + (x)*2)
+#define NCT6687_REG_FAN_RPM(x) (0x140 + (x)*2)
+#define NCT6687_REG_PWM(x) (0x160 + (x))
+#define NCT6687_REG_PWM_WRITE(x) (0xa28 + (x))
+
+#define NCT6687_HWM_CFG 0x180
+
+#define NCT6687_REG_MON_CFG(x) (0x1a0 + (x))
+#define NCT6687_REG_FANIN_CFG(x) (0xA00 + (x))
+#define NCT6687_REG_FANOUT_CFG(x) (0x1d0 + (x))
+
+#define NCT6687_REG_TEMP_HYST(x) (0x330 + (x))	/* 8 bit */
+#define NCT6687_REG_TEMP_MAX(x) (0x350 + (x))	/* 8 bit */
+#define NCT6687_REG_MON_HIGH(x) (0x370 + (x)*2) /* 8 bit */
+#define NCT6687_REG_MON_LOW(x) (0x371 + (x)*2)	/* 8 bit */
+
+#define NCT6687_REG_FAN_MIN(x) (0x3b8 + (x)*2) /* 16 bit */
+
+#define NCT6687_REG_FAN_CTRL_MODE(x) 0xA00
+#define NCT6687_REG_FAN_PWM_COMMAND(x) 0xA01
+#define NCT6687_FAN_CFG_REQ 0x80
+#define NCT6687_FAN_CFG_DONE 0x40
+
+#define NCT6687_REG_BUILD_YEAR 0x604
+#define NCT6687_REG_BUILD_MONTH 0x605
+#define NCT6687_REG_BUILD_DAY 0x606
+#define NCT6687_REG_SERIAL 0x607
+#define NCT6687_REG_VERSION_HI 0x608
+#define NCT6687_REG_VERSION_LO 0x609
+
+#define NCT6687_REG_CR_CASEOPEN 0xe8
+#define NCT6687_CR_CASEOPEN_MASK (1 << 7)
+
+#define NCT6687_REG_CR_BEEP 0xe0
+#define NCT6687_CR_BEEP_MASK (1 << 6)
+
+#define EC_SPACE_PAGE_REGISTER_OFFSET 0x04
+#define EC_SPACE_INDEX_REGISTER_OFFSET 0x05
+#define EC_SPACE_DATA_REGISTER_OFFSET 0x06
+#define EC_SPACE_PAGE_SELECT 0xFF
+
+struct voltage_reg
+{
+	u16 reg;
+	u16 multiplier;
+	const char *label;
+};
+
+static struct voltage_reg nct6687_voltage_definition[] = {
+	// +12V
+	{
+		.reg = 0,
+		.multiplier = 12,
+		.label = "+12V",
+	},
+	// + 5V
+	{
+		.reg = 1,
+		.multiplier = 5,
+		.label = "+5V",
+	},
+	// +3.3V
+	{
+		.reg = 11,
+		.multiplier = 1,
+		.label = "+3.3V",
+	},
+	// CPU SOC
+	{
+		.reg = 2,
+		.multiplier = 1,
+		.label = "CPU Soc",
+	},
+	// CPU Vcore
+	{
+		.reg = 4,
+		.multiplier = 1,
+		.label = "CPU Vcore",
+	},
+	// CPU 1P8
+	{
+		.reg = 9,
+		.multiplier = 1,
+		.label = "CPU 1P8",
+	},
+	// CPU VDDP
+	{
+		.reg = 10,
+		.multiplier = 1,
+		.label = "CPU VDDP",
+	},
+	// DRAM
+	{
+		.reg = 3,
+		.multiplier = 2,
+		.label = "DRAM",
+	},
+	// Chipset
+	{
+		.reg = 5,
+		.multiplier = 1,
+		.label = "Chipset",
+	},
+
+	// CPU SA
+	{
+		.reg = 6,
+		.multiplier = 1,
+		.label = "CPU SA",
+	},
+	// Voltage #2
+	{
+		.reg = 7,
+		.multiplier = 1,
+		.label = "Voltage #2",
+	},
+	// AVCC3
+	{
+		.reg = 8,
+		.multiplier = 1,
+		.label = "AVCC3",
+	},
+	// AVSB
+	{
+		.reg = 12,
+		.multiplier = 1,
+		.label = "AVSB",
+	},
+	// VBAT
+	{
+		.reg = 13,
+		.multiplier = 1,
+		.label = "VBat",
+	},
+
+};
+
+static const char *const nct6687_temp_label[] = {
+	"CPU",
+	"System",
+	"VRM MOS",
+	"PCH",
+	"CPU Socket",
+	"PCIe x1",
+	"M2_1",
+	NULL,
+};
+
+static const char *const nct6687_fan_label[] = {
+	"CPU Fan",
+	"Pump Fan",
+	"System Fan #1",
+	"System Fan #2",
+	"System Fan #3",
+	"System Fan #4",
+	"System Fan #5",
+	"System Fan #6",
+	NULL,
+};
+
+/* ------------------------------------------------------- */
+struct nct6687_data
+{
+	int addr;	/* IO base of EC space */
+	int sioreg; /* SIO register */
+	enum kinds kind;
+
+	struct device *hwmon_dev;
+	const struct attribute_group *groups[6];
+
+	struct mutex update_lock;	/* used to protect sensor updates */
+	bool valid;					/* true if following fields are valid */
+	unsigned long last_updated; /* In jiffies */
+
+	/* Voltage values */
+	s16 voltage[3][NCT6687_NUM_REG_VOLTAGE]; // 0 = current 1 = min 2 = max
+
+	/* Temperature values */
+	s32 temperature[3][NCT6687_NUM_REG_TEMP]; // 0 = current 1 = min 2 = max
+
+	/* Fan attribute values */
+	u16 rpm[3][NCT6687_NUM_REG_FAN]; // 0 = current 1 = min 2 = max
+	u8 _initialFanControlMode[NCT6687_NUM_REG_FAN];
+	u8 _initialFanPwmCommand[NCT6687_NUM_REG_FAN];
+	bool _restoreDefaultFanControlRequired[NCT6687_NUM_REG_FAN];
+
+	u8 pwm[NCT6687_NUM_REG_PWM];
+
+	/* Remember extra register values over suspend/resume */
+	u8 hwm_cfg;
+};
+
+struct nct6687_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 void nct6687_save_fan_control(struct nct6687_data *data, int index);
+
+static const char* nct6687_voltage_label(char* buf, int index)
+{
+	if (manual)
+		sprintf(buf, "in%d", index);
+	else
+		strcpy(buf, nct6687_voltage_definition[index].label);
+
+	return buf;
+}
+
+static struct attribute_group *nct6687_create_attr_group(struct device *dev, const 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_kcalloc(dev, repeat * count + 1, sizeof(*attrs), GFP_KERNEL);
+	if (attrs == NULL)
+		return ERR_PTR(-ENOMEM);
+
+	su = devm_kzalloc(dev, array3_size(repeat, count, sizeof(*su)), 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;
+				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;
+				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;
+				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 = (*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;
+}
+
+static u16 nct6687_read(struct nct6687_data *data, u16 address)
+{
+	u8 page = (u8)(address >> 8);
+	u8 index = (u8)(address & 0xFF);
+	int res;
+
+	outb_p(EC_SPACE_PAGE_SELECT, data->addr + EC_SPACE_PAGE_REGISTER_OFFSET);
+	outb_p(page, data->addr + EC_SPACE_PAGE_REGISTER_OFFSET);
+	outb_p(index, data->addr + EC_SPACE_INDEX_REGISTER_OFFSET);
+
+	res = inb_p(data->addr + EC_SPACE_DATA_REGISTER_OFFSET);
+
+	return res;
+}
+
+static u16 nct6687_read16(struct nct6687_data *data, u16 reg)
+{
+	return (nct6687_read(data, reg) << 8) | nct6687_read(data, reg + 1);
+}
+
+static void nct6687_write(struct nct6687_data *data, u16 address, u16 value)
+{
+	u8 page = (u8)(address >> 8);
+	u8 index = (u8)(address & 0xFF);
+
+	outb_p(EC_SPACE_PAGE_SELECT, data->addr + EC_SPACE_PAGE_REGISTER_OFFSET);
+	outb_p(page, data->addr + EC_SPACE_PAGE_REGISTER_OFFSET);
+	outb_p(index, data->addr + EC_SPACE_INDEX_REGISTER_OFFSET);
+	outb_p(value, data->addr + EC_SPACE_DATA_REGISTER_OFFSET);
+}
+
+static void nct6687_update_temperatures(struct nct6687_data *data)
+{
+	int i;
+
+	for (i = 0; i < NCT6687_NUM_REG_TEMP; i++)
+	{
+		s32 value = (char)nct6687_read(data, NCT6687_REG_TEMP(i));
+		s32 half = (nct6687_read(data, NCT6687_REG_TEMP(i) + 1) >> 7) & 0x1;
+		s32 temperature = (value * 1000) + (500 * half);
+
+		data->temperature[0][i] = temperature;
+		data->temperature[1][i] = MIN(temperature, data->temperature[1][i]);
+		data->temperature[2][i] = MAX(temperature, data->temperature[2][i]);
+
+		pr_debug("nct6687_update_temperatures[%d]], addr=%04X, value=%d, half=%d, temperature=%d\n", i, NCT6687_REG_TEMP(i), value, half, temperature);
+	}
+}
+
+static void nct6687_update_voltage(struct nct6687_data *data)
+{
+	int index;
+	char buf[128];
+
+	/* Measured voltages and limits */
+	for (index = 0; index < NCT6687_NUM_REG_VOLTAGE; index++)
+	{
+		s16 reg = manual ? index : nct6687_voltage_definition[index].reg;
+		s16 high = nct6687_read(data, NCT6687_REG_VOLTAGE(reg)) * 16;
+		s16 low = ((u16)nct6687_read(data, NCT6687_REG_VOLTAGE(reg) + 1)) >> 4;
+		s16 value = low + high;
+		s16 voltage = manual ? value : value * nct6687_voltage_definition[index].multiplier;
+
+		data->voltage[0][index] = voltage;
+		data->voltage[1][index] = MIN(voltage, data->voltage[1][index]);
+		data->voltage[2][index] = MAX(voltage, data->voltage[2][index]);
+
+		pr_debug("nct6687_update_voltage[%d], %s, reg=%d, addr=0x%04x, value=%d, voltage=%d\n", index, nct6687_voltage_label(buf, index), reg, NCT6687_REG_VOLTAGE(index), value, voltage);
+	}
+
+	pr_debug("nct6687_update_voltage\n");
+}
+
+static void nct6687_update_fans(struct nct6687_data *data)
+{
+	int i;
+
+	for (i = 0; i < NCT6687_NUM_REG_FAN; i++)
+	{
+		s16 rmp = nct6687_read16(data, NCT6687_REG_FAN_RPM(i));
+
+		data->rpm[0][i] = rmp;
+		data->rpm[1][i] = MIN(rmp, data->rpm[1][i]);
+		data->rpm[2][i] = MAX(rmp, data->rpm[2][i]);
+
+		pr_debug("nct6687_update_fans[%d], rpm=%d min=%d, max=%d", i, rmp, data->rpm[1][i], data->rpm[2][i]);
+	}
+
+	for (i = 0; i < NCT6687_NUM_REG_PWM; i++)
+	{
+		data->pwm[i] = nct6687_read(data, NCT6687_REG_PWM(i));
+
+		pr_debug("nct6687_update_fans[%d], pwm=%d", i, data->pwm[i]);
+	}
+}
+
+static struct nct6687_data *nct6687_update_device(struct device *dev)
+{
+	struct nct6687_data *data = dev_get_drvdata(dev);
+
+	mutex_lock(&data->update_lock);
+
+	if (time_after(jiffies, data->last_updated + HZ) || !data->valid)
+	{
+		/* Measured voltages and limits */
+		nct6687_update_voltage(data);
+
+		/* Measured temperatures and limits */
+		nct6687_update_temperatures(data);
+
+		/* Measured fan speeds and limits */
+		nct6687_update_fans(data);
+
+		data->last_updated = jiffies;
+		data->valid = true;
+	}
+
+	mutex_unlock(&data->update_lock);
+
+	return data;
+}
+
+/*
+ * Sysfs callback functions
+ */
+static ssize_t show_voltage_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+
+	if (manual)
+		return sprintf(buf, "in%d\n", sattr->index);
+	else
+		return sprintf(buf, "%s\n", nct6687_voltage_definition[sattr->index].label);
+}
+
+static ssize_t show_voltage_value(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct nct6687_data *data = nct6687_update_device(dev);
+
+	return sprintf(buf, "%d\n", data->voltage[sattr->index][sattr->nr]);
+}
+
+static umode_t nct6687_voltage_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+	pr_debug("nct6687_voltage_is_visible[%d], attr=0x%04X\n", index, attr->mode);
+	return attr->mode;
+}
+
+SENSOR_TEMPLATE(voltage_label, "in%d_label", S_IRUGO, show_voltage_label, NULL, 0);
+SENSOR_TEMPLATE_2(voltage_input, "in%d_input", S_IRUGO, show_voltage_value, NULL, 0, 0);
+SENSOR_TEMPLATE_2(voltage_min, "in%d_min", S_IRUGO, show_voltage_value, NULL, 0, 1);
+SENSOR_TEMPLATE_2(voltage_max, "in%d_max", S_IRUGO, show_voltage_value, NULL, 0, 2);
+
+static struct sensor_device_template *nct6687_attributes_voltage_template[] = {
+	&sensor_dev_template_voltage_label,
+	&sensor_dev_template_voltage_input,
+	&sensor_dev_template_voltage_min,
+	&sensor_dev_template_voltage_max,
+	NULL,
+};
+
+static const struct sensor_template_group nct6687_voltage_template_group = {
+	.templates = nct6687_attributes_voltage_template,
+	.is_visible = nct6687_voltage_is_visible,
+};
+
+static ssize_t show_fan_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+
+	return sprintf(buf, "%s\n", nct6687_fan_label[sattr->index]);
+}
+
+static ssize_t show_fan_value(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct nct6687_data *data = nct6687_update_device(dev);
+
+	return sprintf(buf, "%d\n", data->rpm[sattr->index][sattr->nr]);
+}
+
+static umode_t nct6687_fan_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+	return attr->mode;
+}
+
+SENSOR_TEMPLATE(fan_label, "fan%d_label", S_IRUGO, show_fan_label, NULL, 0);
+SENSOR_TEMPLATE_2(fan_input, "fan%d_input", S_IRUGO, show_fan_value, NULL, 0, 0);
+SENSOR_TEMPLATE_2(fan_min, "fan%d_min", S_IRUGO, show_fan_value, NULL, 0, 1);
+SENSOR_TEMPLATE_2(fan_max, "fan%d_max", S_IRUGO, show_fan_value, NULL, 0, 2);
+
+/*
+ * nct6687_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 *nct6687_attributes_fan_template[] = {
+	&sensor_dev_template_fan_label,
+	&sensor_dev_template_fan_input,
+	&sensor_dev_template_fan_min,
+	&sensor_dev_template_fan_max,
+	NULL,
+};
+
+static const struct sensor_template_group nct6687_fan_template_group = {
+	.templates = nct6687_attributes_fan_template,
+	.is_visible = nct6687_fan_is_visible,
+	.base = 1,
+};
+
+static ssize_t show_temperature_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+
+	return sprintf(buf, "%s\n", nct6687_temp_label[sattr->index]);
+}
+
+static ssize_t show_temperature_value(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct nct6687_data *data = nct6687_update_device(dev);
+
+	return sprintf(buf, "%d\n", data->temperature[sattr->index][sattr->nr]);
+}
+
+static umode_t nct6687_temp_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+	return attr->mode;
+}
+
+SENSOR_TEMPLATE(temp_label, "temp%d_label", S_IRUGO, show_temperature_label, NULL, 0);
+SENSOR_TEMPLATE_2(temp_input, "temp%d_input", S_IRUGO, show_temperature_value, NULL, 0, 0);
+SENSOR_TEMPLATE_2(temp_min, "temp%d_min", S_IRUGO, show_temperature_value, NULL, 0, 1);
+SENSOR_TEMPLATE_2(temp_max, "temp%d_max", S_IRUGO, show_temperature_value, NULL, 0, 2);
+
+/*
+ * nct6687_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 *nct6687_attributes_temp_template[] = {
+	&sensor_dev_template_temp_input,
+	&sensor_dev_template_temp_label,
+	&sensor_dev_template_temp_min,
+	&sensor_dev_template_temp_max,
+	NULL,
+};
+
+static const struct sensor_template_group nct6687_temp_template_group = {
+	.templates = nct6687_attributes_temp_template,
+	.is_visible = nct6687_temp_is_visible,
+	.base = 1,
+};
+
+static ssize_t show_pwm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct nct6687_data *data = nct6687_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]);
+}
+
+static ssize_t store_pwm(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct nct6687_data *data = dev_get_drvdata(dev);
+	int index = sattr->index;
+	unsigned long val;
+	u16 mode;
+	u8 bitMask;
+
+	if (kstrtoul(buf, 10, &val) || val > 255 || index >= NCT6687_NUM_REG_FAN)
+		return -EINVAL;
+
+	mutex_lock(&data->update_lock);
+
+	nct6687_save_fan_control(data, index);
+
+	mode = nct6687_read(data, NCT6687_REG_FAN_CTRL_MODE(index));
+	bitMask = (u8)(0x01 << index);
+
+	mode = (u8)(mode | bitMask);
+	nct6687_write(data, NCT6687_REG_FAN_CTRL_MODE(index), mode);
+
+	nct6687_write(data, NCT6687_REG_FAN_PWM_COMMAND(index), NCT6687_FAN_CFG_REQ);
+	msleep(50);
+	nct6687_write(data, NCT6687_REG_PWM_WRITE(index), val);
+	nct6687_write(data, NCT6687_REG_FAN_PWM_COMMAND(index), NCT6687_FAN_CFG_DONE);
+	msleep(50);
+
+	mutex_unlock(&data->update_lock);
+
+	return count;
+}
+
+SENSOR_TEMPLATE(pwm, "pwm%d", S_IRUGO, show_pwm, store_pwm, 0);
+
+static void nct6687_save_fan_control(struct nct6687_data *data, int index)
+{
+	if (data->_restoreDefaultFanControlRequired[index] == false)
+	{
+		u16 reg = nct6687_read(data, NCT6687_REG_FAN_CTRL_MODE(index));
+		u16 bitMask = 0x01 << index;
+		u8 pwm = nct6687_read(data, NCT6687_REG_FAN_PWM_COMMAND(index));
+
+		data->_initialFanControlMode[index] = (u8)(reg & bitMask);
+		data->_initialFanPwmCommand[index] = pwm;
+
+		data->_restoreDefaultFanControlRequired[index] = true;
+	}
+}
+
+static void nct6687_restore_fan_control(struct nct6687_data *data, int index)
+{
+	if (data->_restoreDefaultFanControlRequired[index])
+	{
+		u8 mode = nct6687_read(data, NCT6687_REG_FAN_CTRL_MODE(index));
+		mode = (u8)(mode & ~data->_initialFanControlMode[index]);
+
+		nct6687_write(data, NCT6687_REG_FAN_CTRL_MODE(index), mode);
+
+		nct6687_write(data, NCT6687_REG_FAN_PWM_COMMAND(index), NCT6687_FAN_CFG_REQ);
+		msleep(50);
+		nct6687_write(data, NCT6687_REG_PWM_WRITE(index), data->_initialFanPwmCommand[index]);
+		nct6687_write(data, NCT6687_REG_FAN_PWM_COMMAND(index), NCT6687_FAN_CFG_DONE);
+		msleep(50);
+
+		data->_restoreDefaultFanControlRequired[index] = false;
+
+		pr_debug("nct6687_restore_fan_control[%d], addr=%04X, ctrl=%04X, _initialFanPwmCommand=%d\n", index, NCT6687_REG_FAN_PWM_COMMAND(index), NCT6687_REG_PWM_WRITE(index), data->_initialFanPwmCommand[index]);
+	}
+}
+
+static umode_t nct6687_pwm_is_visible(struct kobject *kobj, struct attribute *attr, int index)
+{
+	return attr->mode | S_IWUSR;
+}
+
+static struct sensor_device_template *nct6687_attributes_pwm_template[] = {
+	&sensor_dev_template_pwm,
+	NULL,
+};
+
+static const struct sensor_template_group nct6687_pwm_template_group = {
+	.templates = nct6687_attributes_pwm_template,
+	.is_visible = nct6687_pwm_is_visible,
+	.base = 1,
+};
+
+/* Get the monitoring functions started */
+static inline void nct6687_init_device(struct nct6687_data *data)
+{
+	u8 tmp;
+
+	pr_debug("nct6687_init_device\n");
+
+	/* Start hardware monitoring if needed */
+	tmp = nct6687_read(data, NCT6687_HWM_CFG);
+	if (!(tmp & 0x80))
+	{
+		pr_debug("nct6687_init_device: 0x%04x\n", tmp);
+		nct6687_write(data, NCT6687_HWM_CFG, tmp | 0x80);
+	}
+
+	// enable SIO voltage
+	nct6687_write(data, 0x1BB, 0x61);
+	nct6687_write(data, 0x1BC, 0x62);
+	nct6687_write(data, 0x1BD, 0x63);
+	nct6687_write(data, 0x1BE, 0x64);
+	nct6687_write(data, 0x1BF, 0x65);
+}
+
+/*
+ * There are a total of 8 fan inputs.
+ */
+static void nct6687_setup_fans(struct nct6687_data *data)
+{
+	int i;
+
+	for (i = 0; i < NCT6687_NUM_REG_FAN; i++)
+	{
+		u16 reg = nct6687_read(data, NCT6687_REG_FAN_CTRL_MODE(i));
+		u16 bitMask = 0x01 << i;
+		u16 rpm = nct6687_read16(data, NCT6687_REG_FAN_RPM(i));
+
+		data->rpm[0][i] = rpm;
+		data->rpm[1][i] = rpm;
+		data->rpm[2][i] = rpm;
+		data->_initialFanControlMode[i] = (u8)(reg & bitMask);
+		data->_restoreDefaultFanControlRequired[i] = false;
+
+		pr_debug("nct6687_setup_fans[%d], %s - addr=%04X, ctrl=%04X, rpm=%d, _initialFanControlMode=%d\n", i, nct6687_fan_label[i], NCT6687_REG_FAN_CTRL_MODE(i), reg, rpm, data->_initialFanControlMode[i]);
+	}
+}
+
+static void nct6687_setup_voltages(struct nct6687_data *data)
+{
+	int index;
+	char buf[64];
+
+	/* Measured voltages and limits */
+	for (index = 0; index < NCT6687_NUM_REG_VOLTAGE; index++)
+	{
+		s16 reg = manual ? index : nct6687_voltage_definition[index].reg;
+		s16 high = nct6687_read(data, NCT6687_REG_VOLTAGE(reg)) * 16;
+		s16 low = ((u16)nct6687_read(data, NCT6687_REG_VOLTAGE(reg) + 1)) >> 4;
+		s16 value = low + high;
+		s16 voltage = manual ? value : value * nct6687_voltage_definition[index].multiplier;
+
+		data->voltage[0][index] = voltage;
+		data->voltage[1][index] = voltage;
+		data->voltage[2][index] = voltage;
+
+		pr_debug("nct6687_setup_voltages[%d], %s, addr=0x%04x, value=%d, voltage=%d\n", index, nct6687_voltage_label(buf, index), NCT6687_REG_VOLTAGE(index), value, voltage);
+	}
+}
+
+static void nct6687_setup_temperatures(struct nct6687_data *data)
+{
+	int i;
+
+	for (i = 0; i < NCT6687_NUM_REG_TEMP; i++)
+	{
+		s32 value = (char)nct6687_read(data, NCT6687_REG_TEMP(i));
+		s32 half = (nct6687_read(data, NCT6687_REG_TEMP(i) + 1) >> 7) & 0x1;
+		s32 temperature = (value * 1000) + (5 * half);
+
+		data->temperature[0][i] = temperature;
+		data->temperature[1][i] = temperature;
+		data->temperature[2][i] = temperature;
+
+		pr_debug("nct6687_setup_temperatures[%d]], addr=%04X, value=%d, half=%d, temperature=%d\n", i, NCT6687_REG_TEMP(i), value, half, temperature);
+	}
+}
+
+static void nct6687_setup_pwm(struct nct6687_data *data)
+{
+	int i;
+
+	for (i = 0; i < NCT6687_NUM_REG_PWM; i++)
+	{
+		data->_initialFanPwmCommand[i] = nct6687_read(data, NCT6687_REG_FAN_PWM_COMMAND(i));
+		data->pwm[i] = nct6687_read(data, NCT6687_REG_PWM(i));
+
+		pr_debug("nct6687_setup_pwm[%d], addr=%04X, pwm=%d, _initialFanPwmCommand=%d\n", i, NCT6687_REG_FAN_PWM_COMMAND(i), data->pwm[i], data->_initialFanPwmCommand[i]);
+	}
+}
+
+static int nct6687_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct nct6687_data *data = dev_get_drvdata(dev);
+	int i;
+
+	mutex_lock(&data->update_lock);
+
+	for (i = 0; i < NCT6687_NUM_REG_FAN; i++)
+	{
+		nct6687_restore_fan_control(data, i);
+	}
+
+	mutex_unlock(&data->update_lock);
+
+	return 0;
+}
+
+static int nct6687_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct nct6687_sio_data *sio_data = dev->platform_data;
+	struct attribute_group *group;
+	struct nct6687_data *data;
+	struct device *hwmon_dev;
+	struct resource *res;
+	int groups = 0;
+	char build[16];
+
+	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 nct6687_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->kind = sio_data->kind;
+	data->sioreg = sio_data->sioreg;
+	data->addr = res->start;
+
+	pr_debug("nct6687_probe addr=0x%04X, sioreg=0x%04X\n", data->addr, data->sioreg);
+
+	mutex_init(&data->update_lock);
+
+	platform_set_drvdata(pdev, data);
+
+	nct6687_init_device(data);
+	nct6687_setup_fans(data);
+	nct6687_setup_pwm(data);
+	nct6687_setup_temperatures(data);
+	nct6687_setup_voltages(data);
+
+	/* Register sysfs hooks */
+
+	group = nct6687_create_attr_group(dev, &nct6687_pwm_template_group, NCT6687_NUM_REG_FAN);
+
+	if (IS_ERR(group))
+		return PTR_ERR(group);
+
+	data->groups[groups++] = group;
+
+	group = nct6687_create_attr_group(dev, &nct6687_voltage_template_group, NCT6687_NUM_REG_VOLTAGE);
+
+	if (IS_ERR(group))
+		return PTR_ERR(group);
+
+	data->groups[groups++] = group;
+
+	group = nct6687_create_attr_group(dev, &nct6687_fan_template_group, NCT6687_NUM_REG_FAN);
+
+	if (IS_ERR(group))
+		return PTR_ERR(group);
+
+	data->groups[groups++] = group;
+
+	group = nct6687_create_attr_group(dev, &nct6687_temp_template_group, NCT6687_NUM_REG_TEMP);
+
+	if (IS_ERR(group))
+		return PTR_ERR(group);
+
+	data->groups[groups++] = group;
+
+	scnprintf(build, sizeof(build), "%02d/%02d/%02d", nct6687_read(data, NCT6687_REG_BUILD_MONTH), nct6687_read(data, NCT6687_REG_BUILD_DAY), nct6687_read(data, NCT6687_REG_BUILD_YEAR));
+
+	dev_info(dev, "%s EC firmware version %d.%d build %s\n", nct6687_chip_names[data->kind], nct6687_read(data, NCT6687_REG_VERSION_HI), nct6687_read(data, NCT6687_REG_VERSION_LO), build);
+
+	hwmon_dev = devm_hwmon_device_register_with_groups(dev, nct6687_device_names[data->kind], data, data->groups);
+
+	return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static int nct6687_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct nct6687_data *data = nct6687_update_device(&pdev->dev);
+
+	mutex_lock(&data->update_lock);
+	data->hwm_cfg = nct6687_read(data, NCT6687_HWM_CFG);
+	mutex_unlock(&data->update_lock);
+
+	return 0;
+}
+
+static int nct6687_resume(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct nct6687_data *data = dev_get_drvdata(dev);
+
+	mutex_lock(&data->update_lock);
+
+	nct6687_write(data, NCT6687_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 nct6687_dev_pm_ops = {
+// 	.suspend = nct6687_suspend,
+// 	.resume = nct6687_resume,
+// 	.freeze = nct6687_suspend,
+// 	.restore = nct6687_resume,
+// };
+
+#define NCT6687_DEV_PM_OPS NULL
+
+static struct platform_driver nct6687_driver = {
+	.driver = {
+		.name = DRVNAME,
+		.pm = NCT6687_DEV_PM_OPS,
+	},
+	.probe = nct6687_probe,
+	.remove = nct6687_remove,
+	.suspend = nct6687_suspend,
+	.resume = nct6687_resume,
+};
+
+static int __init nct6687_find(int sioaddr, struct nct6687_sio_data *sio_data)
+{
+	u16 address;
+	u16 verify;
+	u16 val;
+	int err;
+
+	err = superio_enter(sioaddr);
+	if (err)
+		return err;
+
+	val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8) | superio_inb(sioaddr, SIO_REG_DEVREVISION);
+
+	pr_debug("found chip ID: 0x%04x\n", val);
+
+	if (val == SIO_NCT6683D_ID) {
+		sio_data->kind = nct6683;
+	} else if (val == SIO_NCT6686_ID || val == SIO_NCT6686D_ID) {
+		sio_data->kind = nct6686;
+	} else if (val == SIO_NCT6687_ID || val == SIO_NCT6687D_ID || force)
+	{
+		sio_data->kind = nct6687;
+	}
+	else
+	{
+		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, NCT6687_LD_HWM);
+	address = (superio_inb(sioaddr, SIO_REG_ADDR) << 8) | superio_inb(sioaddr, SIO_REG_ADDR + 1);
+	ssleep(1);
+	verify = (superio_inb(sioaddr, SIO_REG_ADDR) << 8) | superio_inb(sioaddr, SIO_REG_ADDR + 1);
+
+	if (address == 0 || address != verify)
+	{
+		pr_err("EC base I/O port unconfigured\n");
+		goto fail;
+	}
+
+	if ((address & 0x07) == 0x05)
+		address &= 0xFFF8;
+
+	if (address < 0x100 || (address & 0xF007) != 0)
+	{
+		pr_err("EC Invalid address: 0x%04X\n", address);
+		goto fail;
+	}
+
+	/* Activate logical device if needed */
+	val = superio_inb(sioaddr, SIO_REG_ENABLE);
+	if (!(val & 0x01))
+	{
+		pr_warn("Forcibly enabling EC access. Data may be unusable.\n");
+		superio_outb(sioaddr, SIO_REG_ENABLE, val | 0x01);
+	}
+
+	superio_exit(sioaddr);
+	pr_info("Found %s or compatible chip at 0x%04x:0x%04x\n", nct6687_chip_names[sio_data->kind], sioaddr, address);
+	sio_data->sioreg = sioaddr;
+
+	return address;
+
+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 nct6687 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_nct6687_init(void)
+{
+	struct nct6687_sio_data sio_data;
+	int sioaddr[2] = {0x2e, 0x4e};
+	struct resource res;
+	bool found = false;
+	int address;
+	int i, err;
+
+	err = platform_driver_register(&nct6687_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
+	 * nct6687 hardware monitor, and call probe()
+	 */
+	for (i = 0; i < ARRAY_SIZE(pdev); i++)
+	{
+		address = nct6687_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_unregister;
+		}
+
+		err = platform_device_add_data(pdev[i], &sio_data, sizeof(struct nct6687_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:
+	platform_device_put(pdev[i]);
+
+exit_device_unregister:
+	while (--i >= 0)
+	{
+		if (pdev[i])
+			platform_device_unregister(pdev[i]);
+	}
+
+exit_unregister:
+	platform_driver_unregister(&nct6687_driver);
+
+	return err;
+}
+
+static void __exit sensors_nct6687_exit(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(pdev); i++)
+	{
+		if (pdev[i])
+			platform_device_unregister(pdev[i]);
+	}
+
+	platform_driver_unregister(&nct6687_driver);
+}
+
+MODULE_AUTHOR("Frederic Boltz <frederic.boltz@gmail.com>");
+MODULE_DESCRIPTION("Driver for NCT6687D");
+MODULE_LICENSE("GPL");
+
+module_init(sensors_nct6687_init);
+module_exit(sensors_nct6687_exit);
-- 
2.34.1


^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: Driver for Nuvoton NCT6687D eSIO chip
  2023-07-20 11:09 Driver for Nuvoton NCT6687D eSIO chip Thomas R
@ 2023-07-20 13:25 ` Guenter Roeck
  0 siblings, 0 replies; 2+ messages in thread
From: Guenter Roeck @ 2023-07-20 13:25 UTC (permalink / raw)
  To: Thomas R, jdelvare, linux-hwmon, linux-kernel

On 7/20/23 04:09, Thomas R wrote:
> To whom it may concern,
> 
> I am trying to submit a patch for driver Driver for Nuvoton NCT6687D eSIO chip under drivers/hwmon. This driver is confirmed to be working for motherboards series B550 and B650.
> 
> Credits for creating this driver should go to Frederic BOLTZ https://github.com/Fred78290/nct6687d
> 
> Attached is my patch


This is not the way to submit patches into the Linux kernel.

Anyway, this is pretty much just taking the existing nct6683 driver,
extending it with an additional ID for NCT6687 (0xd451), removing
customer ID checks, and claiming new authorship. No, this is not how
kernel development works. If those motherboards use an NCT6687 with
ID 0xd451, or if the boards use a new customer ID, submit patches updating
the existing driver. If there are other changes improving the existing
driver, submit patches for it. I am not going to accept a second driver
for the same chip.

Guenter


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2023-07-20 13:25 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-07-20 11:09 Driver for Nuvoton NCT6687D eSIO chip Thomas R
2023-07-20 13:25 ` Guenter Roeck

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

Powered by JetHome