// 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 " );
MODULE_DESCRIPTION("NCT6683D driver" );
MODULE_LICENSE("GPL" );
module_init(sensors_nct6683_init);
module_exit(sensors_nct6683_exit);
Messung V0.5 C=96 H=93 G=94
¤ Dauer der Verarbeitung: 0.14 Sekunden
¤
*© Formatika GbR, Deutschland