it87: Add support for the IT8716F
[safe/jmp/linux-2.6] / drivers / hwmon / it87.c
index b0ee574..e7f14e6 100644 (file)
@@ -4,6 +4,7 @@
 
     Supports: IT8705F  Super I/O chip w/LPC interface
               IT8712F  Super I/O chip w/LPC interface & SMBus
+              IT8716F  Super I/O chip w/LPC interface
               Sis950   A clone of the IT8705F
 
     Copyright (C) 2001 Chris Gauthron <chrisg@0-in.com> 
@@ -50,7 +51,7 @@ static unsigned short normal_i2c[] = { 0x2d, I2C_CLIENT_END };
 static unsigned short isa_address;
 
 /* Insmod parameters */
-I2C_CLIENT_INSMOD_2(it87, it8712);
+I2C_CLIENT_INSMOD_3(it87, it8712, it8716);
 
 #define        REG     0x2e    /* The register to read/write */
 #define        DEV     0x07    /* Register: Logical device select */
@@ -101,6 +102,7 @@ superio_exit(void)
 
 #define IT8712F_DEVID 0x8712
 #define IT8705F_DEVID 0x8705
+#define IT8716F_DEVID 0x8716
 #define IT87_ACT_REG  0x30
 #define IT87_BASE_REG 0x60
 
@@ -132,12 +134,18 @@ static u16 chip_type;
 #define IT87_REG_ALARM3        0x03
 
 #define IT87_REG_VID           0x0a
+/* Warning: register 0x0b is used for something completely different in
+   new chips/revisions. I suspect only 16-bit tachometer mode will work
+   for these. */
 #define IT87_REG_FAN_DIV       0x0b
+#define IT87_REG_FAN_16BIT     0x0c
 
 /* Monitors: 9 voltage (0 to 7, battery), 3 temp (1 to 3), 3 fan (1 to 3) */
 
 #define IT87_REG_FAN(nr)       (0x0d + (nr))
 #define IT87_REG_FAN_MIN(nr)   (0x10 + (nr))
+#define IT87_REG_FANX(nr)      (0x18 + (nr))
+#define IT87_REG_FANX_MIN(nr)  (0x1b + (nr))
 #define IT87_REG_FAN_MAIN_CTRL 0x13
 #define IT87_REG_FAN_CTL       0x14
 #define IT87_REG_PWM(nr)       (0x15 + (nr))
@@ -169,7 +177,16 @@ static inline u8 FAN_TO_REG(long rpm, int div)
                             254);
 }
 
+static inline u16 FAN16_TO_REG(long rpm)
+{
+       if (rpm == 0)
+               return 0xffff;
+       return SENSORS_LIMIT((1350000 + rpm) / (rpm * 2), 1, 0xfffe);
+}
+
 #define FAN_FROM_REG(val,div) ((val)==0?-1:(val)==255?0:1350000/((val)*(div)))
+/* The divider is fixed to 2 in 16-bit mode */
+#define FAN16_FROM_REG(val) ((val)==0?-1:(val)==0xffff?0:1350000/((val)*2))
 
 #define TEMP_TO_REG(val) (SENSORS_LIMIT(((val)<0?(((val)-500)/1000):\
                                        ((val)+500)/1000),-128,127))
