hwmon: (lm63) Expose automatic fan speed control lookup table

The LM63 and compatible devices have a lookup table to control the fan
speed automatically. Expose it in sysfs. Values are cached for 5
seconds, independently of the other register values to avoid slowing
down "sensors". We might make the table values writable in the future.

Signed-off-by: Jean Delvare <khali@linux-fr.org>
Tested-by: Guenter Roeck <guenter.roeck@ericsson.com>
Acked-by: Guenter Roeck <guenter.roeck@ericsson.com>
This commit is contained in:
Jean Delvare 2012-01-16 22:51:47 +01:00 committed by Jean Delvare
parent d93ab78070
commit d216f6809e
2 changed files with 136 additions and 15 deletions

View File

@ -66,7 +66,8 @@ supported either.
The lm63 driver will not update its values more frequently than configured with The lm63 driver will not update its values more frequently than configured with
the update_interval sysfs attribute; reading them more often will do no harm, the update_interval sysfs attribute; reading them more often will do no harm,
but will return 'old' values. but will return 'old' values. Values in the automatic fan control lookup table
(attributes pwm1_auto_*) have their own independent lifetime of 5 seconds.
The LM64 is effectively an LM63 with GPIO lines. The driver does not The LM64 is effectively an LM63 with GPIO lines. The driver does not
support these GPIO lines at present. support these GPIO lines at present.

View File

