hwmon: (w83781d) Clean up detect function
[safe/jmp/linux-2.6] / drivers / hwmon / adt7470.c
index da6c930..633e1a1 100644 (file)
@@ -28,6 +28,7 @@
 #include <linux/mutex.h>
 #include <linux/delay.h>
 #include <linux/log2.h>
+#include <linux/kthread.h>
 
 /* Addresses to scan */
 static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END };
@@ -74,6 +75,7 @@ I2C_CLIENT_INSMOD_1(adt7470);
 #define ADT7470_REG_PWM12_CFG                  0x68
 #define                ADT7470_PWM2_AUTO_MASK          0x40
 #define                ADT7470_PWM1_AUTO_MASK          0x80
+#define                ADT7470_PWM_AUTO_MASK           0xC0
 #define ADT7470_REG_PWM34_CFG                  0x69
 #define                ADT7470_PWM3_AUTO_MASK          0x40
 #define                ADT7470_PWM4_AUTO_MASK          0x80
@@ -128,8 +130,11 @@ I2C_CLIENT_INSMOD_1(adt7470);
 /* How often do we reread sensor limit values? (In jiffies) */
 #define LIMIT_REFRESH_INTERVAL (60 * HZ)
 
-/* sleep 1s while gathering temperature data */
-#define TEMP_COLLECTION_TIME   1000
+/* Wait at least 200ms per sensor for 10 sensors */
+#define TEMP_COLLECTION_TIME   2000
+
+/* auto update thing won't fire more than every 2s */
+#define AUTO_UPDATE_INTERVAL   2000
 
 /* datasheet says to divide this number by the fan reading to get fan rpm */
 #define FAN_PERIOD_TO_RPM(x)   ((90000 * 60) / (x))
@@ -146,6 +151,9 @@ struct adt7470_data {
        unsigned long           sensors_last_updated;   /* In jiffies */
        unsigned long           limits_last_updated;    /* In jiffies */
 
+       int                     num_temp_sensors;       /* -1 = probe */
+       int                     temperatures_probed;
+
        s8                      temp[ADT7470_TEMP_COUNT];
        s8                      temp_min[ADT7470_TEMP_COUNT];
        s8                      temp_max[ADT7470_TEMP_COUNT];
@@ -161,6 +169,10 @@ struct adt7470_data {
        u8                      pwm_min[ADT7470_PWM_COUNT];
        s8                      pwm_tmin[ADT7470_PWM_COUNT];
        u8                      pwm_auto_temp[ADT7470_PWM_COUNT];
+
+       struct task_struct      *auto_update;
+       struct completion       auto_update_stop;
+       unsigned int            auto_update_interval;
 };
 
 static int adt7470_probe(struct i2c_client *client,
@@ -218,40 +230,126 @@ static void adt7470_init_client(struct i2c_client *client)
        }
 }
 
-static struct adt7470_data *adt7470_update_device(struct device *dev)
+/* Probe for temperature sensors.  Assumes lock is held */
+static int adt7470_read_temperatures(struct i2c_client *client,
+                                    struct adt7470_data *data)
 {
-       struct i2c_client *client = to_i2c_client(dev);
-       struct adt7470_data *data = i2c_get_clientdata(client);
-       unsigned long local_jiffies = jiffies;
-       u8 cfg;
+       unsigned long res;
        int i;
+       u8 cfg, pwm[4], pwm_cfg[2];
 
-       mutex_lock(&data->lock);
-       if (time_before(local_jiffies, data->sensors_last_updated +
-               SENSOR_REFRESH_INTERVAL)
-               && data->sensors_valid)
-               goto no_sensor_update;
+       /* save pwm[1-4] config register */
+       pwm_cfg[0] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM_CFG(0));
+       pwm_cfg[1] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM_CFG(2));
+
+       /* set manual pwm to whatever it is set to now */
+       for (i = 0; i < ADT7470_FAN_COUNT; i++)
+               pwm[i] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM(i));
+
+       /* put pwm in manual mode */
+       i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_CFG(0),
+               pwm_cfg[0] & ~(ADT7470_PWM_AUTO_MASK));
+       i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_CFG(2),
+               pwm_cfg[1] & ~(ADT7470_PWM_AUTO_MASK));
+
+       /* write pwm control to whatever it was */
+       for (i = 0; i < ADT7470_FAN_COUNT; i++)
+               i2c_smbus_write_byte_data(client, ADT7470_REG_PWM(i), pwm[i]);
 
        /* start reading temperature sensors */
        cfg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG);
        cfg |= 0x80;
        i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, cfg);
 