@@ -205,8 +222,8 @@ struct it87_data {
        u8 in[9];               /* Register value */
        u8 in_max[9];           /* Register value */
        u8 in_min[9];           /* Register value */
-       u8 fan[3];              /* Register value */
-       u8 fan_min[3];          /* Register value */
+       u16 fan[3];             /* Register values, possibly combined */
+       u16 fan_min[3];         /* Register values, possibly combined */
        u8 temp[3];             /* Register value */
        u8 temp_high[3];        /* Register value */
        u8 temp_low[3];         /* Register value */
@@ -657,6 +674,59 @@ show_pwm_offset(1);
 show_pwm_offset(2);
 show_pwm_offset(3);
 
+/* A different set of callbacks for 16-bit fans */
+static ssize_t show_fan16(struct device *dev, struct device_attribute *attr,
+               char *buf)
+{
+       struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+       int nr = sensor_attr->index;
+       struct it87_data *data = it87_update_device(dev);
+       return sprintf(buf, "%d\n", FAN16_FROM_REG(data->fan[nr]));
+}
+
+static ssize_t show_fan16_min(struct device *dev, struct device_attribute *attr,
+               char *buf)
+{
+       struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+       int nr = sensor_attr->index;
+       struct it87_data *data = it87_update_device(dev);
+       return sprintf(buf, "%d\n", FAN16_FROM_REG(data->fan_min[nr]));
+}
+
+static ssize_t set_fan16_min(struct device *dev, struct device_attribute *attr,
+               const char *buf, size_t count)
+{
+       struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+       int nr = sensor_attr->index;
+       struct i2c_client *client = to_i2c_client(dev);
+       struct it87_data *data = i2c_get_clientdata(client);
+       int val = simple_strtol(buf, NULL, 10);
+
+       mutex_lock(&data->update_lock);
+       data->fan_min[nr] = FAN16_TO_REG(val);
+       it87_write_value(client, IT87_REG_FAN_MIN(nr),
+                        data->fan_min[nr] & 0xff);
+       it87_write_value(client, IT87_REG_FANX_MIN(nr),
+                        data->fan_min[nr] >> 8);
+       mutex_unlock(&data->update_lock);
+       return count;
+}
+
+/* We want to use the same sysfs file names as 8-bit fans, but we need
+   different variable names, so we have to use SENSOR_ATTR instead of
+   SENSOR_DEVICE_ATTR. */
+#define show_fan16_offset(offset) \
+static struct sensor_device_attribute sensor_dev_attr_fan##offset##_input16 \
+       = SENSOR_ATTR(fan##offset##_input, S_IRUGO,             \
+               show_fan16, NULL, offset - 1);                  \
+static struct sensor_device_attribute sensor_dev_attr_fan##offset##_min16 \
+       = SENSOR_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR,     \
+               show_fan16_min, set_fan16_min, offset - 1)
+
+show_fan16_offset(1);
+show_fan16_offset(2);
+show_fan16_offset(3);
+
 /* Alarms */
 static ssize_t show_alarms(struct device *dev, struct device_attribute *attr, char *buf)
 {
@@ -721,6 +791,7 @@ static int __init it87_find(unsigned short *address)
        superio_enter();
        chip_type = superio_inw(DEVID);
        if (chip_type != IT8712F_DEVID
+        && chip_type != IT8716F_DEVID
         && chip_type != IT8705F_DEVID)
                goto exit;
 
@@ -800,8 +871,16 @@ static int it87_detect(struct i2c_adapter *adapter, int address, int kind)
                i = it87_read_value(new_client, IT87_REG_CHIPID);
                if (i == 0x90) {
                        kind = it87;
-                       if ((is_isa) && (chip_type == IT8712F_DEVID))
-                               kind = it8712;
+                       if (is_isa) {
+                               switch (chip_type) {
+                               case IT8712F_DEVID:
+                                       kind = it8712;
+                                       break;
+                               case IT8716F_DEVID:
+                                       kind = it8716;
+                                       break;
+                               }
+                       }
                }
                else {
                        if (kind == 0)
@@ -818,6 +897,8 @@ static int it87_detect(struct i2c_adapter *adapter, int address, int kind)
                name = "it87";
        } else if (kind == it8712) {
                name = "it8712";
+       } else if (kind == it8716) {
+               name = "it8716";
        }
 
        /* Fill in the remaining client fields and put it into the global list */
@@ -885,15 +966,41 @@ static int it87_detect(struct i2c_adapter *adapter, int address, int kind)
        device_create_file(&new_client->dev, &sensor_dev_attr_temp1_type.dev_attr);
        device_create_file(&new_client->dev, &sensor_dev_attr_temp2_type.dev_attr);
        device_create_file(&new_client->dev, &sensor_dev_attr_temp3_type.dev_attr);
-       device_create_file(&new_client->dev, &sensor_dev_attr_fan1_input.dev_attr);
-       device_create_file(&new_client->dev, &sensor_dev_attr_fan2_input.dev_attr);
-       device_create_file(&new_client->dev, &sensor_dev_attr_fan3_input.dev_attr);
-       device_create_file(&new_client->dev, &sensor_dev_attr_fan1_min.dev_attr);
-       device_create_file(&new_client->dev, &sensor_dev_attr_fan2_min.dev_attr);
-       device_create_file(&new_client->dev, &sensor_dev_attr_fan3_min.dev_attr);
-       device_create_file(&new_client->dev, &sensor_dev_attr_fan1_div.dev_attr);
-       device_create_file(&new_client->dev, &sensor_dev_attr_fan2_div.dev_attr);
-       device_create_file(&new_client->dev, &sensor_dev_attr_fan3_div.dev_attr);
+
+       if (data->type == it8716) { /* 16-bit tachometers */
+               device_create_file(&new_client->dev,
+                                  &sensor_dev_attr_fan1_input16.dev_attr);
+               device_create_file(&new_client->dev,
+                                  &sensor_dev_attr_fan2_input16.dev_attr);
+               device_create_file(&new_client->dev,
+                                  &sensor_dev_attr_fan3_input16.dev_attr);
+               device_create_file(&new_client->dev,
+                                  &sensor_dev_attr_fan1_min16.dev_attr);
+               device_create_file(&new_client->dev,
+                                  &sensor_dev_attr_fan2_min16.dev_attr);
+               device_create_file(&new_client->dev,
+                                  &sensor_dev_attr_fan3_min16.dev_attr);
+       } else {
+               device_create_file(&new_client->dev,
+                                  &sensor_dev_attr_fan1_input.dev_attr);
+               device_create_file(&new_client->dev,
+                                  &sensor_dev_attr_fan2_input.dev_attr);
+               device_create_file(&new_client->dev,
+                                  &sensor_dev_attr_fan3_input.dev_attr);
+               device_create_file(&new_client->dev,
+                                  &sensor_dev_attr_fan1_min.dev_attr);
+               device_create_file(&new_client->dev,
+                                  &sensor_dev_attr_fan2_min.dev_attr);
+               device_create_file(&new_client->dev,
+                                  &sensor_dev_attr_fan3_min.dev_attr);
+               device_create_file(&new_client->dev,
+                                  &sensor_dev_attr_fan1_div.dev_attr);
+               device_create_file(&new_client->dev,
+                                  &sensor_dev_attr_fan2_div.dev_attr);
+               device_create_file(&new_client->dev,
+                                  &sensor_dev_attr_fan3_div.dev_attr);
+       }
+
        device_create_file(&new_client->dev, &dev_attr_alarms);
        if (enable_pwm_interface) {
                device_create_file(&new_client->dev, &sensor_dev_attr_pwm1_enable.dev_attr);
@@ -904,7 +1011,7 @@ static int it87_detect(struct i2c_adapter *adapter, int address, int kind)
                device_create_file(&new_client->dev, &sensor_dev_attr_pwm3.dev_attr);
        }
 
-       if (data->type == it8712) {
+       if (data->type == it8712 || data->type == it8716) {
                data->vrm = vid_which_vrm();
                device_create_file_vrm(new_client);
                device_create_file_vid(new_client);
@@ -1069,6 +1176,17 @@ static void it87_init_client(struct i2c_client *client, struct it87_data *data)
                it87_write_value(client, IT87_REG_FAN_MAIN_CTRL, data->fan_main_ctrl);
        }
 
+       /* Set tachometers to 16-bit mode if needed */
+       if (data->type == it8716) {
+               tmp = it87_read_value(client, IT87_REG_FAN_16BIT);
+               if ((tmp & 0x07) != 0x07) {
+                       dev_dbg(&client->dev,
+                               "Setting fan1-3 to 16-bit mode\n");
+                       it87_write_value(client, IT87_REG_FAN_16BIT,
+                                        tmp | 0x07);
+               }
+       }
+
        /* Set current fan mode registers and the default settings for the
         * other mode registers */
        for (i = 0; i < 3; i++) {
@@ -1126,10 +1244,17 @@ static struct it87_data *it87_update_device(struct device *dev)
                data->in_max[8] = 255;
 
                for (i = 0; i < 3; i++) {
-                       data->fan[i] =
-                           it87_read_value(client, IT87_REG_FAN(i));
                        data->fan_min[i] =
                            it87_read_value(client, IT87_REG_FAN_MIN(i));
+                       data->fan[i] = it87_read_value(client,
+                                      IT87_REG_FAN(i));
+                       /* Add high byte if in 16-bit mode */
+                       if (data->type == it8716) {
+                               data->fan[i] |= it87_read_value(client,
+                                               IT87_REG_FANX(i)) << 8;
+                               data->fan_min[i] |= it87_read_value(client,
+                                               IT87_REG_FANX_MIN(i)) << 8;
+                       }
                }
                for (i = 0; i < 3; i++) {
                        data->temp[i] =
@@ -1140,10 +1265,13 @@ static struct it87_data *it87_update_device(struct device *dev)
                            it87_read_value(client, IT87_REG_TEMP_LOW(i));
                }
 
-               i = it87_read_value(client, IT87_REG_FAN_DIV);
-               data->fan_div[0] = i & 0x07;
-               data->fan_div[1] = (i >> 3) & 0x07;
-               data->fan_div[2] = (i & 0x40) ? 3 : 1;
+               /* Newer chips don't have clock dividers */
+               if (data->type != it8716) {
+                       i = it87_read_value(client, IT87_REG_FAN_DIV);
+                       data->fan_div[0] = i & 0x07;
+                       data->fan_div[1] = (i >> 3) & 0x07;
+                       data->fan_div[2] = (i & 0x40) ? 3 : 1;
+               }
 
                data->alarms =
                        it87_read_value(client, IT87_REG_ALARM1) |
@@ -1153,9 +1281,11 @@ static struct it87_data *it87_update_device(struct device *dev)
 
                data->sensor = it87_read_value(client, IT87_REG_TEMP_ENABLE);
                /* The 8705 does not have VID capability */
-               if (data->type == it8712) {
+               if (data->type == it8712 || data->type == it8716) {
                        data->vid = it87_read_value(client, IT87_REG_VID);
-                       data->vid &= 0x1f;
+                       /* The older IT8712F revisions had only 5 VID pins,
+                          but we assume it is always safe to read 6 bits. */
+                       data->vid &= 0x3f;
                }
                data->last_updated = jiffies;
                data->valid = 1;
@@ -1194,7 +1324,7 @@ static void __exit sm_it87_exit(void)
 
 
 MODULE_AUTHOR("Chris Gauthron <chrisg@0-in.com>");
-MODULE_DESCRIPTION("IT8705F, IT8712F, Sis950 driver");
+MODULE_DESCRIPTION("IT8705F/8712F/8716F, SiS950 driver");
 module_param(update_vbat, bool, 0);
 MODULE_PARM_DESC(update_vbat, "Update vbat if set else return powerup value");
 module_param(fix_pwm_polarity, bool, 0);