| /* | 
 |  * nct6683 - Driver for the hardware monitoring functionality of | 
 |  *	     Nuvoton NCT6683D eSIO | 
 |  * | 
 |  * Copyright (C) 2013  Guenter Roeck <linux@roeck-us.net> | 
 |  * | 
 |  * Derived from nct6775 driver | 
 |  * Copyright (C) 2012, 2013  Guenter Roeck <linux@roeck-us.net> | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify | 
 |  * it under the terms of the GNU General Public License as published by | 
 |  * the Free Software Foundation; either version 2 of the License, or | 
 |  * (at your option) any later version. | 
 |  * | 
 |  * This program is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 |  * GNU General Public License for more details. | 
 |  * | 
 |  * Supports the following chips: | 
 |  * | 
 |  * Chip        #vin    #fan    #pwm    #temp  chip ID | 
 |  * nct6683d     21(1)   16      8       32(1) 0xc730 | 
 |  * | 
 |  * Notes: | 
 |  *	(1) Total number of vin and temp inputs is 32. | 
 |  */ | 
 |  | 
 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | 
 |  | 
 | #include <linux/acpi.h> | 
 | #include <linux/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 }; | 
 |  | 
 | 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", | 
 | }; | 
 |  | 
 | static const char * const nct6683_chip_names[] = { | 
 | 	"NCT6683D", | 
 | }; | 
 |  | 
 | #define DRVNAME "nct6683" | 
 |  | 
 | /* | 
 |  * Super-I/O constants and functions | 
 |  */ | 
 |  | 
 | #define NCT6683_LD_ACPI		0x0a | 
 | #define NCT6683_LD_HWM		0x0b | 
 | #define NCT6683_LD_VID		0x0d | 
 |  | 
 | #define SIO_REG_LDSEL		0x07	/* Logical device select */ | 
 | #define SIO_REG_DEVID		0x20	/* Device ID (2 bytes) */ | 
 | #define SIO_REG_ENABLE		0x30	/* Logical device enable */ | 
 | #define SIO_REG_ADDR		0x60	/* Logical device address (2 bytes) */ | 
 |  | 
 | #define SIO_NCT6681_ID		0xb270	/* for later */ | 
 | #define SIO_NCT6683_ID		0xc730 | 
 | #define SIO_ID_MASK		0xFFF0 | 
 |  | 
 | static inline void | 
 | superio_outb(int ioreg, int reg, int val) | 
 | { | 
 | 	outb(reg, ioreg); | 
 | 	outb(val, ioreg + 1); | 
 | } | 
 |  | 
 | static inline int | 
 | superio_inb(int ioreg, int reg) | 
 | { | 
 | 	outb(reg, ioreg); | 
 | 	return inb(ioreg + 1); | 
 | } | 
 |  | 
 | static inline void | 
 | superio_select(int ioreg, int ld) | 
 | { | 
 | 	outb(SIO_REG_LDSEL, ioreg); | 
 | 	outb(ld, ioreg + 1); | 
 | } | 
 |  | 
 | static inline int | 
 | superio_enter(int ioreg) | 
 | { | 
 | 	/* | 
 | 	 * Try to reserve <ioreg> and <ioreg + 1> for exclusive access. | 
 | 	 */ | 
 | 	if (!request_muxed_region(ioreg, 2, DRVNAME)) | 
 | 		return -EBUSY; | 
 |  | 
 | 	outb(0x87, ioreg); | 
 | 	outb(0x87, ioreg); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static inline void | 
 | superio_exit(int ioreg) | 
 | { | 
 | 	outb(0xaa, ioreg); | 
 | 	outb(0x02, ioreg); | 
 | 	outb(0x02, ioreg + 1); | 
 | 	release_region(ioreg, 2); | 
 | } | 
 |  | 
 | /* | 
 |  * ISA constants | 
 |  */ | 
 |  | 
 | #define IOREGION_ALIGNMENT	(~7) | 
 | #define IOREGION_OFFSET		4	/* Use EC port 1 */ | 
 | #define IOREGION_LENGTH		4 | 
 |  | 
 | #define EC_PAGE_REG		0 | 
 | #define EC_INDEX_REG		1 | 
 | #define EC_DATA_REG		2 | 
 | #define EC_EVENT_REG		3 | 
 |  | 
 | /* Common and NCT6683 specific data */ | 
 |  | 
 | #define NCT6683_NUM_REG_MON		32 | 
 | #define NCT6683_NUM_REG_FAN		16 | 
 | #define NCT6683_NUM_REG_PWM		8 | 
 |  | 
 | #define NCT6683_REG_MON(x)		(0x100 + (x) * 2) | 
 | #define NCT6683_REG_FAN_RPM(x)		(0x140 + (x) * 2) | 
 | #define NCT6683_REG_PWM(x)		(0x160 + (x)) | 
 | #define NCT6683_REG_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_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 inline u16 in_to_reg(u32 val, u8 src) | 
 | { | 
 | 	int scale = 16; | 
 |  | 
 | 	if (src == MON_SRC_VCC || src == MON_SRC_VSB || src == MON_SRC_AVSB || | 
 | 	    src == MON_SRC_VBAT) | 
 | 		scale <<= 1; | 
 |  | 
 | 	return clamp_val(DIV_ROUND_CLOSEST(val, scale), 0, 127); | 
 | } | 
 |  | 
 | static u16 nct6683_read(struct nct6683_data *data, u16 reg) | 
 | { | 
 | 	int res; | 
 |  | 
 | 	outb_p(0xff, data->addr + EC_PAGE_REG);		/* unlock */ | 
 | 	outb_p(reg >> 8, data->addr + EC_PAGE_REG); | 
 | 	outb_p(reg & 0xff, data->addr + EC_INDEX_REG); | 
 | 	res = inb_p(data->addr + EC_DATA_REG); | 
 | 	return res; | 
 | } | 
 |  | 
 | static u16 nct6683_read16(struct nct6683_data *data, u16 reg) | 
 | { | 
 | 	return (nct6683_read(data, reg) << 8) | nct6683_read(data, reg + 1); | 
 | } | 
 |  | 
 | static void nct6683_write(struct nct6683_data *data, u16 reg, u16 value) | 
 | { | 
 | 	outb_p(0xff, data->addr + EC_PAGE_REG);		/* unlock */ | 
 | 	outb_p(reg >> 8, data->addr + EC_PAGE_REG); | 
 | 	outb_p(reg & 0xff, data->addr + EC_INDEX_REG); | 
 | 	outb_p(value & 0xff, data->addr + EC_DATA_REG); | 
 | } | 
 |  | 
 | static int get_in_reg(struct nct6683_data *data, int nr, int index) | 
 | { | 
 | 	int ch = data->in_index[index]; | 
 | 	int reg = -EINVAL; | 
 |  | 
 | 	switch (nr) { | 
 | 	case 0: | 
 | 		reg = NCT6683_REG_MON(ch); | 
 | 		break; | 
 | 	case 1: | 
 | 		if (data->customer_id != NCT6683_CUSTOMER_ID_INTEL) | 
 | 			reg = NCT6683_REG_MON_LOW(ch); | 
 | 		break; | 
 | 	case 2: | 
 | 		if (data->customer_id != NCT6683_CUSTOMER_ID_INTEL) | 
 | 			reg = NCT6683_REG_MON_HIGH(ch); | 
 | 		break; | 
 | 	default: | 
 | 		break; | 
 | 	} | 
 | 	return reg; | 
 | } | 
 |  | 
 | static int get_temp_reg(struct nct6683_data *data, int nr, int index) | 
 | { | 
 | 	int ch = data->temp_index[index]; | 
 | 	int reg = -EINVAL; | 
 |  | 
 | 	switch (data->customer_id) { | 
 | 	case NCT6683_CUSTOMER_ID_INTEL: | 
 | 		switch (nr) { | 
 | 		default: | 
 | 		case 1:	/* max */ | 
 | 			reg = NCT6683_REG_INTEL_TEMP_MAX(ch); | 
 | 			break; | 
 | 		case 3:	/* crit */ | 
 | 			reg = NCT6683_REG_INTEL_TEMP_CRIT(ch); | 
 | 			break; | 
 | 		} | 
 | 		break; | 
 | 	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 = container_of(kobj, struct device, kobj); | 
 | 	struct nct6683_data *data = dev_get_drvdata(dev); | 
 | 	int nr = index % 4;	/* attribute */ | 
 |  | 
 | 	/* | 
 | 	 * Voltage limits exist for Intel boards, | 
 | 	 * but register location and encoding is unknown | 
 | 	 */ | 
 | 	if ((nr == 2 || nr == 3) && | 
 | 	    data->customer_id == NCT6683_CUSTOMER_ID_INTEL) | 
 | 		return 0; | 
 |  | 
 | 	return attr->mode; | 
 | } | 
 |  | 
 | SENSOR_TEMPLATE(in_label, "in%d_label", S_IRUGO, show_in_label, NULL, 0); | 
 | SENSOR_TEMPLATE_2(in_input, "in%d_input", S_IRUGO, show_in_reg, NULL, 0, 0); | 
 | SENSOR_TEMPLATE_2(in_min, "in%d_min", S_IRUGO, show_in_reg, NULL, 0, 1); | 
 | SENSOR_TEMPLATE_2(in_max, "in%d_max", S_IRUGO, show_in_reg, NULL, 0, 2); | 
 |  | 
 | static struct sensor_device_template *nct6683_attributes_in_template[] = { | 
 | 	&sensor_dev_template_in_label, | 
 | 	&sensor_dev_template_in_input, | 
 | 	&sensor_dev_template_in_min, | 
 | 	&sensor_dev_template_in_max, | 
 | 	NULL | 
 | }; | 
 |  | 
 | static 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 = container_of(kobj, struct device, kobj); | 
 | 	struct nct6683_data *data = dev_get_drvdata(dev); | 
 | 	int fan = index / 3;	/* fan index */ | 
 | 	int nr = index % 3;	/* attribute index */ | 
 |  | 
 | 	if (!(data->have_fan & (1 << fan))) | 
 | 		return 0; | 
 |  | 
 | 	/* | 
 | 	 * Intel may have minimum fan speed limits, | 
 | 	 * but register location and encoding are unknown. | 
 | 	 */ | 
 | 	if (nr == 2 && data->customer_id == NCT6683_CUSTOMER_ID_INTEL) | 
 | 		return 0; | 
 |  | 
 | 	return attr->mode; | 
 | } | 
 |  | 
 | SENSOR_TEMPLATE(fan_input, "fan%d_input", S_IRUGO, show_fan, NULL, 0); | 
 | SENSOR_TEMPLATE(fan_pulses, "fan%d_pulses", S_IRUGO, show_fan_pulses, NULL, 0); | 
 | SENSOR_TEMPLATE(fan_min, "fan%d_min", S_IRUGO, show_fan_min, NULL, 0); | 
 |  | 
 | /* | 
 |  * nct6683_fan_is_visible uses the index into the following array | 
 |  * to determine if attributes should be created or not. | 
 |  * Any change in order or content must be matched. | 
 |  */ | 
 | static struct sensor_device_template *nct6683_attributes_fan_template[] = { | 
 | 	&sensor_dev_template_fan_input, | 
 | 	&sensor_dev_template_fan_pulses, | 
 | 	&sensor_dev_template_fan_min, | 
 | 	NULL | 
 | }; | 
 |  | 
 | static 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 = container_of(kobj, struct device, kobj); | 
 | 	struct nct6683_data *data = dev_get_drvdata(dev); | 
 | 	int temp = index / 7;	/* temp index */ | 
 | 	int nr = index % 7;	/* attribute index */ | 
 |  | 
 | 	/* | 
 | 	 * Intel does not have low temperature limits or temperature hysteresis | 
 | 	 * registers, or at least register location and encoding is unknown. | 
 | 	 */ | 
 | 	if ((nr == 2 || nr == 4) && | 
 | 	    data->customer_id == NCT6683_CUSTOMER_ID_INTEL) | 
 | 		return 0; | 
 |  | 
 | 	if (nr == 6 && get_temp_type(data->temp_src[temp]) == 0) | 
 | 		return 0;				/* type */ | 
 |  | 
 | 	return attr->mode; | 
 | } | 
 |  | 
 | SENSOR_TEMPLATE(temp_input, "temp%d_input", S_IRUGO, show_temp16, NULL, 0); | 
 | SENSOR_TEMPLATE(temp_label, "temp%d_label", S_IRUGO, show_temp_label, NULL, 0); | 
 | SENSOR_TEMPLATE_2(temp_min, "temp%d_min", S_IRUGO, show_temp8, NULL, 0, 0); | 
 | SENSOR_TEMPLATE_2(temp_max, "temp%d_max", S_IRUGO, show_temp8, NULL, 0, 1); | 
 | SENSOR_TEMPLATE(temp_max_hyst, "temp%d_max_hyst", S_IRUGO, show_temp_hyst, NULL, | 
 | 		0); | 
 | SENSOR_TEMPLATE_2(temp_crit, "temp%d_crit", S_IRUGO, show_temp8, NULL, 0, 3); | 
 | SENSOR_TEMPLATE(temp_type, "temp%d_type", S_IRUGO, show_temp_type, NULL, 0); | 
 |  | 
 | /* | 
 |  * nct6683_temp_is_visible uses the index into the following array | 
 |  * to determine if attributes should be created or not. | 
 |  * Any change in order or content must be matched. | 
 |  */ | 
 | static struct sensor_device_template *nct6683_attributes_temp_template[] = { | 
 | 	&sensor_dev_template_temp_input, | 
 | 	&sensor_dev_template_temp_label, | 
 | 	&sensor_dev_template_temp_min,		/* 2 */ | 
 | 	&sensor_dev_template_temp_max,		/* 3 */ | 
 | 	&sensor_dev_template_temp_max_hyst,	/* 4 */ | 
 | 	&sensor_dev_template_temp_crit,		/* 5 */ | 
 | 	&sensor_dev_template_temp_type,		/* 6 */ | 
 | 	NULL | 
 | }; | 
 |  | 
 | static 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 = container_of(kobj, struct device, kobj); | 
 | 	struct nct6683_data *data = dev_get_drvdata(dev); | 
 | 	int pwm = index;	/* pwm index */ | 
 |  | 
 | 	if (!(data->have_pwm & (1 << pwm))) | 
 | 		return 0; | 
 |  | 
 | 	/* 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; | 
 | 	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; | 
 | 	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); |