mirror of
				git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
				synced 2025-09-04 20:19:47 +08:00 
			
		
		
		
	 c3dd4b7d1e
			
		
	
	
		c3dd4b7d1e
		
	
	
	
	
		
			
			Fix the following clang warning: drivers/hwmon/nct6683.c:491:19: warning: unused function 'in_to_reg' [-Wunused-function]. Reported-by: Abaci Robot <abaci@linux.alibaba.com> Signed-off-by: Jiapeng Chong <jiapeng.chong@linux.alibaba.com> Link: https://lore.kernel.org/r/1618293770-55307-1-git-send-email-jiapeng.chong@linux.alibaba.com Signed-off-by: Guenter Roeck <linux@roeck-us.net>
		
			
				
	
	
		
			1503 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1503 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0-or-later
 | |
| /*
 | |
|  * nct6683 - Driver for the hardware monitoring functionality of
 | |
|  *	     Nuvoton NCT6683D/NCT6686D/NCT6687D eSIO
 | |
|  *
 | |
|  * Copyright (C) 2013  Guenter Roeck <linux@roeck-us.net>
 | |
|  *
 | |
|  * Derived from nct6775 driver
 | |
|  * Copyright (C) 2012, 2013  Guenter Roeck <linux@roeck-us.net>
 | |
|  *
 | |
|  * Supports the following chips:
 | |
|  *
 | |
|  * Chip        #vin    #fan    #pwm    #temp  chip ID
 | |
|  * nct6683d     21(1)   16      8       32(1) 0xc730
 | |
|  * nct6686d     21(1)   16      8       32(1) 0xd440
 | |
|  * nct6687d     21(1)   16      8       32(1) 0xd590
 | |
|  *
 | |
|  * Notes:
 | |
|  *	(1) Total number of vin and temp inputs is 32.
 | |
|  */
 | |
| 
 | |
| #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>
 | |
| 
 | |
| enum kinds { nct6683, nct6686, nct6687 };
 | |
| 
 | |
| static bool force;
 | |
| module_param(force, bool, 0);
 | |
| MODULE_PARM_DESC(force, "Set to one to enable support for unknown vendors");
 | |
| 
 | |
| static const char * const nct6683_device_names[] = {
 | |
| 	"nct6683",
 | |
| 	"nct6686",
 | |
| 	"nct6687",
 | |
| };
 | |
| 
 | |
| static const char * const nct6683_chip_names[] = {
 | |
| 	"NCT6683D",
 | |
| 	"NCT6686D",
 | |
| 	"NCT6687D",
 | |
| };
 | |
| 
 | |
| #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_NCT6686_ID		0xd440
 | |
| #define SIO_NCT6687_ID		0xd590
 | |
| #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_PWM_WRITE(x)	(0xa28 + (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_FAN_CFG_CTRL	0xa01
 | |
| #define NCT6683_FAN_CFG_REQ		0x80
 | |
| #define NCT6683_FAN_CFG_DONE		0x40
 | |
| 
 | |
| #define NCT6683_REG_CUSTOMER_ID		0x602
 | |
| #define NCT6683_CUSTOMER_ID_INTEL	0x805
 | |
| #define NCT6683_CUSTOMER_ID_MITAC	0xa0e
 | |
| #define NCT6683_CUSTOMER_ID_MSI		0x201
 | |
| #define NCT6683_CUSTOMER_ID_ASROCK		0xe2c
 | |
| 
 | |
| #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,
 | |
| 			  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;
 | |
| }
 | |
| 
 | |
| /* 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 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;
 | |
| 	case NCT6683_CUSTOMER_ID_MITAC:
 | |
| 	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 = kobj_to_dev(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 const 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 = kobj_to_dev(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 const 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 = kobj_to_dev(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 const 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]);
 | |
| }
 | |
| 
 | |
| 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 nct6683_data *data = dev_get_drvdata(dev);
 | |
| 	int index = sattr->index;
 | |
| 	unsigned long val;
 | |
| 
 | |
| 	if (kstrtoul(buf, 10, &val) || val > 255)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	mutex_lock(&data->update_lock);
 | |
| 	nct6683_write(data, NCT6683_REG_FAN_CFG_CTRL, NCT6683_FAN_CFG_REQ);
 | |
| 	usleep_range(1000, 2000);
 | |
| 	nct6683_write(data, NCT6683_REG_PWM_WRITE(index), val);
 | |
| 	nct6683_write(data, NCT6683_REG_FAN_CFG_CTRL, NCT6683_FAN_CFG_DONE);
 | |
| 	mutex_unlock(&data->update_lock);
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| SENSOR_TEMPLATE(pwm, "pwm%d", S_IRUGO, show_pwm, store_pwm, 0);
 | |
| 
 | |
| static umode_t nct6683_pwm_is_visible(struct kobject *kobj,
 | |
| 				      struct attribute *attr, int index)
 | |
| {
 | |
| 	struct device *dev = kobj_to_dev(kobj);
 | |
| 	struct nct6683_data *data = dev_get_drvdata(dev);
 | |
| 	int pwm = index;	/* pwm index */
 | |
