mirror of https://gitee.com/openkylin/linux.git
hwmon: (nct6775) Add support for fan speed attributes
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
This commit is contained in:
parent
aa136e5dad
commit
1c65dc365e
|
@ -53,8 +53,9 @@ triggered if the rotation speed has dropped below a programmable limit. On
|
||||||
NCT6775F, fan readings can be divided by a programmable divider (1, 2, 4, 8,
|
NCT6775F, fan readings can be divided by a programmable divider (1, 2, 4, 8,
|
||||||
16, 32, 64 or 128) to give the readings more range or accuracy; the other chips
|
16, 32, 64 or 128) to give the readings more range or accuracy; the other chips
|
||||||
do not have a fan speed divider. The driver sets the most suitable fan divisor
|
do not have a fan speed divider. The driver sets the most suitable fan divisor
|
||||||
itself; specifically, it doubles the divider value each time a fan speed reading
|
itself; specifically, it increases the divider value each time a fan speed
|
||||||
returns an invalid value. Some fans might not be present because they share pins
|
reading returns an invalid value, and it reduces it if the fan speed reading
|
||||||
|
is lower than optimal. Some fans might not be present because they share pins
|
||||||
with other functions.
|
with other functions.
|
||||||
|
|
||||||
Voltage sensors (also known as IN sensors) report their values in millivolts.
|
Voltage sensors (also known as IN sensors) report their values in millivolts.
|
||||||
|
|
|
@ -180,6 +180,9 @@ static const u16 NCT6775_REG_IN[] = {
|
||||||
#define NCT6775_REG_VBAT 0x5D
|
#define NCT6775_REG_VBAT 0x5D
|
||||||
#define NCT6775_REG_DIODE 0x5E
|
#define NCT6775_REG_DIODE 0x5E
|
||||||
|
|
||||||
|
#define NCT6775_REG_FANDIV1 0x506
|
||||||
|
#define NCT6775_REG_FANDIV2 0x507
|
||||||
|
|
||||||
static const u16 NCT6775_REG_ALARM[NUM_REG_ALARM] = { 0x459, 0x45A, 0x45B };
|
static const u16 NCT6775_REG_ALARM[NUM_REG_ALARM] = { 0x459, 0x45A, 0x45B };
|
||||||
|
|
||||||
/* 0..15 voltages, 16..23 fans, 24..31 temperatures */
|
/* 0..15 voltages, 16..23 fans, 24..31 temperatures */
|
||||||
|
@ -193,12 +196,16 @@ static const s8 NCT6775_ALARM_BITS[] = {
|
||||||
4, 5, 13, -1, -1, -1, /* temp1..temp6 */
|
4, 5, 13, -1, -1, -1, /* temp1..temp6 */
|
||||||
12, -1 }; /* intrusion0, intrusion1 */
|
12, -1 }; /* intrusion0, intrusion1 */
|
||||||
|
|
||||||
|
#define FAN_ALARM_BASE 16
|
||||||
#define TEMP_ALARM_BASE 24
|
#define TEMP_ALARM_BASE 24
|
||||||
#define INTRUSION_ALARM_BASE 30
|
#define INTRUSION_ALARM_BASE 30
|
||||||
|
|
||||||
static const u8 NCT6775_REG_CR_CASEOPEN_CLR[] = { 0xe6, 0xee };
|
static const u8 NCT6775_REG_CR_CASEOPEN_CLR[] = { 0xe6, 0xee };
|
||||||
static const u8 NCT6775_CR_CASEOPEN_CLR_MASK[] = { 0x20, 0x01 };
|
static const u8 NCT6775_CR_CASEOPEN_CLR_MASK[] = { 0x20, 0x01 };
|
||||||
|
|
||||||
|
static const u16 NCT6775_REG_FAN[] = { 0x630, 0x632, 0x634, 0x636, 0x638 };
|
||||||
|
static const u16 NCT6775_REG_FAN_MIN[] = { 0x3b, 0x3c, 0x3d };
|
||||||
|
|
||||||
static const u16 NCT6775_REG_TEMP[] = {
|
static const u16 NCT6775_REG_TEMP[] = {
|
||||||
0x27, 0x150, 0x250, 0x62b, 0x62c, 0x62d };
|
0x27, 0x150, 0x250, 0x62b, 0x62c, 0x62d };
|
||||||
|
|
||||||
|
@ -256,6 +263,8 @@ static const s8 NCT6776_ALARM_BITS[] = {
|
||||||
4, 5, 13, -1, -1, -1, /* temp1..temp6 */
|
4, 5, 13, -1, -1, -1, /* temp1..temp6 */
|
||||||
12, 9 }; /* intrusion0, intrusion1 */
|
12, 9 }; /* intrusion0, intrusion1 */
|
||||||
|
|
||||||
|
static const u16 NCT6776_REG_FAN_MIN[] = { 0x63a, 0x63c, 0x63e, 0x640, 0x642 };
|
||||||
|
|
||||||
static const u16 NCT6776_REG_TEMP_CONFIG[ARRAY_SIZE(NCT6775_REG_TEMP)] = {
|
static const u16 NCT6776_REG_TEMP_CONFIG[ARRAY_SIZE(NCT6775_REG_TEMP)] = {
|
||||||
0x18, 0x152, 0x252, 0x628, 0x629, 0x62A };
|
0x18, 0x152, 0x252, 0x628, 0x629, 0x62A };
|
||||||
|
|
||||||
|
@ -309,6 +318,8 @@ static const s8 NCT6779_ALARM_BITS[] = {
|
||||||
4, 5, 13, -1, -1, -1, /* temp1..temp6 */
|
4, 5, 13, -1, -1, -1, /* temp1..temp6 */
|
||||||
12, 9 }; /* intrusion0, intrusion1 */
|
12, 9 }; /* intrusion0, intrusion1 */
|
||||||
|
|
||||||
|
static const u16 NCT6779_REG_FAN[] = { 0x4b0, 0x4b2, 0x4b4, 0x4b6, 0x4b8 };
|
||||||
|
|
||||||
static const u16 NCT6779_REG_TEMP[] = { 0x27, 0x150 };
|
static const u16 NCT6779_REG_TEMP[] = { 0x27, 0x150 };
|
||||||
static const u16 NCT6779_REG_TEMP_CONFIG[ARRAY_SIZE(NCT6779_REG_TEMP)] = {
|
static const u16 NCT6779_REG_TEMP_CONFIG[ARRAY_SIZE(NCT6779_REG_TEMP)] = {
|
||||||
0x18, 0x152 };
|
0x18, 0x152 };
|
||||||
|
@ -363,6 +374,44 @@ static const u16 NCT6779_REG_TEMP_CRIT[ARRAY_SIZE(nct6779_temp_label) - 1]
|
||||||
* Conversions
|
* Conversions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
static unsigned int fan_from_reg8(u16 reg, unsigned int divreg)
|
||||||
|
{
|
||||||
|
if (reg == 0 || reg == 255)
|
||||||
|
return 0;
|
||||||
|
return 1350000U / (reg << divreg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int fan_from_reg13(u16 reg, unsigned int divreg)
|
||||||
|
{
|
||||||
|
if ((reg & 0xff1f) == 0xff1f)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
reg = (reg & 0x1f) | ((reg & 0xff00) >> 3);
|
||||||
|
|
||||||
|
if (reg == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return 1350000U / reg;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int fan_from_reg16(u16 reg, unsigned int divreg)
|
||||||
|
{
|
||||||
|
if (reg == 0 || reg == 0xffff)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Even though the registers are 16 bit wide, the fan divisor
|
||||||
|
* still applies.
|
||||||
|
*/
|
||||||
|
return 1350000U / (reg << divreg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline unsigned int
|
||||||
|
div_from_reg(u8 reg)
|
||||||
|
{
|
||||||
|
return 1 << reg;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Some of the voltage inputs have internal scaling, the tables below
|
* Some of the voltage inputs have internal scaling, the tables below
|
||||||
* contain 8 (the ADC LSB in mV) * scaling factor * 100
|
* contain 8 (the ADC LSB in mV) * scaling factor * 100
|
||||||
|
@ -411,12 +460,17 @@ struct nct6775_data {
|
||||||
const u16 *REG_VIN;
|
const u16 *REG_VIN;
|
||||||
const u16 *REG_IN_MINMAX[2];
|
const u16 *REG_IN_MINMAX[2];
|
||||||
|
|
||||||
const u16 *REG_TEMP_SOURCE; /* temp register sources */
|
const u16 *REG_FAN;
|
||||||
|
const u16 *REG_FAN_MIN;
|
||||||
|
|
||||||
|
const u16 *REG_TEMP_SOURCE; /* temp register sources */
|
||||||
const u16 *REG_TEMP_OFFSET;
|
const u16 *REG_TEMP_OFFSET;
|
||||||
|
|
||||||
const u16 *REG_ALARM;
|
const u16 *REG_ALARM;
|
||||||
|
|
||||||
|
unsigned int (*fan_from_reg)(u16 reg, unsigned int divreg);
|
||||||
|
unsigned int (*fan_from_reg_min)(u16 reg, unsigned int divreg);
|
||||||
|
|
||||||
struct mutex update_lock;
|
struct mutex update_lock;
|
||||||
bool valid; /* true if following fields are valid */
|
bool valid; /* true if following fields are valid */
|
||||||
unsigned long last_updated; /* In jiffies */
|
unsigned long last_updated; /* In jiffies */
|
||||||
|
@ -425,6 +479,12 @@ struct nct6775_data {
|
||||||
u8 bank; /* current register bank */
|
u8 bank; /* current register bank */
|
||||||
u8 in_num; /* number of in inputs we have */
|
u8 in_num; /* number of in inputs we have */
|
||||||
u8 in[15][3]; /* [0]=in, [1]=in_max, [2]=in_min */
|
u8 in[15][3]; /* [0]=in, [1]=in_max, [2]=in_min */
|
||||||
|
unsigned int rpm[5];
|
||||||
|
u16 fan_min[5];
|
||||||
|
u8 fan_div[5];
|
||||||
|
u8 has_fan; /* some fan inputs can be disabled */
|
||||||
|
u8 has_fan_min; /* some fans don't have min register */
|
||||||
|
bool has_fan_div;
|
||||||
|
|
||||||
u8 temp_fixed_num; /* 3 or 6 */
|
u8 temp_fixed_num; /* 3 or 6 */
|
||||||
u8 temp_type[NUM_TEMP_FIXED];
|
u8 temp_type[NUM_TEMP_FIXED];
|
||||||
|
@ -556,6 +616,153 @@ static int nct6775_write_temp(struct nct6775_data *data, u16 reg, u16 value)
|
||||||
return nct6775_write_value(data, reg, value);
|
return nct6775_write_value(data, reg, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* This function assumes that the caller holds data->update_lock */
|
||||||
|
static void nct6775_write_fan_div(struct nct6775_data *data, int nr)
|
||||||
|
{
|
||||||
|
u8 reg;
|
||||||
|
|
||||||
|
switch (nr) {
|
||||||
|
case 0:
|
||||||
|
reg = (nct6775_read_value(data, NCT6775_REG_FANDIV1) & 0x70)
|
||||||
|
| (data->fan_div[0] & 0x7);
|
||||||
|
nct6775_write_value(data, NCT6775_REG_FANDIV1, reg);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
reg = (nct6775_read_value(data, NCT6775_REG_FANDIV1) & 0x7)
|
||||||
|
| ((data->fan_div[1] << 4) & 0x70);
|
||||||
|
nct6775_write_value(data, NCT6775_REG_FANDIV1, reg);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
reg = (nct6775_read_value(data, NCT6775_REG_FANDIV2) & 0x70)
|
||||||
|
| (data->fan_div[2] & 0x7);
|
||||||
|
nct6775_write_value(data, NCT6775_REG_FANDIV2, reg);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
reg = (nct6775_read_value(data, NCT6775_REG_FANDIV2) & 0x7)
|
||||||
|
| ((data->fan_div[3] << 4) & 0x70);
|
||||||
|
nct6775_write_value(data, NCT6775_REG_FANDIV2, reg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nct6775_write_fan_div_common(struct nct6775_data *data, int nr)
|
||||||
|
{
|
||||||
|
if (data->kind == nct6775)
|
||||||
|
nct6775_write_fan_div(data, nr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nct6775_update_fan_div(struct nct6775_data *data)
|
||||||
|
{
|
||||||
|
u8 i;
|
||||||
|
|
||||||
|
i = nct6775_read_value(data, NCT6775_REG_FANDIV1);
|
||||||
|
data->fan_div[0] = i & 0x7;
|
||||||
|
data->fan_div[1] = (i & 0x70) >> 4;
|
||||||
|
i = nct6775_read_value(data, NCT6775_REG_FANDIV2);
|
||||||
|
data->fan_div[2] = i & 0x7;
|
||||||
|
if (data->has_fan & (1<<3))
|
||||||
|
data->fan_div[3] = (i & 0x70) >> 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nct6775_update_fan_div_common(struct nct6775_data *data)
|
||||||
|
{
|
||||||
|
if (data->kind == nct6775)
|
||||||
|
nct6775_update_fan_div(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nct6775_init_fan_div(struct nct6775_data *data)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
nct6775_update_fan_div_common(data);
|
||||||
|
/*
|
||||||
|
* For all fans, start with highest divider value if the divider
|
||||||
|
* register is not initialized. This ensures that we get a
|
||||||
|
* reading from the fan count register, even if it is not optimal.
|
||||||
|
* We'll compute a better divider later on.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
if (!(data->has_fan & (1 << i)))
|
||||||
|
continue;
|
||||||
|
if (data->fan_div[i] == 0) {
|
||||||
|
data->fan_div[i] = 7;
|
||||||
|
nct6775_write_fan_div_common(data, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nct6775_init_fan_common(struct device *dev,
|
||||||
|
struct nct6775_data *data)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
u8 reg;
|
||||||
|
|
||||||
|
if (data->has_fan_div)
|
||||||
|
nct6775_init_fan_div(data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If fan_min is not set (0), set it to 0xff to disable it. This
|
||||||
|
* prevents the unnecessary warning when fanX_min is reported as 0.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < 5; i++) {
|
||||||
|
if (data->has_fan_min & (1 << i)) {
|
||||||
|
reg = nct6775_read_value(data, data->REG_FAN_MIN[i]);
|
||||||
|
if (!reg)
|
||||||
|
nct6775_write_value(data, data->REG_FAN_MIN[i],
|
||||||
|
data->has_fan_div ? 0xff
|
||||||
|
: 0xff1f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nct6775_select_fan_div(struct device *dev,
|
||||||
|
struct nct6775_data *data, int nr, u16 reg)
|
||||||
|
{
|
||||||
|
u8 fan_div = data->fan_div[nr];
|
||||||
|
u16 fan_min;
|
||||||
|
|
||||||
|
if (!data->has_fan_div)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we failed to measure the fan speed, or the reported value is not
|
||||||
|
* in the optimal range, and the clock divider can be modified,
|
||||||
|
* let's try that for next time.
|
||||||
|
*/
|
||||||
|
if (reg == 0x00 && fan_div < 0x07)
|
||||||
|
fan_div++;
|
||||||
|
else if (reg != 0x00 && reg < 0x30 && fan_div > 0)
|
||||||
|
fan_div--;
|
||||||
|
|
||||||
|
if (fan_div != data->fan_div[nr]) {
|
||||||
|
dev_dbg(dev, "Modifying fan%d clock divider from %u to %u\n",
|
||||||
|
nr + 1, div_from_reg(data->fan_div[nr]),
|
||||||
|
div_from_reg(fan_div));
|
||||||
|
|
||||||
|
/* Preserve min limit if possible */
|
||||||
|
if (data->has_fan_min & (1 << nr)) {
|
||||||
|
fan_min = data->fan_min[nr];
|
||||||
|
if (fan_div > data->fan_div[nr]) {
|
||||||
|
if (fan_min != 255 && fan_min > 1)
|
||||||
|
fan_min >>= 1;
|
||||||
|
} else {
|
||||||
|
if (fan_min != 255) {
|
||||||
|
fan_min <<= 1;
|
||||||
|
if (fan_min > 254)
|
||||||
|
fan_min = 254;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fan_min != data->fan_min[nr]) {
|
||||||
|
data->fan_min[nr] = fan_min;
|
||||||
|
nct6775_write_value(data, data->REG_FAN_MIN[nr],
|
||||||
|
fan_min);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data->fan_div[nr] = fan_div;
|
||||||
|
nct6775_write_fan_div_common(data, nr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static struct nct6775_data *nct6775_update_device(struct device *dev)
|
static struct nct6775_data *nct6775_update_device(struct device *dev)
|
||||||
{
|
{
|
||||||
struct nct6775_data *data = dev_get_drvdata(dev);
|
struct nct6775_data *data = dev_get_drvdata(dev);
|
||||||
|
@ -565,6 +772,9 @@ static struct nct6775_data *nct6775_update_device(struct device *dev)
|
||||||
|
|
||||||
if (time_after(jiffies, data->last_updated + HZ + HZ/2)
|
if (time_after(jiffies, data->last_updated + HZ + HZ/2)
|
||||||
|| !data->valid) {
|
|| !data->valid) {
|
||||||
|
/* Fan clock dividers */
|
||||||
|
nct6775_update_fan_div_common(data);
|
||||||
|
|
||||||
/* Measured voltages and limits */
|
/* Measured voltages and limits */
|
||||||
for (i = 0; i < data->in_num; i++) {
|
for (i = 0; i < data->in_num; i++) {
|
||||||
if (!(data->have_in & (1 << i)))
|
if (!(data->have_in & (1 << i)))
|
||||||
|
@ -578,6 +788,24 @@ static struct nct6775_data *nct6775_update_device(struct device *dev)
|
||||||
data->REG_IN_MINMAX[1][i]);
|
data->REG_IN_MINMAX[1][i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Measured fan speeds and limits */
|
||||||
|
for (i = 0; i < 5; i++) {
|
||||||
|
u16 reg;
|
||||||
|
|
||||||
|
if (!(data->has_fan & (1 << i)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
reg = nct6775_read_value(data, data->REG_FAN[i]);
|
||||||
|
data->rpm[i] = data->fan_from_reg(reg,
|
||||||
|
data->fan_div[i]);
|
||||||
|
|
||||||
|
if (data->has_fan_min & (1 << i))
|
||||||
|
data->fan_min[i] = nct6775_read_value(data,
|
||||||
|
data->REG_FAN_MIN[i]);
|
||||||
|
|
||||||
|
nct6775_select_fan_div(dev, data, i, reg);
|
||||||
|
}
|
||||||
|
|
||||||
/* Measured temperatures and limits */
|
/* Measured temperatures and limits */
|
||||||
for (i = 0; i < NUM_TEMP; i++) {
|
for (i = 0; i < NUM_TEMP; i++) {
|
||||||
if (!(data->have_temp & (1 << i)))
|
if (!(data->have_temp & (1 << i)))
|
||||||
|
@ -874,6 +1102,166 @@ static const struct attribute_group nct6775_group_in[15] = {
|
||||||
{ .attrs = nct6775_attributes_in[14] },
|
{ .attrs = nct6775_attributes_in[14] },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
show_fan(struct device *dev, struct device_attribute *attr, char *buf)
|
||||||
|
{
|
||||||
|
struct nct6775_data *data = nct6775_update_device(dev);
|
||||||
|
struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
|
||||||
|
int nr = sattr->index;
|
||||||
|
return sprintf(buf, "%d\n", data->rpm[nr]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
show_fan_min(struct device *dev, struct device_attribute *attr, char *buf)
|
||||||
|
{
|
||||||
|
struct nct6775_data *data = nct6775_update_device(dev);
|
||||||
|
struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
|
||||||
|
int nr = sattr->index;
|
||||||
|
return sprintf(buf, "%d\n",
|
||||||
|
data->fan_from_reg_min(data->fan_min[nr],
|
||||||
|
data->fan_div[nr]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
show_fan_div(struct device *dev, struct device_attribute *attr, char *buf)
|
||||||
|
{
|
||||||
|
struct nct6775_data *data = nct6775_update_device(dev);
|
||||||
|
struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
|
||||||
|
int nr = sattr->index;
|
||||||
|
return sprintf(buf, "%u\n", div_from_reg(data->fan_div[nr]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
store_fan_min(struct device *dev, struct device_attribute *attr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct nct6775_data *data = dev_get_drvdata(dev);
|
||||||
|
struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
|
||||||
|
int nr = sattr->index;
|
||||||
|
unsigned long val;
|
||||||
|
int err;
|
||||||
|
unsigned int reg;
|
||||||
|
u8 new_div;
|
||||||
|
|
||||||
|
err = kstrtoul(buf, 10, &val);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
mutex_lock(&data->update_lock);
|
||||||
|
if (!data->has_fan_div) {
|
||||||
|
/* NCT6776F or NCT6779D; we know this is a 13 bit register */
|
||||||
|
if (!val) {
|
||||||
|
val = 0xff1f;
|
||||||
|
} else {
|
||||||
|
if (val > 1350000U)
|
||||||
|
val = 135000U;
|
||||||
|
val = 1350000U / val;
|
||||||
|
val = (val & 0x1f) | ((val << 3) & 0xff00);
|
||||||
|
}
|
||||||
|
data->fan_min[nr] = val;
|
||||||
|
goto write_min; /* Leave fan divider alone */
|
||||||
|
}
|
||||||
|
if (!val) {
|
||||||
|
/* No min limit, alarm disabled */
|
||||||
|
data->fan_min[nr] = 255;
|
||||||
|
new_div = data->fan_div[nr]; /* No change */
|
||||||
|
dev_info(dev, "fan%u low limit and alarm disabled\n", nr + 1);
|
||||||
|
goto write_div;
|
||||||
|
}
|
||||||
|
reg = 1350000U / val;
|
||||||
|
if (reg >= 128 * 255) {
|
||||||
|
/*
|
||||||
|
* Speed below this value cannot possibly be represented,
|
||||||
|
* even with the highest divider (128)
|
||||||
|
*/
|
||||||
|
data->fan_min[nr] = 254;
|
||||||
|
new_div = 7; /* 128 == (1 << 7) */
|
||||||
|
dev_warn(dev,
|
||||||
|
"fan%u low limit %lu below minimum %u, set to minimum\n",
|
||||||
|
nr + 1, val, data->fan_from_reg_min(254, 7));
|
||||||
|
} else if (!reg) {
|
||||||
|
/*
|
||||||
|
* Speed above this value cannot possibly be represented,
|
||||||
|
* even with the lowest divider (1)
|
||||||
|
*/
|
||||||
|
data->fan_min[nr] = 1;
|
||||||
|
new_div = 0; /* 1 == (1 << 0) */
|
||||||
|
dev_warn(dev,
|
||||||
|
"fan%u low limit %lu above maximum %u, set to maximum\n",
|
||||||
|
nr + 1, val, data->fan_from_reg_min(1, 0));
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Automatically pick the best divider, i.e. the one such
|
||||||
|
* that the min limit will correspond to a register value
|
||||||
|
* in the 96..192 range
|
||||||
|
*/
|
||||||
|
new_div = 0;
|
||||||
|
while (reg > 192 && new_div < 7) {
|
||||||
|
reg >>= 1;
|
||||||
|
new_div++;
|
||||||
|
}
|
||||||
|
data->fan_min[nr] = reg;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_div:
|
||||||
|
/*
|
||||||
|
* Write both the fan clock divider (if it changed) and the new
|
||||||
|
* fan min (unconditionally)
|
||||||
|
*/
|
||||||
|
if (new_div != data->fan_div[nr]) {
|
||||||
|
dev_dbg(dev, "fan%u clock divider changed from %u to %u\n",
|
||||||
|
nr + 1, div_from_reg(data->fan_div[nr]),
|
||||||
|
div_from_reg(new_div));
|
||||||
|
data->fan_div[nr] = new_div;
|
||||||
|
nct6775_write_fan_div_common(data, nr);
|
||||||
|
/* Give the chip time to sample a new speed value */
|
||||||
|
data->last_updated = jiffies;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_min:
|
||||||
|
nct6775_write_value(data, data->REG_FAN_MIN[nr], data->fan_min[nr]);
|
||||||
|
mutex_unlock(&data->update_lock);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct sensor_device_attribute sda_fan_input[] = {
|
||||||
|
SENSOR_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0),
|
||||||
|
SENSOR_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1),
|
||||||
|
SENSOR_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2),
|
||||||
|
SENSOR_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3),
|
||||||
|
SENSOR_ATTR(fan5_input, S_IRUGO, show_fan, NULL, 4),
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct sensor_device_attribute sda_fan_alarm[] = {
|
||||||
|
SENSOR_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, FAN_ALARM_BASE),
|
||||||
|
SENSOR_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, FAN_ALARM_BASE + 1),
|
||||||
|
SENSOR_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, FAN_ALARM_BASE + 2),
|
||||||
|
SENSOR_ATTR(fan4_alarm, S_IRUGO, show_alarm, NULL, FAN_ALARM_BASE + 3),
|
||||||
|
SENSOR_ATTR(fan5_alarm, S_IRUGO, show_alarm, NULL, FAN_ALARM_BASE + 4),
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct sensor_device_attribute sda_fan_min[] = {
|
||||||
|
SENSOR_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan_min,
|
||||||
|
store_fan_min, 0),
|
||||||
|
SENSOR_ATTR(fan2_min, S_IWUSR | S_IRUGO, show_fan_min,
|
||||||
|
store_fan_min, 1),
|
||||||
|
SENSOR_ATTR(fan3_min, S_IWUSR | S_IRUGO, show_fan_min,
|
||||||
|
store_fan_min, 2),
|
||||||
|
SENSOR_ATTR(fan4_min, S_IWUSR | S_IRUGO, show_fan_min,
|
||||||
|
store_fan_min, 3),
|
||||||
|
SENSOR_ATTR(fan5_min, S_IWUSR | S_IRUGO, show_fan_min,
|
||||||
|
store_fan_min, 4),
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct sensor_device_attribute sda_fan_div[] = {
|
||||||
|
SENSOR_ATTR(fan1_div, S_IRUGO, show_fan_div, NULL, 0),
|
||||||
|
SENSOR_ATTR(fan2_div, S_IRUGO, show_fan_div, NULL, 1),
|
||||||
|
SENSOR_ATTR(fan3_div, S_IRUGO, show_fan_div, NULL, 2),
|
||||||
|
SENSOR_ATTR(fan4_div, S_IRUGO, show_fan_div, NULL, 3),
|
||||||
|
SENSOR_ATTR(fan5_div, S_IRUGO, show_fan_div, NULL, 4),
|
||||||
|
};
|
||||||
|
|
||||||
static ssize_t
|
static ssize_t
|
||||||
show_temp_label(struct device *dev, struct device_attribute *attr, char *buf)
|
show_temp_label(struct device *dev, struct device_attribute *attr, char *buf)
|
||||||
{
|
{
|
||||||
|
@ -1228,6 +1616,12 @@ static void nct6775_device_remove_files(struct device *dev)
|
||||||
for (i = 0; i < data->in_num; i++)
|
for (i = 0; i < data->in_num; i++)
|
||||||
sysfs_remove_group(&dev->kobj, &nct6775_group_in[i]);
|
sysfs_remove_group(&dev->kobj, &nct6775_group_in[i]);
|
||||||
|
|
||||||
|
for (i = 0; i < 5; i++) {
|
||||||
|
device_remove_file(dev, &sda_fan_input[i].dev_attr);
|
||||||
|
device_remove_file(dev, &sda_fan_alarm[i].dev_attr);
|
||||||
|
device_remove_file(dev, &sda_fan_div[i].dev_attr);
|
||||||
|
device_remove_file(dev, &sda_fan_min[i].dev_attr);
|
||||||
|
}
|
||||||
for (i = 0; i < NUM_TEMP; i++) {
|
for (i = 0; i < NUM_TEMP; i++) {
|
||||||
if (!(data->have_temp & (1 << i)))
|
if (!(data->have_temp & (1 << i)))
|
||||||
continue;
|
continue;
|
||||||
|
@ -1294,6 +1688,75 @@ static inline void nct6775_init_device(struct nct6775_data *data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
nct6775_check_fan_inputs(const struct nct6775_sio_data *sio_data,
|
||||||
|
struct nct6775_data *data)
|
||||||
|
{
|
||||||
|
int regval;
|
||||||
|
bool fan3pin, fan3min, fan4pin, fan4min, fan5pin;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = superio_enter(sio_data->sioreg);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* fan4 and fan5 share some pins with the GPIO and serial flash */
|
||||||
|
if (data->kind == nct6775) {
|
||||||
|
regval = superio_inb(sio_data->sioreg, 0x2c);
|
||||||
|
|
||||||
|
fan3pin = regval & (1 << 6);
|
||||||
|
fan3min = fan3pin;
|
||||||
|
|
||||||
|
/* On NCT6775, fan4 shares pins with the fdc interface */
|
||||||
|
fan4pin = !(superio_inb(sio_data->sioreg, 0x2A) & 0x80);
|
||||||
|
fan4min = 0;
|
||||||
|
fan5pin = 0;
|
||||||
|
} else if (data->kind == nct6776) {
|
||||||
|
bool gpok = superio_inb(sio_data->sioreg, 0x27) & 0x80;
|
||||||
|
|
||||||
|
superio_select(sio_data->sioreg, NCT6775_LD_HWM);
|
||||||
|
regval = superio_inb(sio_data->sioreg, SIO_REG_ENABLE);
|
||||||
|
|
||||||
|
if (regval & 0x80)
|
||||||
|
fan3pin = gpok;
|
||||||
|
else
|
||||||
|
fan3pin = !(superio_inb(sio_data->sioreg, 0x24) & 0x40);
|
||||||
|
|
||||||
|
if (regval & 0x40)
|
||||||
|
fan4pin = gpok;
|
||||||
|
else
|
||||||
|
fan4pin = superio_inb(sio_data->sioreg, 0x1C) & 0x01;
|
||||||
|
|
||||||
|
if (regval & 0x20)
|
||||||
|
fan5pin = gpok;
|
||||||
|
else
|
||||||
|
fan5pin = superio_inb(sio_data->sioreg, 0x1C) & 0x02;
|
||||||
|
|
||||||
|
fan4min = fan4pin;
|
||||||
|
fan3min = fan3pin;
|
||||||
|
} else { /* NCT6779D */
|
||||||
|
regval = superio_inb(sio_data->sioreg, 0x1c);
|
||||||
|
|
||||||
|
fan3pin = !(regval & (1 << 5));
|
||||||
|
fan4pin = !(regval & (1 << 6));
|
||||||
|
fan5pin = !(regval & (1 << 7));
|
||||||
|
|
||||||
|
fan3min = fan3pin;
|
||||||
|
fan4min = fan4pin;
|
||||||
|
}
|
||||||
|
|
||||||
|
superio_exit(sio_data->sioreg);
|
||||||
|
|
||||||
|
data->has_fan = data->has_fan_min = 0x03; /* fan1 and fan2 */
|
||||||
|
data->has_fan |= fan3pin << 2;
|
||||||
|
data->has_fan_min |= fan3min << 2;
|
||||||
|
|
||||||
|
data->has_fan |= (fan4pin << 3) | (fan5pin << 4);
|
||||||
|
data->has_fan_min |= (fan4min << 3) | (fan5pin << 4);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int nct6775_probe(struct platform_device *pdev)
|
static int nct6775_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct device *dev = &pdev->dev;
|
struct device *dev = &pdev->dev;
|
||||||
|
@ -1327,10 +1790,14 @@ static int nct6775_probe(struct platform_device *pdev)
|
||||||
switch (data->kind) {
|
switch (data->kind) {
|
||||||
case nct6775:
|
case nct6775:
|
||||||
data->in_num = 9;
|
data->in_num = 9;
|
||||||
|
data->has_fan_div = true;
|
||||||
data->temp_fixed_num = 3;
|
data->temp_fixed_num = 3;
|
||||||
|
|
||||||
data->ALARM_BITS = NCT6775_ALARM_BITS;
|
data->ALARM_BITS = NCT6775_ALARM_BITS;
|
||||||
|
|
||||||
|
data->fan_from_reg = fan_from_reg16;
|
||||||
|
data->fan_from_reg_min = fan_from_reg8;
|
||||||
|
|
||||||
data->temp_label = nct6775_temp_label;
|
data->temp_label = nct6775_temp_label;
|
||||||
data->temp_label_num = ARRAY_SIZE(nct6775_temp_label);
|
data->temp_label_num = ARRAY_SIZE(nct6775_temp_label);
|
||||||
|
|
||||||
|
@ -1340,6 +1807,8 @@ static int nct6775_probe(struct platform_device *pdev)
|
||||||
data->REG_VIN = NCT6775_REG_IN;
|
data->REG_VIN = NCT6775_REG_IN;
|
||||||
data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN;
|
data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN;
|
||||||
data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX;
|
data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX;
|
||||||
|
data->REG_FAN = NCT6775_REG_FAN;
|
||||||
|
data->REG_FAN_MIN = NCT6775_REG_FAN_MIN;
|
||||||
data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET;
|
data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET;
|
||||||
data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE;
|
data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE;
|
||||||
data->REG_ALARM = NCT6775_REG_ALARM;
|
data->REG_ALARM = NCT6775_REG_ALARM;
|
||||||
|
@ -1355,10 +1824,14 @@ static int nct6775_probe(struct platform_device *pdev)
|
||||||
break;
|
break;
|
||||||
case nct6776:
|
case nct6776:
|
||||||
data->in_num = 9;
|
data->in_num = 9;
|
||||||
|
data->has_fan_div = false;
|
||||||
data->temp_fixed_num = 3;
|
data->temp_fixed_num = 3;
|
||||||
|
|
||||||
data->ALARM_BITS = NCT6776_ALARM_BITS;
|
data->ALARM_BITS = NCT6776_ALARM_BITS;
|
||||||
|
|
||||||
|
data->fan_from_reg = fan_from_reg13;
|
||||||
|
data->fan_from_reg_min = fan_from_reg13;
|
||||||
|
|
||||||
data->temp_label = nct6776_temp_label;
|
data->temp_label = nct6776_temp_label;
|
||||||
data->temp_label_num = ARRAY_SIZE(nct6776_temp_label);
|
data->temp_label_num = ARRAY_SIZE(nct6776_temp_label);
|
||||||
|
|
||||||
|
@ -1368,6 +1841,8 @@ static int nct6775_probe(struct platform_device *pdev)
|
||||||
data->REG_VIN = NCT6775_REG_IN;
|
data->REG_VIN = NCT6775_REG_IN;
|
||||||
data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN;
|
data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN;
|
||||||
data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX;
|
data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX;
|
||||||
|
data->REG_FAN = NCT6775_REG_FAN;
|
||||||
|
data->REG_FAN_MIN = NCT6776_REG_FAN_MIN;
|
||||||
data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET;
|
data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET;
|
||||||
data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE;
|
data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE;
|
||||||
data->REG_ALARM = NCT6775_REG_ALARM;
|
data->REG_ALARM = NCT6775_REG_ALARM;
|
||||||
|
@ -1383,10 +1858,14 @@ static int nct6775_probe(struct platform_device *pdev)
|
||||||
break;
|
break;
|
||||||
case nct6779:
|
case nct6779:
|
||||||
data->in_num = 15;
|
data->in_num = 15;
|
||||||
|
data->has_fan_div = false;
|
||||||
data->temp_fixed_num = 6;
|
data->temp_fixed_num = 6;
|
||||||
|
|
||||||
data->ALARM_BITS = NCT6779_ALARM_BITS;
|
data->ALARM_BITS = NCT6779_ALARM_BITS;
|
||||||
|
|
||||||
|
data->fan_from_reg = fan_from_reg13;
|
||||||
|
data->fan_from_reg_min = fan_from_reg13;
|
||||||
|
|
||||||
data->temp_label = nct6779_temp_label;
|
data->temp_label = nct6779_temp_label;
|
||||||
data->temp_label_num = ARRAY_SIZE(nct6779_temp_label);
|
data->temp_label_num = ARRAY_SIZE(nct6779_temp_label);
|
||||||
|
|
||||||
|
@ -1396,6 +1875,8 @@ static int nct6775_probe(struct platform_device *pdev)
|
||||||
data->REG_VIN = NCT6779_REG_IN;
|
data->REG_VIN = NCT6779_REG_IN;
|
||||||
data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN;
|
data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN;
|
||||||
data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX;
|
data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX;
|
||||||
|
data->REG_FAN = NCT6779_REG_FAN;
|
||||||
|
data->REG_FAN_MIN = NCT6776_REG_FAN_MIN;
|
||||||
data->REG_TEMP_OFFSET = NCT6779_REG_TEMP_OFFSET;
|
data->REG_TEMP_OFFSET = NCT6779_REG_TEMP_OFFSET;
|
||||||
data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE;
|
data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE;
|
||||||
data->REG_ALARM = NCT6779_REG_ALARM;
|
data->REG_ALARM = NCT6779_REG_ALARM;
|
||||||
|
@ -1575,6 +2056,13 @@ static int nct6775_probe(struct platform_device *pdev)
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
|
err = nct6775_check_fan_inputs(sio_data, data);
|
||||||
|
if (err)
|
||||||
|
goto exit_remove;
|
||||||
|
|
||||||
|
/* Read fan clock dividers immediately */
|
||||||
|
nct6775_init_fan_common(dev, data);
|
||||||
|
|
||||||
for (i = 0; i < data->in_num; i++) {
|
for (i = 0; i < data->in_num; i++) {
|
||||||
if (!(data->have_in & (1 << i)))
|
if (!(data->have_in & (1 << i)))
|
||||||
continue;
|
continue;
|
||||||
|
@ -1583,6 +2071,32 @@ static int nct6775_probe(struct platform_device *pdev)
|
||||||
goto exit_remove;
|
goto exit_remove;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < 5; i++) {
|
||||||
|
if (data->has_fan & (1 << i)) {
|
||||||
|
err = device_create_file(dev,
|
||||||
|
&sda_fan_input[i].dev_attr);
|
||||||
|
if (err)
|
||||||
|
goto exit_remove;
|
||||||
|
err = device_create_file(dev,
|
||||||
|
&sda_fan_alarm[i].dev_attr);
|
||||||
|
if (err)
|
||||||
|
goto exit_remove;
|
||||||
|
if (data->kind != nct6776 &&
|
||||||
|
data->kind != nct6779) {
|
||||||
|
err = device_create_file(dev,
|
||||||
|
&sda_fan_div[i].dev_attr);
|
||||||
|
if (err)
|
||||||
|
goto exit_remove;
|
||||||
|
}
|
||||||
|
if (data->has_fan_min & (1 << i)) {
|
||||||
|
err = device_create_file(dev,
|
||||||
|
&sda_fan_min[i].dev_attr);
|
||||||
|
if (err)
|
||||||
|
goto exit_remove;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (i = 0; i < NUM_TEMP; i++) {
|
for (i = 0; i < NUM_TEMP; i++) {
|
||||||
if (!(data->have_temp & (1 << i)))
|
if (!(data->have_temp & (1 << i)))
|
||||||
continue;
|
continue;
|
||||||
|
|
Loading…
Reference in New Issue