mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-27 11:19:37 +00:00
![Guenter Roeck](/assets/img/avatar_default.png)
Mitac microcode differs from Intel microcode. One key difference is that pwm values can be written. Detect vendor from customer ID field and no longer use DMI data to identify which microcode is running on the chip. Signed-off-by: Guenter Roeck <linux@roeck-us.net>
1503 lines
37 KiB
C
1503 lines
37 KiB
C
/*
|
|
* nct6683 - Driver for the hardware monitoring functionality of
|
|
* Nuvoton NCT6683D eSIO
|
|
*
|
|
* Copyright (C) 2013 Guenter Roeck <linux@roeck-us.net>
|
|
*
|
|
* Derived from nct6775 driver
|
|
* Copyright (C) 2012, 2013 Guenter Roeck <linux@roeck-us.net>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* Supports the following chips:
|
|
*
|
|
* Chip #vin #fan #pwm #temp chip ID
|
|
* nct6683d 21(1) 16 8 32(1) 0xc730
|
|
*
|
|
* Notes:
|
|
* (1) Total number of vin and temp inputs is 32.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/acpi.h>
|
|
#include <linux/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 };
|
|
|
|
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",
|
|
};
|
|
|
|
static const char * const nct6683_chip_names[] = {
|
|
"NCT6683D",
|
|
};
|
|
|
|
#define DRVNAME "nct6683"
|
|
|
|
/*
|
|
* Super-I/O constants and functions
|
|
*/
|
|
|
|
#define NCT6683_LD_ACPI 0x0a
|
|
#define NCT6683_LD_HWM 0x0b
|
|
#define NCT6683_LD_VID 0x0d
|
|
|
|
#define SIO_REG_LDSEL 0x07 /* Logical device select */
|
|
#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */
|
|
#define SIO_REG_ENABLE 0x30 /* Logical device enable */
|
|
#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */
|
|
|
|
#define SIO_NCT6681_ID 0xb270 /* for later */
|
|
#define SIO_NCT6683_ID 0xc730
|
|
#define SIO_ID_MASK 0xFFF0
|
|
|
|
static inline void
|
|
superio_outb(int ioreg, int reg, int val)
|
|
{
|
|
outb(reg, ioreg);
|
|
outb(val, ioreg + 1);
|
|
}
|
|
|
|
static inline int
|
|
superio_inb(int ioreg, int reg)
|
|
{
|
|
outb(reg, ioreg);
|
|
return inb(ioreg + 1);
|
|
}
|
|
|
|
static inline void
|
|
superio_select(int ioreg, int ld)
|
|
{
|
|
outb(SIO_REG_LDSEL, ioreg);
|
|
outb(ld, ioreg + 1);
|
|
}
|
|
|
|
static inline int
|
|
superio_enter(int ioreg)
|
|
{
|
|
/*
|
|
* Try to reserve <ioreg> and <ioreg + 1> for exclusive access.
|
|
*/
|
|
if (!request_muxed_region(ioreg, 2, DRVNAME))
|
|
return -EBUSY;
|
|
|
|
outb(0x87, ioreg);
|
|
outb(0x87, ioreg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void
|
|
superio_exit(int ioreg)
|
|
{
|
|
outb(0xaa, ioreg);
|
|
outb(0x02, ioreg);
|
|
outb(0x02, ioreg + 1);
|
|
release_region(ioreg, 2);
|
|
}
|
|
|
|
/*
|
|
* ISA constants
|
|
*/
|
|
|
|
#define IOREGION_ALIGNMENT (~7)
|
|
#define IOREGION_OFFSET 4 /* Use EC port 1 */
|
|
#define IOREGION_LENGTH 4
|
|
|
|
#define EC_PAGE_REG 0
|
|
#define EC_INDEX_REG 1
|
|
#define EC_DATA_REG 2
|
|
#define EC_EVENT_REG 3
|
|
|
|
/* Common and NCT6683 specific data */
|
|
|
|
#define NCT6683_NUM_REG_MON 32
|
|
#define NCT6683_NUM_REG_FAN 16
|
|
#define NCT6683_NUM_REG_PWM 8
|
|
|
|
#define NCT6683_REG_MON(x) (0x100 + (x) * 2)
|
|
#define NCT6683_REG_FAN_RPM(x) (0x140 + (x) * 2)
|
|
#define NCT6683_REG_PWM(x) (0x160 + (x))
|
|
#define NCT6683_REG_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_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_kzalloc(dev, sizeof(*attrs) * (repeat * count + 1),
|
|
GFP_KERNEL);
|
|
if (attrs == NULL)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
su = devm_kzalloc(dev, sizeof(*su) * repeat * count,
|
|
GFP_KERNEL);
|
|
if (su == NULL)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
group->attrs = attrs;
|
|
group->is_visible = tg->is_visible;
|
|
|
|
for (i = 0; i < repeat; i++) {
|
|
t = tg->templates;
|
|
for (j = 0; *t != NULL; j++) {
|
|
snprintf(su->name, sizeof(su->name),
|
|
(*t)->dev_attr.attr.name, tg->base + i);
|
|
if ((*t)->s2) {
|
|
a2 = &su->u.a2;
|
|
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 inline u16 in_to_reg(u32 val, u8 src)
|
|
{
|
|
int scale = 16;
|
|
|
|
if (src == MON_SRC_VCC || src == MON_SRC_VSB || src == MON_SRC_AVSB ||
|
|
src == MON_SRC_VBAT)
|
|
scale <<= 1;
|
|
|
|
return clamp_val(DIV_ROUND_CLOSEST(val, scale), 0, 127);
|
|
}
|
|
|
|
static u16 nct6683_read(struct nct6683_data *data, u16 reg)
|
|
{
|
|
int res;
|
|
|
|
outb_p(0xff, data->addr + EC_PAGE_REG); /* unlock */
|
|
outb_p(reg >> 8, data->addr + EC_PAGE_REG);
|
|
outb_p(reg & 0xff, data->addr + EC_INDEX_REG);
|
|
res = inb_p(data->addr + EC_DATA_REG);
|
|
return res;
|
|
}
|
|
|
|
static u16 nct6683_read16(struct nct6683_data *data, u16 reg)
|
|
{
|
|
return (nct6683_read(data, reg) << 8) | nct6683_read(data, reg + 1);
|
|
}
|
|
|
|
static void nct6683_write(struct nct6683_data *data, u16 reg, u16 value)
|
|
{
|
|
outb_p(0xff, data->addr + EC_PAGE_REG); /* unlock */
|
|
outb_p(reg >> 8, data->addr + EC_PAGE_REG);
|
|
outb_p(reg & 0xff, data->addr + EC_INDEX_REG);
|
|
outb_p(value & 0xff, data->addr + EC_DATA_REG);
|
|
}
|
|
|
|
static int get_in_reg(struct nct6683_data *data, int nr, int index)
|
|
{
|
|
int ch = data->in_index[index];
|
|
int reg = -EINVAL;
|
|
|
|
switch (nr) {
|
|
case 0:
|
|
reg = NCT6683_REG_MON(ch);
|
|
break;
|
|
case 1:
|
|
if (data->customer_id != NCT6683_CUSTOMER_ID_INTEL)
|
|
reg = NCT6683_REG_MON_LOW(ch);
|
|
break;
|
|
case 2:
|
|
if (data->customer_id != NCT6683_CUSTOMER_ID_INTEL)
|
|
reg = NCT6683_REG_MON_HIGH(ch);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return reg;
|
|
}
|
|
|
|
static int get_temp_reg(struct nct6683_data *data, int nr, int index)
|
|
{
|
|
int ch = data->temp_index[index];
|
|
int reg = -EINVAL;
|
|
|
|
switch (data->customer_id) {
|
|
case NCT6683_CUSTOMER_ID_INTEL:
|
|
switch (nr) {
|
|
default:
|
|
case 1: /* max */
|
|
reg = NCT6683_REG_INTEL_TEMP_MAX(ch);
|
|
break;
|
|
case 3: /* crit */
|
|
reg = NCT6683_REG_INTEL_TEMP_CRIT(ch);
|
|
break;
|
|
}
|
|
break;
|
|
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 = container_of(kobj, struct device, kobj);
|
|
struct nct6683_data *data = dev_get_drvdata(dev);
|
|
int nr = index % 4; /* attribute */
|
|
|
|
/*
|
|
* Voltage limits exist for Intel boards,
|
|
* but register location and encoding is unknown
|
|
*/
|
|
if ((nr == 2 || nr == 3) &&
|
|
data->customer_id == NCT6683_CUSTOMER_ID_INTEL)
|
|
return 0;
|
|
|
|
return attr->mode;
|
|
}
|
|
|
|
SENSOR_TEMPLATE(in_label, "in%d_label", S_IRUGO, show_in_label, NULL, 0);
|
|
SENSOR_TEMPLATE_2(in_input, "in%d_input", S_IRUGO, show_in_reg, NULL, 0, 0);
|
|
SENSOR_TEMPLATE_2(in_min, "in%d_min", S_IRUGO, show_in_reg, NULL, 0, 1);
|
|
SENSOR_TEMPLATE_2(in_max, "in%d_max", S_IRUGO, show_in_reg, NULL, 0, 2);
|
|
|
|
static struct sensor_device_template *nct6683_attributes_in_template[] = {
|
|
&sensor_dev_template_in_label,
|
|
&sensor_dev_template_in_input,
|
|
&sensor_dev_template_in_min,
|
|
&sensor_dev_template_in_max,
|
|
NULL
|
|
};
|
|
|
|
static 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 = container_of(kobj, struct device, kobj);
|
|
struct nct6683_data *data = dev_get_drvdata(dev);
|
|
int fan = index / 3; /* fan index */
|
|
int nr = index % 3; /* attribute index */
|
|
|
|
if (!(data->have_fan & (1 << fan)))
|
|
return 0;
|
|
|
|
/*
|
|
* Intel may have minimum fan speed limits,
|
|
* but register location and encoding are unknown.
|
|
*/
|
|
if (nr == 2 && data->customer_id == NCT6683_CUSTOMER_ID_INTEL)
|
|
return 0;
|
|
|
|
return attr->mode;
|
|
}
|
|
|
|
SENSOR_TEMPLATE(fan_input, "fan%d_input", S_IRUGO, show_fan, NULL, 0);
|
|
SENSOR_TEMPLATE(fan_pulses, "fan%d_pulses", S_IRUGO, show_fan_pulses, NULL, 0);
|
|
SENSOR_TEMPLATE(fan_min, "fan%d_min", S_IRUGO, show_fan_min, NULL, 0);
|
|
|
|
/*
|
|
* nct6683_fan_is_visible uses the index into the following array
|
|
* to determine if attributes should be created or not.
|
|
* Any change in order or content must be matched.
|
|
*/
|
|
static struct sensor_device_template *nct6683_attributes_fan_template[] = {
|
|
&sensor_dev_template_fan_input,
|
|
&sensor_dev_template_fan_pulses,
|
|
&sensor_dev_template_fan_min,
|
|
NULL
|
|
};
|
|
|
|
static 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 = container_of(kobj, struct device, kobj);
|
|
struct nct6683_data *data = dev_get_drvdata(dev);
|
|
int temp = index / 7; /* temp index */
|
|
int nr = index % 7; /* attribute index */
|
|
|
|
/*
|
|
* Intel does not have low temperature limits or temperature hysteresis
|
|
* registers, or at least register location and encoding is unknown.
|
|
*/
|
|
if ((nr == 2 || nr == 4) &&
|
|
data->customer_id == NCT6683_CUSTOMER_ID_INTEL)
|
|
return 0;
|
|
|
|
if (nr == 6 && get_temp_type(data->temp_src[temp]) == 0)
|
|
return 0; /* type */
|
|
|
|
return attr->mode;
|
|
}
|
|
|
|
SENSOR_TEMPLATE(temp_input, "temp%d_input", S_IRUGO, show_temp16, NULL, 0);
|
|
SENSOR_TEMPLATE(temp_label, "temp%d_label", S_IRUGO, show_temp_label, NULL, 0);
|
|
SENSOR_TEMPLATE_2(temp_min, "temp%d_min", S_IRUGO, show_temp8, NULL, 0, 0);
|
|
SENSOR_TEMPLATE_2(temp_max, "temp%d_max", S_IRUGO, show_temp8, NULL, 0, 1);
|
|
SENSOR_TEMPLATE(temp_max_hyst, "temp%d_max_hyst", S_IRUGO, show_temp_hyst, NULL,
|
|
0);
|
|
SENSOR_TEMPLATE_2(temp_crit, "temp%d_crit", S_IRUGO, show_temp8, NULL, 0, 3);
|
|
SENSOR_TEMPLATE(temp_type, "temp%d_type", S_IRUGO, show_temp_type, NULL, 0);
|
|
|
|
/*
|
|
* nct6683_temp_is_visible uses the index into the following array
|
|
* to determine if attributes should be created or not.
|
|
* Any change in order or content must be matched.
|
|
*/
|
|
static struct sensor_device_template *nct6683_attributes_temp_template[] = {
|
|
&sensor_dev_template_temp_input,
|
|
&sensor_dev_template_temp_label,
|
|
&sensor_dev_template_temp_min, /* 2 */
|
|
&sensor_dev_template_temp_max, /* 3 */
|
|
&sensor_dev_template_temp_max_hyst, /* 4 */
|
|
&sensor_dev_template_temp_crit, /* 5 */
|
|
&sensor_dev_template_temp_type, /* 6 */
|
|
NULL
|
|
};
|
|
|
|
static 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 = container_of(kobj, struct device, kobj);
|
|
struct nct6683_data *data = dev_get_drvdata(dev);
|
|
int pwm = index; /* pwm index */
|
|
|
|
if (!(data->have_pwm & (1 << pwm)))
|
|
return 0;
|
|
|
|
/* 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
|
|
show_global_beep(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct nct6683_data *data = dev_get_drvdata(dev);
|
|
int ret;
|
|
u8 reg;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
|
|
ret = superio_enter(data->sioreg);
|
|
if (ret)
|
|
goto error;
|
|
superio_select(data->sioreg, NCT6683_LD_HWM);
|
|
reg = superio_inb(data->sioreg, NCT6683_REG_CR_BEEP);
|
|
superio_exit(data->sioreg);
|
|
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
return sprintf(buf, "%u\n", !!(reg & NCT6683_CR_BEEP_MASK));
|
|
|
|
error:
|
|
mutex_unlock(&data->update_lock);
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t
|
|
store_global_beep(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct nct6683_data *data = dev_get_drvdata(dev);
|
|
unsigned long val;
|
|
u8 reg;
|
|
int ret;
|
|
|
|
if (kstrtoul(buf, 10, &val) || (val != 0 && val != 1))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
|
|
ret = superio_enter(data->sioreg);
|
|
if (ret) {
|
|
count = ret;
|
|
goto error;
|
|
}
|
|
|
|
superio_select(data->sioreg, NCT6683_LD_HWM);
|
|
reg = superio_inb(data->sioreg, NCT6683_REG_CR_BEEP);
|
|
if (val)
|
|
reg |= NCT6683_CR_BEEP_MASK;
|
|
else
|
|
reg &= ~NCT6683_CR_BEEP_MASK;
|
|
superio_outb(data->sioreg, NCT6683_REG_CR_BEEP, reg);
|
|
superio_exit(data->sioreg);
|
|
error:
|
|
mutex_unlock(&data->update_lock);
|
|
return count;
|
|
}
|
|
|
|
/* Case open detection */
|
|
|
|
static ssize_t
|
|
show_caseopen(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct nct6683_data *data = dev_get_drvdata(dev);
|
|
int ret;
|
|
u8 reg;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
|
|
ret = superio_enter(data->sioreg);
|
|
if (ret)
|
|
goto error;
|
|
superio_select(data->sioreg, NCT6683_LD_ACPI);
|
|
reg = superio_inb(data->sioreg, NCT6683_REG_CR_CASEOPEN);
|
|
superio_exit(data->sioreg);
|
|
|
|
mutex_unlock(&data->update_lock);
|
|
|
|
return sprintf(buf, "%u\n", !(reg & NCT6683_CR_CASEOPEN_MASK));
|
|
|
|
error:
|
|
mutex_unlock(&data->update_lock);
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t
|
|
clear_caseopen(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct nct6683_data *data = dev_get_drvdata(dev);
|
|
unsigned long val;
|
|
u8 reg;
|
|
int ret;
|
|
|
|
if (kstrtoul(buf, 10, &val) || val != 0)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&data->update_lock);
|
|
|
|
/*
|
|
* Use CR registers to clear caseopen status.
|
|
* Caseopen is activ low, clear by writing 1 into the register.
|
|
*/
|
|
|
|
ret = superio_enter(data->sioreg);
|
|
if (ret) {
|
|
count = ret;
|
|
goto error;
|
|
}
|
|
|
|
superio_select(data->sioreg, NCT6683_LD_ACPI);
|
|
reg = superio_inb(data->sioreg, NCT6683_REG_CR_CASEOPEN);
|
|
reg |= NCT6683_CR_CASEOPEN_MASK;
|
|
superio_outb(data->sioreg, NCT6683_REG_CR_CASEOPEN, reg);
|
|
reg &= ~NCT6683_CR_CASEOPEN_MASK;
|
|
superio_outb(data->sioreg, NCT6683_REG_CR_CASEOPEN, reg);
|
|
superio_exit(data->sioreg);
|
|
|
|
data->valid = false; /* Force cache refresh */
|
|
error:
|
|
mutex_unlock(&data->update_lock);
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(intrusion0_alarm, S_IWUSR | S_IRUGO, show_caseopen,
|
|
clear_caseopen);
|
|
static DEVICE_ATTR(beep_enable, S_IWUSR | S_IRUGO, show_global_beep,
|
|
store_global_beep);
|
|
|
|
static struct attribute *nct6683_attributes_other[] = {
|
|
&dev_attr_intrusion0_alarm.attr,
|
|
&dev_attr_beep_enable.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group nct6683_group_other = {
|
|
.attrs = nct6683_attributes_other,
|
|
};
|
|
|
|
/* Get the monitoring functions started */
|
|
static inline void nct6683_init_device(struct nct6683_data *data)
|
|
{
|
|
u8 tmp;
|
|
|
|
/* Start hardware monitoring if needed */
|
|
tmp = nct6683_read(data, NCT6683_HWM_CFG);
|
|
if (!(tmp & 0x80))
|
|
nct6683_write(data, NCT6683_HWM_CFG, tmp | 0x80);
|
|
}
|
|
|
|
/*
|
|
* There are a total of 24 fan inputs. Each can be configured as input
|
|
* or as output. A maximum of 16 inputs and 8 outputs is configurable.
|
|
*/
|
|
static void
|
|
nct6683_setup_fans(struct nct6683_data *data)
|
|
{
|
|
int i;
|
|
u8 reg;
|
|
|
|
for (i = 0; i < NCT6683_NUM_REG_FAN; i++) {
|
|
reg = nct6683_read(data, NCT6683_REG_FANIN_CFG(i));
|
|
if (reg & 0x80)
|
|
data->have_fan |= 1 << i;
|
|
data->fanin_cfg[i] = reg;
|
|
}
|
|
for (i = 0; i < NCT6683_NUM_REG_PWM; i++) {
|
|
reg = nct6683_read(data, NCT6683_REG_FANOUT_CFG(i));
|
|
if (reg & 0x80)
|
|
data->have_pwm |= 1 << i;
|
|
data->fanout_cfg[i] = reg;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Translation from monitoring register to temperature and voltage attributes
|
|
* ==========================================================================
|
|
*
|
|
* There are a total of 32 monitoring registers. Each can be assigned to either
|
|
* a temperature or voltage monitoring source.
|
|
* NCT6683_REG_MON_CFG(x) defines assignment for each monitoring source.
|
|
*
|
|
* Temperature and voltage attribute mapping is determined by walking through
|
|
* the NCT6683_REG_MON_CFG registers. If the assigned source is
|
|
* a temperature, temp_index[n] is set to the monitor register index, and
|
|
* temp_src[n] is set to the temperature source. If the assigned source is
|
|
* a voltage, the respective values are stored in in_index[] and in_src[],
|
|
* respectively.
|
|
*/
|
|
|
|
static void nct6683_setup_sensors(struct nct6683_data *data)
|
|
{
|
|
u8 reg;
|
|
int i;
|
|
|
|
data->temp_num = 0;
|
|
data->in_num = 0;
|
|
for (i = 0; i < NCT6683_NUM_REG_MON; i++) {
|
|
reg = nct6683_read(data, NCT6683_REG_MON_CFG(i)) & 0x7f;
|
|
/* Ignore invalid assignments */
|
|
if (reg >= NUM_MON_LABELS)
|
|
continue;
|
|
/* Skip if disabled or reserved */
|
|
if (nct6683_mon_label[reg] == NULL)
|
|
continue;
|
|
if (reg < MON_VOLTAGE_START) {
|
|
data->temp_index[data->temp_num] = i;
|
|
data->temp_src[data->temp_num] = reg;
|
|
data->temp_num++;
|
|
} else {
|
|
data->in_index[data->in_num] = i;
|
|
data->in_src[data->in_num] = reg;
|
|
data->in_num++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int nct6683_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct nct6683_sio_data *sio_data = dev->platform_data;
|
|
struct attribute_group *group;
|
|
struct nct6683_data *data;
|
|
struct device *hwmon_dev;
|
|
struct resource *res;
|
|
int groups = 0;
|
|
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;
|
|
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;
|
|
default:
|
|
if (val != 0xffff)
|
|
pr_debug("unsupported chip ID: 0x%04x\n", val);
|
|
goto fail;
|
|
}
|
|
|
|
/* We have a known chip, find the HWM I/O address */
|
|
superio_select(sioaddr, NCT6683_LD_HWM);
|
|
val = (superio_inb(sioaddr, SIO_REG_ADDR) << 8)
|
|
| superio_inb(sioaddr, SIO_REG_ADDR + 1);
|
|
addr = val & IOREGION_ALIGNMENT;
|
|
if (addr == 0) {
|
|
pr_err("EC base I/O port unconfigured\n");
|
|
goto fail;
|
|
}
|
|
|
|
/* Activate logical device if needed */
|
|
val = superio_inb(sioaddr, SIO_REG_ENABLE);
|
|
if (!(val & 0x01)) {
|
|
pr_err("EC is disabled\n");
|
|
goto fail;
|
|
}
|
|
|
|
superio_exit(sioaddr);
|
|
pr_info("Found %s or compatible chip at %#x:%#x\n",
|
|
nct6683_chip_names[sio_data->kind], sioaddr, addr);
|
|
sio_data->sioreg = sioaddr;
|
|
|
|
return addr;
|
|
|
|
fail:
|
|
superio_exit(sioaddr);
|
|
return -ENODEV;
|
|
}
|
|
|
|
/*
|
|
* when Super-I/O functions move to a separate file, the Super-I/O
|
|
* bus will manage the lifetime of the device and this module will only keep
|
|
* track of the nct6683 driver. But since we use platform_device_alloc(), we
|
|
* must keep track of the device
|
|
*/
|
|
static struct platform_device *pdev[2];
|
|
|
|
static int __init sensors_nct6683_init(void)
|
|
{
|
|
struct nct6683_sio_data sio_data;
|
|
int sioaddr[2] = { 0x2e, 0x4e };
|
|
struct resource res;
|
|
bool found = false;
|
|
int address;
|
|
int i, err;
|
|
|
|
err = platform_driver_register(&nct6683_driver);
|
|
if (err)
|
|
return err;
|
|
|
|
/*
|
|
* initialize sio_data->kind and sio_data->sioreg.
|
|
*
|
|
* when Super-I/O functions move to a separate file, the Super-I/O
|
|
* driver will probe 0x2e and 0x4e and auto-detect the presence of a
|
|
* nct6683 hardware monitor, and call probe()
|
|
*/
|
|
for (i = 0; i < ARRAY_SIZE(pdev); i++) {
|
|
address = nct6683_find(sioaddr[i], &sio_data);
|
|
if (address <= 0)
|
|
continue;
|
|
|
|
found = true;
|
|
|
|
pdev[i] = platform_device_alloc(DRVNAME, address);
|
|
if (!pdev[i]) {
|
|
err = -ENOMEM;
|
|
goto exit_device_unregister;
|
|
}
|
|
|
|
err = platform_device_add_data(pdev[i], &sio_data,
|
|
sizeof(struct nct6683_sio_data));
|
|
if (err)
|
|
goto exit_device_put;
|
|
|
|
memset(&res, 0, sizeof(res));
|
|
res.name = DRVNAME;
|
|
res.start = address + IOREGION_OFFSET;
|
|
res.end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1;
|
|
res.flags = IORESOURCE_IO;
|
|
|
|
err = acpi_check_resource_conflict(&res);
|
|
if (err) {
|
|
platform_device_put(pdev[i]);
|
|
pdev[i] = NULL;
|
|
continue;
|
|
}
|
|
|
|
err = platform_device_add_resources(pdev[i], &res, 1);
|
|
if (err)
|
|
goto exit_device_put;
|
|
|
|
/* platform_device_add calls probe() */
|
|
err = platform_device_add(pdev[i]);
|
|
if (err)
|
|
goto exit_device_put;
|
|
}
|
|
if (!found) {
|
|
err = -ENODEV;
|
|
goto exit_unregister;
|
|
}
|
|
|
|
return 0;
|
|
|
|
exit_device_put:
|
|
platform_device_put(pdev[i]);
|
|
exit_device_unregister:
|
|
while (--i >= 0) {
|
|
if (pdev[i])
|
|
platform_device_unregister(pdev[i]);
|
|
}
|
|
exit_unregister:
|
|
platform_driver_unregister(&nct6683_driver);
|
|
return err;
|
|
}
|
|
|
|
static void __exit sensors_nct6683_exit(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(pdev); i++) {
|
|
if (pdev[i])
|
|
platform_device_unregister(pdev[i]);
|
|
}
|
|
platform_driver_unregister(&nct6683_driver);
|
|
}
|
|
|
|
MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>");
|
|
MODULE_DESCRIPTION("NCT6683D driver");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
module_init(sensors_nct6683_init);
|
|
module_exit(sensors_nct6683_exit);
|