hwmon: (lm63) Make fan speed control strategy changeable
authorJean Delvare <khali@linux-fr.org>
Fri, 23 Mar 2012 09:02:19 +0000 (10:02 +0100)
committerJean Delvare <khali@endymion.delvare>
Fri, 23 Mar 2012 09:02:19 +0000 (10:02 +0100)
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>
drivers/hwmon/lm63.c

index b90bdc9..910599a 100644 (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);
 }
 
+/*
+ * 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)
 {
        struct i2c_client *client = to_i2c_client(dev);
        struct lm63_data *data = i2c_get_clientdata(client);
        unsigned long next_update;
-       int i;
 
        mutex_lock(&data->update_lock);
 
@@ -278,26 +302,39 @@ static struct lm63_data *lm63_update_device(struct device *dev)
                data->valid = 1;
        }
 
-       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;
-       }
+       lm63_update_lut(client);
 
        mutex_unlock(&data->update_lock);
 
        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
  */
@@ -381,6 +418,41 @@ static ssize_t show_pwm1_enable(struct device *dev,
        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.
  * 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);
 
 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_temp, S_IRUGO,
        show_lut_temp, NULL, 3);