|  | // 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_MSI2	0x200 | 
|  | #define NCT6683_CUSTOMER_ID_MSI3	0x207 | 
|  | #define NCT6683_CUSTOMER_ID_MSI4	0x20d | 
|  | #define NCT6683_CUSTOMER_ID_AMD		0x162b | 
|  | #define NCT6683_CUSTOMER_ID_ASROCK		0xe2c | 
|  | #define NCT6683_CUSTOMER_ID_ASROCK2	0xe1b | 
|  | #define NCT6683_CUSTOMER_ID_ASROCK3	0x1631 | 
|  | #define NCT6683_CUSTOMER_ID_ASROCK4	0x163e | 
|  |  | 
|  | #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, 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; | 
|  | while (*t) { | 
|  | 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_MSI2: | 
|  | break; | 
|  | case NCT6683_CUSTOMER_ID_MSI3: | 
|  | break; | 
|  | case NCT6683_CUSTOMER_ID_MSI4: | 
|  | break; | 
|  | case NCT6683_CUSTOMER_ID_AMD: | 
|  | break; | 
|  | case NCT6683_CUSTOMER_ID_ASROCK: | 
|  | break; | 
|  | case NCT6683_CUSTOMER_ID_ASROCK2: | 
|  | break; | 
|  | case NCT6683_CUSTOMER_ID_ASROCK3: | 
|  | break; | 
|  | case NCT6683_CUSTOMER_ID_ASROCK4: | 
|  | break; | 
|  | default: | 
|  | if (!force) | 
|  | return -ENODEV; | 
|  | dev_warn(dev, "Enabling support for unknown customer ID 0x%04x\n", data->customer_id); | 
|  | break; | 
|  | } | 
|  |  | 
|  | 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); |