#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"
#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)
{
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);
}
#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
#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_ASROCK 0xe2c
#define NCT6683_CUSTOMER_ID_ASROCK2 0xe1b
#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,
"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",
"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",
"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",
"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",
"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",
"Virtual 1",
"Virtual 2",
"Virtual 3",
"Virtual 4",
"Virtual 5",
"Virtual 6",
"Virtual 7",
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
"VCC",
"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;
int sioreg;
enum kinds kind;
u16 customer_id;
struct device *hwmon_dev;
const struct attribute_group *groups[6];
int temp_num;
u8 temp_index[NCT6683_NUM_REG_MON];
u8 temp_src[NCT6683_NUM_REG_MON];
u8 in_num;
u8 in_index[NCT6683_NUM_REG_MON];
u8 in_src[NCT6683_NUM_REG_MON];
struct mutex update_lock;
bool valid;
unsigned long last_updated;
u8 in[3][NCT6683_NUM_REG_MON];
s16 temp_in[NCT6683_NUM_REG_MON];
s8 temp[4][NCT6683_NUM_REG_MON];
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;
u8 have_pwm;
u8 pwm[NCT6683_NUM_REG_PWM];
#ifdef CONFIG_PM
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;
};
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;
}
#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);
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);
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:
reg = NCT6683_REG_INTEL_TEMP_MAX(ch);
break;
case 3:
reg = NCT6683_REG_INTEL_TEMP_CRIT(ch);
break;
}
break;
case NCT6683_CUSTOMER_ID_MITAC:
default:
switch (nr) {
default:
case 0:
reg = NCT6683_REG_MON_LOW(ch);
break;
case 1:
reg = NCT6683_REG_TEMP_MAX(ch);
break;
case 2:
reg = NCT6683_REG_TEMP_HYST(ch);
break;
case 3:
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) {
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);
}
}
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);
}
}
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;
}
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;
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;
int nr = index % 3;
if (!(data->have_fan & (1 << fan)))
return 0;
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);
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);
}
static int get_temp_type(u8 src)
{
if (src >= 0x02 && src <= 0x07)
return 3;
else if (src >= 0x08 && src <= 0x18)
return 4;
else if (src >= 0x20 && src <= 0x2b)
return 6;
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;
int nr = index % 7;
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;
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);
static struct sensor_device_template *nct6683_attributes_temp_template[] = {
&sensor_dev_template_temp_input,
&sensor_dev_template_temp_label,
&sensor_dev_template_temp_min,
&sensor_dev_template_temp_max,
&sensor_dev_template_temp_max_hyst,
&sensor_dev_template_temp_crit,
&sensor_dev_template_temp_type,
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;
if (!(data->have_pwm & (1 << pwm)))
return 0;
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;
}
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);
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;
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,
};
static inline void nct6683_init_device(struct nct6683_data *data)
{
u8 tmp;
tmp = nct6683_read(data, NCT6683_HWM_CFG);
if (!(tmp & 0x80))
nct6683_write(data, NCT6683_HWM_CFG, tmp | 0x80);
}
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;
}
}
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;
if (reg >= NUM_MON_LABELS)
continue;
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);
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_ASROCK:
break;
case NCT6683_CUSTOMER_ID_ASROCK2:
break;
default:
if (!force)
return -ENODEV;
}
nct6683_init_device(data);
nct6683_setup_fans(data);
nct6683_setup_sensors(data);
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);
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;
}
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;
}
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;
}
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;
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;
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