-       /*
-        * Delay is 200ms * number of tmp05 sensors.  Too bad
-        * there's no way to figure out how many are connected.
-        * For now, assume 1s will work.
-        */
-       msleep(TEMP_COLLECTION_TIME);
+       /* Delay is 200ms * number of temp sensors. */
+       res = msleep_interruptible((data->num_temp_sensors >= 0 ?
+                                   data->num_temp_sensors * 200 :
+                                   TEMP_COLLECTION_TIME));
 
        /* done reading temperature sensors */
        cfg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG);
        cfg &= ~0x80;
        i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, cfg);
 
-       for (i = 0; i < ADT7470_TEMP_COUNT; i++)
+       /* restore pwm[1-4] config registers */
+       i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_CFG(0), pwm_cfg[0]);
+       i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_CFG(2), pwm_cfg[1]);
+
+       if (res) {
+               printk(KERN_ERR "ha ha, interrupted");
+               return -EAGAIN;
+       }
+
+       /* Only count fans if we have to */
+       if (data->num_temp_sensors >= 0)
+               return 0;
+
+       for (i = 0; i < ADT7470_TEMP_COUNT; i++) {
                data->temp[i] = i2c_smbus_read_byte_data(client,
                                                ADT7470_TEMP_REG(i));
+               if (data->temp[i])
+                       data->num_temp_sensors = i + 1;
+       }
+       data->temperatures_probed = 1;
+       return 0;
+}
+
+static int adt7470_update_thread(void *p)
+{
+       struct i2c_client *client = p;
+       struct adt7470_data *data = i2c_get_clientdata(client);
+
+       while (!kthread_should_stop()) {
+               mutex_lock(&data->lock);
+               adt7470_read_temperatures(client, data);
+               mutex_unlock(&data->lock);
+               if (kthread_should_stop())
+                       break;
+               msleep_interruptible(data->auto_update_interval);
+       }
+
+       complete_all(&data->auto_update_stop);
+       return 0;
+}
+
+static struct adt7470_data *adt7470_update_device(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct adt7470_data *data = i2c_get_clientdata(client);
+       unsigned long local_jiffies = jiffies;
+       u8 cfg;
+       int i;
+       int need_sensors = 1;
+       int need_limits = 1;
+
+       /*
+        * Figure out if we need to update the shadow registers.
+        * Lockless means that we may occasionally report out of
+        * date data.
+        */
+       if (time_before(local_jiffies, data->sensors_last_updated +
+                       SENSOR_REFRESH_INTERVAL) &&
+           data->sensors_valid)
+               need_sensors = 0;
+
+       if (time_before(local_jiffies, data->limits_last_updated +
+                       LIMIT_REFRESH_INTERVAL) &&
+           data->limits_valid)
+               need_limits = 0;
+
+       if (!need_sensors && !need_limits)
+               return data;
+
+       mutex_lock(&data->lock);
+       if (!need_sensors)
+               goto no_sensor_update;
+
+       if (!data->temperatures_probed)
+               adt7470_read_temperatures(client, data);
+       else
+               for (i = 0; i < ADT7470_TEMP_COUNT; i++)
+                       data->temp[i] = i2c_smbus_read_byte_data(client,
+                                               ADT7470_TEMP_REG(i));
 
        for (i = 0; i < ADT7470_FAN_COUNT; i++)
                data->fan[i] = adt7470_read_word_data(client,
@@ -300,9 +398,7 @@ static struct adt7470_data *adt7470_update_device(struct device *dev)
        data->sensors_valid = 1;
 
 no_sensor_update:
-       if (time_before(local_jiffies, data->limits_last_updated +
-               LIMIT_REFRESH_INTERVAL)
-               && data->limits_valid)
+       if (!need_limits)
                goto out;
 
        for (i = 0; i < ADT7470_TEMP_COUNT; i++) {
@@ -336,6 +432,66 @@ out:
        return data;
 }
 
+static ssize_t show_auto_update_interval(struct device *dev,
+                                        struct device_attribute *devattr,
+                                        char *buf)
+{
+       struct adt7470_data *data = adt7470_update_device(dev);
+       return sprintf(buf, "%d\n", data->auto_update_interval);
+}
+
+static ssize_t set_auto_update_interval(struct device *dev,
+                                       struct device_attribute *devattr,
+                                       const char *buf,
+                                       size_t count)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct adt7470_data *data = i2c_get_clientdata(client);
+       long temp;
+
+       if (strict_strtol(buf, 10, &temp))
+               return -EINVAL;
+
+       temp = SENSORS_LIMIT(temp, 0, 60000);
+
+       mutex_lock(&data->lock);
+       data->auto_update_interval = temp;
+       mutex_unlock(&data->lock);
+
+       return count;
+}
+
+static ssize_t show_num_temp_sensors(struct device *dev,
+                                    struct device_attribute *devattr,
+                                    char *buf)
+{
+       struct adt7470_data *data = adt7470_update_device(dev);
+       return sprintf(buf, "%d\n", data->num_temp_sensors);
+}
+
+static ssize_t set_num_temp_sensors(struct device *dev,
+                                   struct device_attribute *devattr,
+                                   const char *buf,
+                                   size_t count)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct adt7470_data *data = i2c_get_clientdata(client);
+       long temp;
+
+       if (strict_strtol(buf, 10, &temp))
+               return -EINVAL;
+
+       temp = SENSORS_LIMIT(temp, -1, 10);
+
+       mutex_lock(&data->lock);
+       data->num_temp_sensors = temp;
+       if (temp < 0)
+               data->temperatures_probed = 0;
+       mutex_unlock(&data->lock);
+
+       return count;
+}
+
 static ssize_t show_temp_min(struct device *dev,
                             struct device_attribute *devattr,
                             char *buf)
