summary refs log tree commit diff
path: root/drivers/hwmon/it87.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hwmon/it87.c')
-rw-r--r--drivers/hwmon/it87.c178
1 files changed, 154 insertions, 24 deletions
diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c
index b0ee57492228..e7f14e61d6f2 100644
--- a/drivers/hwmon/it87.c
+++ b/drivers/hwmon/it87.c
@@ -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);