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