forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1502 lines
37 KiB
1502 lines
37 KiB
// 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 <[email protected]> |
|
* |
|
* Derived from nct6775 driver |
|
* Copyright (C) 2012, 2013 Guenter Roeck <[email protected]> |
|
* |
|
* 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_ASROCK 0xe2c |
|
|
|
#define NCT6683_REG_BUILD_YEAR 0x604 |
|
#define NCT6683_REG_BUILD_MONTH 0x605 |
|
#define NCT6683_REG_BUILD_DAY 0x606 |
|
#define NCT6683_REG_SERIAL 0x607 |
|
#define NCT6683_REG_VERSION_HI 0x608 |
|
#define NCT6683_REG_VERSION_LO 0x609 |
|
|
|
#define NCT6683_REG_CR_CASEOPEN 0xe8 |
|
#define NCT6683_CR_CASEOPEN_MASK (1 << 7) |
|
|
|
#define NCT6683_REG_CR_BEEP 0xe0 |
|
#define NCT6683_CR_BEEP_MASK (1 << 6) |
|
|
|
static const char *const nct6683_mon_label[] = { |
|
NULL, /* disabled */ |
|
"Local", |
|
"Diode 0 (curr)", |
|
"Diode 1 (curr)", |
|
"Diode 2 (curr)", |
|
"Diode 0 (volt)", |
|
"Diode 1 (volt)", |
|
"Diode 2 (volt)", |
|
"Thermistor 14", |
|
"Thermistor 15", |
|
"Thermistor 16", |
|
"Thermistor 0", |
|
"Thermistor 1", |
|
"Thermistor 2", |
|
"Thermistor 3", |
|
"Thermistor 4", |
|
"Thermistor 5", /* 0x10 */ |
|
"Thermistor 6", |
|
"Thermistor 7", |
|
"Thermistor 8", |
|
"Thermistor 9", |
|
"Thermistor 10", |
|
"Thermistor 11", |
|
"Thermistor 12", |
|
"Thermistor 13", |
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, |
|
"PECI 0.0", /* 0x20 */ |
|
"PECI 1.0", |
|
"PECI 2.0", |
|
"PECI 3.0", |
|
"PECI 0.1", |
|
"PECI 1.1", |
|
"PECI 2.1", |
|
"PECI 3.1", |
|
"PECI DIMM 0", |
|
"PECI DIMM 1", |
|
"PECI DIMM 2", |
|
"PECI DIMM 3", |
|
NULL, NULL, NULL, NULL, |
|
"PCH CPU", /* 0x30 */ |
|
"PCH CHIP", |
|
"PCH CHIP CPU MAX", |
|
"PCH MCH", |
|
"PCH DIMM 0", |
|
"PCH DIMM 1", |
|
"PCH DIMM 2", |
|
"PCH DIMM 3", |
|
"SMBus 0", |
|
"SMBus 1", |
|
"SMBus 2", |
|
"SMBus 3", |
|
"SMBus 4", |
|
"SMBus 5", |
|
"DIMM 0", |
|
"DIMM 1", |
|
"DIMM 2", /* 0x40 */ |
|
"DIMM 3", |
|
"AMD TSI Addr 90h", |
|
"AMD TSI Addr 92h", |
|
"AMD TSI Addr 94h", |
|
"AMD TSI Addr 96h", |
|
"AMD TSI Addr 98h", |
|
"AMD TSI Addr 9ah", |
|
"AMD TSI Addr 9ch", |
|
"AMD TSI Addr 9dh", |
|
NULL, NULL, NULL, NULL, NULL, NULL, |
|
"Virtual 0", /* 0x50 */ |
|
"Virtual 1", |
|
"Virtual 2", |
|
"Virtual 3", |
|
"Virtual 4", |
|
"Virtual 5", |
|
"Virtual 6", |
|
"Virtual 7", |
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, |
|
"VCC", /* 0x60 voltage sensors */ |
|
"VSB", |
|
"AVSB", |
|
"VTT", |
|
"VBAT", |
|
"VREF", |
|
"VIN0", |
|
"VIN1", |
|
"VIN2", |
|
"VIN3", |
|
"VIN4", |
|
"VIN5", |
|
"VIN6", |
|
"VIN7", |
|
"VIN8", |
|
"VIN9", |
|
"VIN10", |
|
"VIN11", |
|
"VIN12", |
|
"VIN13", |
|
"VIN14", |
|
"VIN15", |
|
"VIN16", |
|
}; |
|
|
|
#define NUM_MON_LABELS ARRAY_SIZE(nct6683_mon_label) |
|
#define MON_VOLTAGE_START 0x60 |
|
|
|
/* ------------------------------------------------------- */ |
|
|
|
struct nct6683_data { |
|
int addr; /* IO base of EC space */ |
|
int sioreg; /* SIO register */ |
|
enum kinds kind; |
|
u16 customer_id; |
|
|
|
struct device *hwmon_dev; |
|
const struct attribute_group *groups[6]; |
|
|
|
int temp_num; /* number of temperature attributes */ |
|
u8 temp_index[NCT6683_NUM_REG_MON]; |
|
u8 temp_src[NCT6683_NUM_REG_MON]; |
|
|
|
u8 in_num; /* number of voltage attributes */ |
|
u8 in_index[NCT6683_NUM_REG_MON]; |
|
u8 in_src[NCT6683_NUM_REG_MON]; |
|
|
|
struct mutex update_lock; /* used to protect sensor updates */ |
|
bool valid; /* true if following fields are valid */ |
|
unsigned long last_updated; /* In jiffies */ |
|
|
|
/* Voltage attribute values */ |
|
u8 in[3][NCT6683_NUM_REG_MON]; /* [0]=in, [1]=in_max, [2]=in_min */ |
|
|
|
/* Temperature attribute values */ |
|
s16 temp_in[NCT6683_NUM_REG_MON]; |
|
s8 temp[4][NCT6683_NUM_REG_MON];/* [0]=min, [1]=max, [2]=hyst, |
|
* [3]=crit |
|
*/ |
|
|
|
/* Fan attribute values */ |
|
unsigned int rpm[NCT6683_NUM_REG_FAN]; |
|
u16 fan_min[NCT6683_NUM_REG_FAN]; |
|
u8 fanin_cfg[NCT6683_NUM_REG_FAN]; |
|
u8 fanout_cfg[NCT6683_NUM_REG_FAN]; |
|
u16 have_fan; /* some fan inputs can be disabled */ |
|
|
|
u8 have_pwm; |
|
u8 pwm[NCT6683_NUM_REG_PWM]; |
|
|
|
#ifdef CONFIG_PM |
|
/* Remember extra register values over suspend/resume */ |
|
u8 hwm_cfg; |
|
#endif |
|
}; |
|
|
|
struct nct6683_sio_data { |
|
int sioreg; |
|
enum kinds kind; |
|
}; |
|
|
|
struct sensor_device_template { |
|
struct device_attribute dev_attr; |
|
union { |
|
struct { |
|
u8 nr; |
|
u8 index; |
|
} s; |
|
int index; |
|
} u; |
|
bool s2; /* true if both index and nr are used */ |
|
}; |
|
|
|
struct sensor_device_attr_u { |
|
union { |
|
struct sensor_device_attribute a1; |
|
struct sensor_device_attribute_2 a2; |
|
} u; |
|
char name[32]; |
|
}; |
|
|
|
#define __TEMPLATE_ATTR(_template, _mode, _show, _store) { \ |
|
.attr = {.name = _template, .mode = _mode }, \ |
|
.show = _show, \ |
|
.store = _store, \ |
|
} |
|
|
|
#define SENSOR_DEVICE_TEMPLATE(_template, _mode, _show, _store, _index) \ |
|
{ .dev_attr = __TEMPLATE_ATTR(_template, _mode, _show, _store), \ |
|
.u.index = _index, \ |
|
.s2 = false } |
|
|
|
#define SENSOR_DEVICE_TEMPLATE_2(_template, _mode, _show, _store, \ |
|
_nr, _index) \ |
|
{ .dev_attr = __TEMPLATE_ATTR(_template, _mode, _show, _store), \ |
|
.u.s.index = _index, \ |
|
.u.s.nr = _nr, \ |
|
.s2 = true } |
|
|
|
#define SENSOR_TEMPLATE(_name, _template, _mode, _show, _store, _index) \ |
|
static struct sensor_device_template sensor_dev_template_##_name \ |
|
= SENSOR_DEVICE_TEMPLATE(_template, _mode, _show, _store, \ |
|
_index) |
|
|
|
#define SENSOR_TEMPLATE_2(_name, _template, _mode, _show, _store, \ |
|
_nr, _index) \ |
|
static struct sensor_device_template sensor_dev_template_##_name \ |
|
= SENSOR_DEVICE_TEMPLATE_2(_template, _mode, _show, _store, \ |
|
_nr, _index) |
|
|
|
struct sensor_template_group { |
|
struct sensor_device_template **templates; |
|
umode_t (*is_visible)(struct kobject *, struct attribute *, int); |
|
int base; |
|
}; |
|
|
|
static struct attribute_group * |
|
nct6683_create_attr_group(struct device *dev, |
|
const struct sensor_template_group *tg, |
|
int repeat) |
|
{ |
|
struct sensor_device_attribute_2 *a2; |
|
struct sensor_device_attribute *a; |
|
struct sensor_device_template **t; |
|
struct sensor_device_attr_u *su; |
|
struct attribute_group *group; |
|
struct attribute **attrs; |
|
int i, j, count; |
|
|
|
if (repeat <= 0) |
|
return ERR_PTR(-EINVAL); |
|
|
|
t = tg->templates; |
|
for (count = 0; *t; t++, count++) |
|
; |
|
|
|
if (count == 0) |
|
return ERR_PTR(-EINVAL); |
|
|
|
group = devm_kzalloc(dev, sizeof(*group), GFP_KERNEL); |
|
if (group == NULL) |
|
return ERR_PTR(-ENOMEM); |
|
|
|
attrs = devm_kcalloc(dev, repeat * count + 1, sizeof(*attrs), |
|
GFP_KERNEL); |
|
if (attrs == NULL) |
|
return ERR_PTR(-ENOMEM); |
|
|
|
su = devm_kzalloc(dev, array3_size(repeat, count, sizeof(*su)), |
|
GFP_KERNEL); |
|
if (su == NULL) |
|
return ERR_PTR(-ENOMEM); |
|
|
|
group->attrs = attrs; |
|
group->is_visible = tg->is_visible; |
|
|
|
for (i = 0; i < repeat; i++) { |
|
t = tg->templates; |
|
for (j = 0; *t != NULL; j++) { |
|
snprintf(su->name, sizeof(su->name), |
|
(*t)->dev_attr.attr.name, tg->base + i); |
|
if ((*t)->s2) { |
|
a2 = &su->u.a2; |
|
sysfs_attr_init(&a2->dev_attr.attr); |
|
a2->dev_attr.attr.name = su->name; |
|
a2->nr = (*t)->u.s.nr + i; |
|
a2->index = (*t)->u.s.index; |
|
a2->dev_attr.attr.mode = |
|
(*t)->dev_attr.attr.mode; |
|
a2->dev_attr.show = (*t)->dev_attr.show; |
|
a2->dev_attr.store = (*t)->dev_attr.store; |
|
*attrs = &a2->dev_attr.attr; |
|
} else { |
|
a = &su->u.a1; |
|
sysfs_attr_init(&a->dev_attr.attr); |
|
a->dev_attr.attr.name = su->name; |
|
a->index = (*t)->u.index + i; |
|
a->dev_attr.attr.mode = |
|
(*t)->dev_attr.attr.mode; |
|
a->dev_attr.show = (*t)->dev_attr.show; |
|
a->dev_attr.store = (*t)->dev_attr.store; |
|
*attrs = &a->dev_attr.attr; |
|
} |
|
attrs++; |
|
su++; |
|
t++; |
|
} |
|
} |
|
|
|
return group; |
|
} |
|
|
|
/* LSB is 16 mV, except for the following sources, where it is 32 mV */ |
|
#define MON_SRC_VCC 0x60 |
|
#define MON_SRC_VSB 0x61 |
|
#define MON_SRC_AVSB 0x62 |
|
#define MON_SRC_VBAT 0x64 |
|
|
|
static inline long in_from_reg(u16 reg, u8 src) |
|
{ |
|
int scale = 16; |
|
|
|
if (src == MON_SRC_VCC || src == MON_SRC_VSB || src == MON_SRC_AVSB || |
|
src == MON_SRC_VBAT) |
|
scale <<= 1; |
|
return reg * scale; |
|
} |
|
|
|
static 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_ASROCK: |
|
break; |
|
default: |
|
if (!force) |
|
return -ENODEV; |
|
} |
|
|
|
nct6683_init_device(data); |
|
nct6683_setup_fans(data); |
|
nct6683_setup_sensors(data); |
|
|
|
/* Register sysfs hooks */ |
|
|
|
if (data->have_pwm) { |
|
group = nct6683_create_attr_group(dev, |
|
&nct6683_pwm_template_group, |
|
fls(data->have_pwm)); |
|
if (IS_ERR(group)) |
|
return PTR_ERR(group); |
|
data->groups[groups++] = group; |
|
} |
|
|
|
if (data->in_num) { |
|
group = nct6683_create_attr_group(dev, |
|
&nct6683_in_template_group, |
|
data->in_num); |
|
if (IS_ERR(group)) |
|
return PTR_ERR(group); |
|
data->groups[groups++] = group; |
|
} |
|
|
|
if (data->have_fan) { |
|
group = nct6683_create_attr_group(dev, |
|
&nct6683_fan_template_group, |
|
fls(data->have_fan)); |
|
if (IS_ERR(group)) |
|
return PTR_ERR(group); |
|
data->groups[groups++] = group; |
|
} |
|
|
|
if (data->temp_num) { |
|
group = nct6683_create_attr_group(dev, |
|
&nct6683_temp_template_group, |
|
data->temp_num); |
|
if (IS_ERR(group)) |
|
return PTR_ERR(group); |
|
data->groups[groups++] = group; |
|
} |
|
data->groups[groups++] = &nct6683_group_other; |
|
|
|
if (data->customer_id == NCT6683_CUSTOMER_ID_INTEL) |
|
scnprintf(build, sizeof(build), "%02x/%02x/%02x", |
|
nct6683_read(data, NCT6683_REG_BUILD_MONTH), |
|
nct6683_read(data, NCT6683_REG_BUILD_DAY), |
|
nct6683_read(data, NCT6683_REG_BUILD_YEAR)); |
|
else |
|
scnprintf(build, sizeof(build), "%02d/%02d/%02d", |
|
nct6683_read(data, NCT6683_REG_BUILD_MONTH), |
|
nct6683_read(data, NCT6683_REG_BUILD_DAY), |
|
nct6683_read(data, NCT6683_REG_BUILD_YEAR)); |
|
|
|
dev_info(dev, "%s EC firmware version %d.%d build %s\n", |
|
nct6683_chip_names[data->kind], |
|
nct6683_read(data, NCT6683_REG_VERSION_HI), |
|
nct6683_read(data, NCT6683_REG_VERSION_LO), |
|
build); |
|
|
|
hwmon_dev = devm_hwmon_device_register_with_groups(dev, |
|
nct6683_device_names[data->kind], data, data->groups); |
|
return PTR_ERR_OR_ZERO(hwmon_dev); |
|
} |
|
|
|
#ifdef CONFIG_PM |
|
static int nct6683_suspend(struct device *dev) |
|
{ |
|
struct nct6683_data *data = nct6683_update_device(dev); |
|
|
|
mutex_lock(&data->update_lock); |
|
data->hwm_cfg = nct6683_read(data, NCT6683_HWM_CFG); |
|
mutex_unlock(&data->update_lock); |
|
|
|
return 0; |
|
} |
|
|
|
static int nct6683_resume(struct device *dev) |
|
{ |
|
struct nct6683_data *data = dev_get_drvdata(dev); |
|
|
|
mutex_lock(&data->update_lock); |
|
|
|
nct6683_write(data, NCT6683_HWM_CFG, data->hwm_cfg); |
|
|
|
/* Force re-reading all values */ |
|
data->valid = false; |
|
mutex_unlock(&data->update_lock); |
|
|
|
return 0; |
|
} |
|
|
|
static const struct dev_pm_ops nct6683_dev_pm_ops = { |
|
.suspend = nct6683_suspend, |
|
.resume = nct6683_resume, |
|
.freeze = nct6683_suspend, |
|
.restore = nct6683_resume, |
|
}; |
|
|
|
#define NCT6683_DEV_PM_OPS (&nct6683_dev_pm_ops) |
|
#else |
|
#define NCT6683_DEV_PM_OPS NULL |
|
#endif /* CONFIG_PM */ |
|
|
|
static struct platform_driver nct6683_driver = { |
|
.driver = { |
|
.name = DRVNAME, |
|
.pm = NCT6683_DEV_PM_OPS, |
|
}, |
|
.probe = nct6683_probe, |
|
}; |
|
|
|
static int __init nct6683_find(int sioaddr, struct nct6683_sio_data *sio_data) |
|
{ |
|
int addr; |
|
u16 val; |
|
int err; |
|
|
|
err = superio_enter(sioaddr); |
|
if (err) |
|
return err; |
|
|
|
val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8) |
|
| superio_inb(sioaddr, SIO_REG_DEVID + 1); |
|
|
|
switch (val & SIO_ID_MASK) { |
|
case SIO_NCT6683_ID: |
|
sio_data->kind = nct6683; |
|
break; |
|
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 <[email protected]>"); |
|
MODULE_DESCRIPTION("NCT6683D driver"); |
|
MODULE_LICENSE("GPL"); |
|
|
|
module_init(sensors_nct6683_init); |
|
module_exit(sensors_nct6683_exit);
|
|
|