hwmon: (lm63) Make fan speed control strategy changeable

Let the user switch between automatic and manual fan speed control.
Before switching to automatic fan speed control, we always check that
the lookup table looks sane.

Signed-off-by: Jean Delvare <khali@linux-fr.org>
Acked-by: Guenter Roeck <guenter.roeck@ericsson.com>
This commit is contained in:
Jean Delvare 2012-03-23 10:02:19 +01:00 committed by Jean Delvare
parent dac27dce31
commit 817c6cc546
1 changed files with 89 additions and 16 deletions

View File

@ -205,12 +205,36 @@ static inline int lut_temp_from_reg(struct lm63_data *data, int nr)
return data->temp8[nr] * (data->lut_temp_highres ? 500 : 1000); return data->temp8[nr] * (data->lut_temp_highres ? 500 : 1000);
} }
/*
* Update the lookup table register cache.
* client->update_lock must be held when calling this function.
*/
static void lm63_update_lut(struct i2c_client *client)
{
struct lm63_data *data = i2c_get_clientdata(client);
int i;
if (time_after(jiffies, data->lut_last_updated + 5 * HZ) ||
!data->lut_valid) {
for (i = 0; i < data->lut_size; 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;
}
}
static struct lm63_data *lm63_update_device(struct device *dev) 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);
@ -278,26 +302,39 @@ 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) || lm63_update_lut(client);
!data->lut_valid) {
for (i = 0; i < data->lut_size; 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;
} }
/*
* Trip points in the lookup table should be in ascending order for both
* temperatures and PWM output values.
*/
static int lm63_lut_looks_bad(struct i2c_client *client)
{
struct lm63_data *data = i2c_get_clientdata(client);
int i;
mutex_lock(&data->update_lock);
lm63_update_lut(client);
for (i = 1; i < data->lut_size; i++) {
if (data->pwm1[1 + i - 1] > data->pwm1[1 + i]
|| data->temp8[3 + i - 1] > data->temp8[3 + i]) {
dev_warn(&client->dev,
"Lookup table doesn't look sane (check entries %d and %d)\n",
i, i + 1);
break;
}
}
mutex_unlock(&data->update_lock);
return i == data->lut_size ? 0 : 1;
}
/* /*
* Sysfs callback functions and files * Sysfs callback functions and files
*/ */
@ -381,6 +418,41 @@ static ssize_t show_pwm1_enable(struct device *dev,
return sprintf(buf, "%d\n", data->config_fan & 0x20 ? 1 : 2); return sprintf(buf, "%d\n", data->config_fan & 0x20 ? 1 : 2);
} }
static ssize_t set_pwm1_enable(struct device *dev,
struct device_attribute *dummy,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct lm63_data *data = i2c_get_clientdata(client);
unsigned long val;
int err;
err = kstrtoul(buf, 10, &val);
if (err)
return err;
if (val < 1 || val > 2)
return -EINVAL;
/*
* Only let the user switch to automatic mode if the lookup table
* looks sane.
*/
if (val == 2 && lm63_lut_looks_bad(client))
return -EPERM;
mutex_lock(&data->update_lock);
data->config_fan = i2c_smbus_read_byte_data(client,
LM63_REG_CONFIG_FAN);
if (val == 1)
data->config_fan |= 0x20;
else
data->config_fan &= ~0x20;
i2c_smbus_write_byte_data(client, LM63_REG_CONFIG_FAN,
data->config_fan);
mutex_unlock(&data->update_lock);
return count;
}
/* /*
* There are 8bit registers for both local(temp1) and remote(temp2) sensor. * There are 8bit registers for both local(temp1) and remote(temp2) sensor.
* For remote sensor registers temp2_offset has to be considered, * For remote sensor registers temp2_offset has to be considered,
@ -669,7 +741,8 @@ static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan,
set_fan, 1); set_fan, 1);
static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm1, set_pwm1, 0); 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_IWUSR | S_IRUGO,
show_pwm1_enable, set_pwm1_enable);
static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IRUGO, show_pwm1, NULL, 1); static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IRUGO, show_pwm1, NULL, 1);
static SENSOR_DEVICE_ATTR(pwm1_auto_point1_temp, S_IRUGO, static SENSOR_DEVICE_ATTR(pwm1_auto_point1_temp, S_IRUGO,
show_lut_temp, NULL, 3); show_lut_temp, NULL, 3);