@@ -802,6 +958,10 @@ static ssize_t show_alarm(struct device *dev,
 }
 
 static DEVICE_ATTR(alarm_mask, S_IRUGO, show_alarm_mask, NULL);
+static DEVICE_ATTR(num_temp_sensors, S_IWUSR | S_IRUGO, show_num_temp_sensors,
+                  set_num_temp_sensors);
+static DEVICE_ATTR(auto_update_interval, S_IWUSR | S_IRUGO,
+                  show_auto_update_interval, set_auto_update_interval);
 
 static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_max,
                    set_temp_max, 0);
@@ -974,6 +1134,8 @@ static SENSOR_DEVICE_ATTR(pwm4_auto_channels_temp, S_IWUSR | S_IRUGO,
 static struct attribute *adt7470_attr[] =
 {
        &dev_attr_alarm_mask.attr,
+       &dev_attr_num_temp_sensors.attr,
+       &dev_attr_auto_update_interval.attr,
        &sensor_dev_attr_temp1_max.dev_attr.attr,
        &sensor_dev_attr_temp2_max.dev_attr.attr,
        &sensor_dev_attr_temp3_max.dev_attr.attr,
@@ -1106,6 +1268,9 @@ static int adt7470_probe(struct i2c_client *client,
                goto exit;
        }
 
+       data->num_temp_sensors = -1;
+       data->auto_update_interval = AUTO_UPDATE_INTERVAL;
+
        i2c_set_clientdata(client, data);
        mutex_init(&data->lock);
 
@@ -1125,8 +1290,16 @@ static int adt7470_probe(struct i2c_client *client,
                goto exit_remove;
        }
 
+       init_completion(&data->auto_update_stop);
+       data->auto_update = kthread_run(adt7470_update_thread, client,
+                                       dev_name(data->hwmon_dev));
+       if (IS_ERR(data->auto_update))
+               goto exit_unregister;
+
        return 0;
 
+exit_unregister:
+       hwmon_device_unregister(data->hwmon_dev);
 exit_remove:
        sysfs_remove_group(&client->dev.kobj, &data->attrs);
 exit_free:
@@ -1139,6 +1312,8 @@ static int adt7470_remove(struct i2c_client *client)
 {
        struct adt7470_data *data = i2c_get_clientdata(client);
 
+       kthread_stop(data->auto_update);
+       wait_for_completion(&data->auto_update_stop);
        hwmon_device_unregister(data->hwmon_dev);
        sysfs_remove_group(&client->dev.kobj, &data->attrs);
        kfree(data);