| 
 | |
| 	if (!(data->have_pwm & (1 << pwm)))
 | |
| 		return 0;
 | |
| 
 | |
| 	/* Only update pwm values for Mitac boards */
 | |
| 	if (data->customer_id == NCT6683_CUSTOMER_ID_MITAC)
 | |
| 		return attr->mode | S_IWUSR;
 | |
| 
 | |
| 	return attr->mode;
 | |
| }
 | |
| 
 | |
| static struct sensor_device_template *nct6683_attributes_pwm_template[] = {
 | |
| 	&sensor_dev_template_pwm,
 | |
| 	NULL
 | |
| };
 | |
| 
 | |
| static const struct sensor_template_group nct6683_pwm_template_group = {
 | |
| 	.templates = nct6683_attributes_pwm_template,
 | |
| 	.is_visible = nct6683_pwm_is_visible,
 | |
| 	.base = 1,
 | |
| };
 | |
| 
 | |
| static ssize_t
 | |
| beep_enable_show(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
 | |
| beep_enable_store(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
 | |
| intrusion0_alarm_show(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
 | |
| intrusion0_alarm_store(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_RW(intrusion0_alarm);
 | |
| static DEVICE_ATTR_RW(beep_enable);
 | |
| 
 | |
| 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;
 | |
| 	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 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);
 | |
| 
 | |
| 	/* By default only instantiate driver if the customer ID is known */
 | |
| 	switch (data->customer_id) {
 | |
| 	case NCT6683_CUSTOMER_ID_INTEL:
 | |
| 		break;
 | |
| 	case NCT6683_CUSTOMER_ID_MITAC:
 | |
| 		break;
 | |
| 	case NCT6683_CUSTOMER_ID_MSI:
 | |
| 		break;
 | |
| 	case NCT6683_CUSTOMER_ID_ASROCK:
 | |
| 		break;
 | |
| 	default:
 | |
| 		if (!force)
 | |
| 			return -ENODEV;
 | |
| 	}
 | |
| 
 | |
| 	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;
 | |
| 
 | |
| 	if (data->customer_id == NCT6683_CUSTOMER_ID_INTEL)
 | |
| 		scnprintf(build, sizeof(build), "%02x/%02x/%02x",
 | |
| 			  nct6683_read(data, NCT6683_REG_BUILD_MONTH),
 | |
| 			  nct6683_read(data, NCT6683_REG_BUILD_DAY),
 | |
| 			  nct6683_read(data, NCT6683_REG_BUILD_YEAR));
 | |
| 	else
 | |
| 		scnprintf(build, sizeof(build), "%02d/%02d/%02d",
 | |
| 			  nct6683_read(data, NCT6683_REG_BUILD_MONTH),
 | |
| 			  nct6683_read(data, NCT6683_REG_BUILD_DAY),
 | |
| 			  nct6683_read(data, NCT6683_REG_BUILD_YEAR));
 | |
| 
 | |
| 	dev_info(dev, "%s EC firmware version %d.%d build %s\n",
 | |
| 		 nct6683_chip_names[data->kind],
 | |
| 		 nct6683_read(data, NCT6683_REG_VERSION_HI),
 | |
| 		 nct6683_read(data, NCT6683_REG_VERSION_LO),
 | |
| 		 build);
 | |
| 
 | |
| 	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 = {
 | |
| 		.name	= DRVNAME,
 | |
| 		.pm	= NCT6683_DEV_PM_OPS,
 | |
| 	},
 | |
| 	.probe		= nct6683_probe,
 | |
| };
 | |
| 
 | |
| static int __init nct6683_find(int sioaddr, struct nct6683_sio_data *sio_data)
 | |
| {
 | |
| 	int addr;
 | |
| 	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_DEVID + 1);
 | |
| 
 | |
| 	switch (val & SIO_ID_MASK) {
 | |
| 	case SIO_NCT6683_ID:
 | |
| 		sio_data->kind = nct6683;
 | |
| 		break;
 | |
| 	case SIO_NCT6686_ID:
 | |
| 		sio_data->kind = nct6686;
 | |
| 		break;
 | |
| 	case SIO_NCT6687_ID:
 | |
| 		sio_data->kind = nct6687;
 | |
| 		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_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 %#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_unregister;
 | |
| 		}
 | |
| 
 | |
| 		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:
 | |
| 	platform_device_put(pdev[i]);
 | |
| exit_device_unregister:
 | |
| 	while (--i >= 0) {
 | |
| 		if (pdev[i])
 | |
| 			platform_device_unregister(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);
 |