@ -75,6 +75,9 @@ static const unsigned short normal_i2c[] = { 0x18, 0x4c, 0x4e, I2C_CLIENT_END };
#define LM63_REG_PWM_VALUE 0x4C #define LM63_REG_PWM_VALUE 0x4C
#define LM63_REG_PWM_FREQ 0x4D #define LM63_REG_PWM_FREQ 0x4D
#define LM63_REG_LUT_TEMP_HYST 0x4F
#define LM63_REG_LUT_TEMP(nr) (0x50 + 2 * (nr))
#define LM63_REG_LUT_PWM(nr) (0x51 + 2 * (nr))
#define LM63_REG_LOCAL_TEMP 0x00 #define LM63_REG_LOCAL_TEMP 0x00
#define LM63_REG_LOCAL_HIGH 0x05 #define LM63_REG_LOCAL_HIGH 0x05
@ -192,7 +195,9 @@ struct lm63_data {
struct device *hwmon_dev; struct device *hwmon_dev;
struct mutex update_lock; struct mutex update_lock;
char valid; /* zero until following fields are valid */ char valid; /* zero until following fields are valid */
char lut_valid; /* zero until lut fields are valid */
unsigned long last_updated; /* in jiffies */ unsigned long last_updated; /* in jiffies */
unsigned long lut_last_updated; /* in jiffies */
enum chips kind; enum chips kind;
int temp2_offset; int temp2_offset;
@ -204,18 +209,22 @@ struct lm63_data {
u16 fan[2]; /* 0: input u16 fan[2]; /* 0: input
1: low limit */ 1: low limit */
u8 pwm1_freq; u8 pwm1_freq;
u8 pwm1_value; u8 pwm1[9]; /* 0: current output
s8 temp8[3]; /* 0: local input 1-8: lookup table */
s8 temp8[11]; /* 0: local input
1: local high limit 1: local high limit
2: remote critical limit */ 2: remote critical limit
3-10: lookup table */
s16 temp11[4]; /* 0: remote input s16 temp11[4]; /* 0: remote input
1: remote low limit 1: remote low limit
2: remote high limit 2: remote high limit
3: remote offset */ 3: remote offset */
u16 temp11u; /* remote input (unsigned) */ u16 temp11u; /* remote input (unsigned) */
u8 temp2_crit_hyst; u8 temp2_crit_hyst;
u8 lut_temp_hyst;
u8 alarms; u8 alarms;
bool pwm_highres; bool pwm_highres;
bool lut_temp_highres;
bool remote_unsigned; /* true if unsigned remote upper limits */ bool remote_unsigned; /* true if unsigned remote upper limits */
bool trutherm; bool trutherm;
}; };
@ -227,6 +236,11 @@ static inline int temp8_from_reg(struct lm63_data *data, int nr)
return TEMP8_FROM_REG(data->temp8[nr]); return TEMP8_FROM_REG(data->temp8[nr]);
} }
static inline int lut_temp_from_reg(struct lm63_data *data, int nr)
{
return data->temp8[nr] * (data->lut_temp_highres ? 500 : 1000);
}
/* /*
* Sysfs callback functions and files * Sysfs callback functions and files
*/ */
@ -261,17 +275,19 @@ static ssize_t set_fan(struct device *dev, struct device_attribute *dummy,
return count; return count;
} }
static ssize_t show_pwm1(struct device *dev, struct device_attribute *dummy, static ssize_t show_pwm1(struct device *dev, struct device_attribute *devattr,
char *buf) char *buf)
{ {
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct lm63_data *data = lm63_update_device(dev); struct lm63_data *data = lm63_update_device(dev);
int nr = attr->index;
int pwm; int pwm;
if (data->pwm_highres) if (data->pwm_highres)
pwm = data->pwm1_value; pwm = data->pwm1[nr];
else else
pwm = data->pwm1_value >= 2 * data->pwm1_freq ? pwm = data->pwm1[nr] >= 2 * data->pwm1_freq ?
255 : (data->pwm1_value * 255 + data->pwm1_freq) / 255 : (data->pwm1[nr] * 255 + data->pwm1_freq) /
(2 * data->pwm1_freq); (2 * data->pwm1_freq);
return sprintf(buf, "%d\n", pwm); return sprintf(buf, "%d\n", pwm);
@ -294,9 +310,9 @@ static ssize_t set_pwm1(struct device *dev, struct device_attribute *dummy,
val = SENSORS_LIMIT(val, 0, 255); val = SENSORS_LIMIT(val, 0, 255);
mutex_lock(&data->update_lock); mutex_lock(&data->update_lock);
data->pwm1_value = data->pwm_highres ? val : data->pwm1[0] = data->pwm_highres ? val :
(val * data->pwm1_freq * 2 + 127) / 255; (val * data->pwm1_freq * 2 + 127) / 255;
i2c_smbus_write_byte_data(client, LM63_REG_PWM_VALUE, data->pwm1_value); i2c_smbus_write_byte_data(client, LM63_REG_PWM_VALUE, data->pwm1[0]);
mutex_unlock(&data->update_lock); mutex_unlock(&data->update_lock);
return count; return count;
} }
@ -333,6 +349,16 @@ static ssize_t show_remote_temp8(struct device *dev,
+ data->temp2_offset); + data->temp2_offset);
} }
static ssize_t show_lut_temp(struct device *dev,
struct device_attribute *devattr,
char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct lm63_data *data = lm63_update_device(dev);
return sprintf(buf, "%d\n", lut_temp_from_reg(data, attr->index)
+ data->temp2_offset);
}
static ssize_t set_temp8(struct device *dev, struct device_attribute *devattr, static ssize_t set_temp8(struct device *dev, struct device_attribute *devattr,
const char *buf, size_t count) const char *buf, size_t count)
{ {
@ -440,6 +466,17 @@ static ssize_t show_temp2_crit_hyst(struct device *dev,
- TEMP8_FROM_REG(data->temp2_crit_hyst)); - TEMP8_FROM_REG(data->temp2_crit_hyst));
} }
static ssize_t show_lut_temp_hyst(struct device *dev,
struct device_attribute *devattr, char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct lm63_data *data = lm63_update_device(dev);
return sprintf(buf, "%d\n", lut_temp_from_reg(data, attr->index)
+ data->temp2_offset
- TEMP8_FROM_REG(data->lut_temp_hyst));
}
/* /*
* And now the other way around, user-space provides an absolute * And now the other way around, user-space provides an absolute
* hysteresis value and we have to store a relative one * hysteresis value and we have to store a relative one
@ -574,8 +611,48 @@ static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0);
static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan, static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan,
set_fan, 1); set_fan, 1);
static DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm1, set_pwm1); static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm1, set_pwm1, 0);
static DEVICE_ATTR(pwm1_enable, S_IRUGO, show_pwm1_enable, NULL); static DEVICE_ATTR(pwm1_enable, S_IRUGO, show_pwm1_enable, NULL);
static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IRUGO, show_pwm1, NULL, 1);
static SENSOR_DEVICE_ATTR(pwm1_auto_point1_temp, S_IRUGO,
show_lut_temp, NULL, 3);
static SENSOR_DEVICE_ATTR(pwm1_auto_point1_temp_hyst, S_IRUGO,
show_lut_temp_hyst, NULL, 3);
static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IRUGO, show_pwm1, NULL, 2);
static SENSOR_DEVICE_ATTR(pwm1_auto_point2_temp, S_IRUGO,
show_lut_temp, NULL, 4);
static SENSOR_DEVICE_ATTR(pwm1_auto_point2_temp_hyst, S_IRUGO,
show_lut_temp_hyst, NULL, 4);
static SENSOR_DEVICE_ATTR(pwm1_auto_point3_pwm, S_IRUGO, show_pwm1, NULL, 3);
static SENSOR_DEVICE_ATTR(pwm1_auto_point3_temp, S_IRUGO,
show_lut_temp, NULL, 5);
static SENSOR_DEVICE_ATTR(pwm1_auto_point3_temp_hyst, S_IRUGO,
show_lut_temp_hyst, NULL, 5);
static SENSOR_DEVICE_ATTR(pwm1_auto_point4_pwm, S_IRUGO, show_pwm1, NULL, 4);
static SENSOR_DEVICE_ATTR(pwm1_auto_point4_temp, S_IRUGO,
show_lut_temp, NULL, 6);
static SENSOR_DEVICE_ATTR(pwm1_auto_point4_temp_hyst, S_IRUGO,
show_lut_temp_hyst, NULL, 6);
static SENSOR_DEVICE_ATTR(pwm1_auto_point5_pwm, S_IRUGO, show_pwm1, NULL, 5);
static SENSOR_DEVICE_ATTR(pwm1_auto_point5_temp, S_IRUGO,
show_lut_temp, NULL, 7);
static SENSOR_DEVICE_ATTR(pwm1_auto_point5_temp_hyst, S_IRUGO,
show_lut_temp_hyst, NULL, 7);
static SENSOR_DEVICE_ATTR(pwm1_auto_point6_pwm, S_IRUGO, show_pwm1, NULL, 6);
static SENSOR_DEVICE_ATTR(pwm1_auto_point6_temp, S_IRUGO,
show_lut_temp, NULL, 8);
static SENSOR_DEVICE_ATTR(pwm1_auto_point6_temp_hyst, S_IRUGO,
show_lut_temp_hyst, NULL, 8);
static SENSOR_DEVICE_ATTR(pwm1_auto_point7_pwm, S_IRUGO, show_pwm1, NULL, 7);
static SENSOR_DEVICE_ATTR(pwm1_auto_point7_temp, S_IRUGO,
show_lut_temp, NULL, 9);
static SENSOR_DEVICE_ATTR(pwm1_auto_point7_temp_hyst, S_IRUGO,
show_lut_temp_hyst, NULL, 9);
static SENSOR_DEVICE_ATTR(pwm1_auto_point8_pwm, S_IRUGO, show_pwm1, NULL, 8);
static SENSOR_DEVICE_ATTR(pwm1_auto_point8_temp, S_IRUGO,
show_lut_temp, NULL, 10);
static SENSOR_DEVICE_ATTR(pwm1_auto_point8_temp_hyst, S_IRUGO,
show_lut_temp_hyst, NULL, 10);
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_local_temp8, NULL, 0); static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_local_temp8, NULL, 0);
static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_local_temp8, static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_local_temp8,
@ -609,8 +686,33 @@ static DEVICE_ATTR(update_interval, S_IRUGO | S_IWUSR, show_update_interval,
set_update_interval); set_update_interval);
static struct attribute *lm63_attributes[] = { static struct attribute *lm63_attributes[] = {
&dev_attr_pwm1.attr, &sensor_dev_attr_pwm1.dev_attr.attr,
&dev_attr_pwm1_enable.attr, &dev_attr_pwm1_enable.attr,
&sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point1_temp_hyst.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point2_temp_hyst.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point3_temp_hyst.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point4_temp_hyst.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point5_temp_hyst.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point6_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point6_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point6_temp_hyst.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point7_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point7_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point7_temp_hyst.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point8_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point8_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point8_temp_hyst.dev_attr.attr,
&sensor_dev_attr_temp1_input.dev_attr.attr, &sensor_dev_attr_temp1_input.dev_attr.attr,
&sensor_dev_attr_temp2_input.dev_attr.attr, &sensor_dev_attr_temp2_input.dev_attr.attr,
&sensor_dev_attr_temp2_min.dev_attr.attr, &sensor_dev_attr_temp2_min.dev_attr.attr,
@ -834,6 +936,8 @@ static void lm63_init_client(struct i2c_client *client)
u8 config_enhanced u8 config_enhanced
= i2c_smbus_read_byte_data(client, = i2c_smbus_read_byte_data(client,
LM96163_REG_CONFIG_ENHANCED); LM96163_REG_CONFIG_ENHANCED);
if (config_enhanced & 0x20)
data->lut_temp_highres = true;
if ((config_enhanced & 0x10) if ((config_enhanced & 0x10)
&& !(data->config_fan & 0x08) && data->pwm1_freq == 8) && !(data->config_fan & 0x08) && data->pwm1_freq == 8)
data->pwm_highres = true; data->pwm_highres = true;
@ -872,6 +976,7 @@ static struct lm63_data *lm63_update_device(struct device *dev)
struct i2c_client *client = to_i2c_client(dev); struct i2c_client *client = to_i2c_client(dev);
struct lm63_data *data = i2c_get_clientdata(client); struct lm63_data *data = i2c_get_clientdata(client);
unsigned long next_update; unsigned long next_update;
int i;
mutex_lock(&data->update_lock); mutex_lock(&data->update_lock);
@ -895,8 +1000,8 @@ static struct lm63_data *lm63_update_device(struct device *dev)
LM63_REG_PWM_FREQ); LM63_REG_PWM_FREQ);
if (data->pwm1_freq == 0) if (data->pwm1_freq == 0)
data->pwm1_freq = 1; data->pwm1_freq = 1;
data->pwm1_value = i2c_smbus_read_byte_data(client, data->pwm1[0] = i2c_smbus_read_byte_data(client,
LM63_REG_PWM_VALUE); LM63_REG_PWM_VALUE);
data->temp8[0] = i2c_smbus_read_byte_data(client, data->temp8[0] = i2c_smbus_read_byte_data(client,
LM63_REG_LOCAL_TEMP); LM63_REG_LOCAL_TEMP);
@ -939,6 +1044,21 @@ static struct lm63_data *lm63_update_device(struct device *dev)
data->valid = 1; data->valid = 1;
} }
if (time_after(jiffies, data->lut_last_updated + 5 * HZ) ||
!data->lut_valid) {
for (i = 0; i < 8; i++) {
data->pwm1[1 + i] = i2c_smbus_read_byte_data(client,
LM63_REG_LUT_PWM(i));
data->temp8[3 + i] = i2c_smbus_read_byte_data(client,
LM63_REG_LUT_TEMP(i));
}
data->lut_temp_hyst = i2c_smbus_read_byte_data(client,
LM63_REG_LUT_TEMP_HYST);
data->lut_last_updated = jiffies;
data->lut_valid = 1;
}
mutex_unlock(&data->update_lock); mutex_unlock(&data->update_lock);
return data; return data;