]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/commitdiff
hwmon/f71805f: Add support for the Fintek F71872F/FG chip
authorJean Delvare <khali@linux-fr.org>
Tue, 12 Dec 2006 17:18:29 +0000 (18:18 +0100)
committerJean Delvare <khali@arrakis.delvare>
Tue, 12 Dec 2006 17:18:29 +0000 (18:18 +0100)
Add support for the Fintek F71872F/FG Super-I/O chip. It is basically the
same as the Fintek F71805F/FG as far as hardware monitoring is concerned,
with two additional internal voltages monitored (VSB and battery), and 6
VID inputs (not yet supported.)

To make things a bit more confusing, two of the voltage input pins (in4
and in8) can be used for other functions. The driver reads the pin
configuration from the Super-I/O configuration space to decide whether
it must create interface files for these inputs or not.

Many thanks to Nikolay Derkach for testing the early iterations of this
code and reporting bugs.

Signed-off-by: Jean Delvare <khali@linux-fr.org>
Documentation/hwmon/f71805f
drivers/hwmon/Kconfig
drivers/hwmon/f71805f.c

index bc571f99d0ee7fb1463e0bc88349928c7969ec09..bfd0f154959cce0e3398e899e7b5a0619f2abcce 100644 (file)
@@ -6,6 +6,10 @@ Supported chips:
     Prefix: 'f71805f'
     Addresses scanned: none, address read from Super I/O config space
     Datasheet: Provided by Fintek on request
+  * Fintek F71872F/FG
+    Prefix: 'f71872f'
+    Addresses scanned: none, address read from Super I/O config space
+    Datasheet: Provided by Fintek on request
 
 Author: Jean Delvare <khali@linux-fr.org>
 
@@ -30,6 +34,10 @@ source), 3 fans and 3 temperature sensors.
 This chip also has fan controlling features, using either DC or PWM, in
 three different modes (one manual, two automatic).
 
+The Fintek F71872F/FG Super I/O chip is almost the same, with two
+additional internal voltages monitored (VSB and battery). It also features
+6 VID inputs. The VID inputs are not yet supported by this driver.
+
 The driver assumes that no more than one chip is present, which seems
 reasonable.
 
@@ -41,7 +49,8 @@ Voltages are sampled by an 8-bit ADC with a LSB of 8 mV. The supported
 range is thus from 0 to 2.040 V. Voltage values outside of this range
 need external resistors. An exception is in0, which is used to monitor
 the chip's own power source (+3.3V), and is divided internally by a
-factor 2.
+factor 2. For the F71872F/FG, in9 (VSB) and in10 (battery) are also
+divided internally by a factor 2.
 
 The two LSB of the voltage limit registers are not used (always 0), so
 you can only set the limits in steps of 32 mV (before scaling).
@@ -60,9 +69,12 @@ in5     VIN5    +12V        200K     20K       11.00    1.05 V
 in6     VIN6    VCC1.5V      10K       -        1.00    1.50 V
 in7     VIN7    VCORE        10K       -        1.00   ~1.40 V (1)
 in8     VIN8    VSB5V       200K     47K        1.00    0.95 V
+in10    VSB     VSB3.3V     int.    int.        2.00    1.65 V (3)
+in9     VBAT    VBATTERY    int.    int.        2.00    1.50 V (3)
 
 (1) Depends on your hardware setup.
 (2) Obviously not correct, swapping R1 and R2 would make more sense.
+(3) F71872F/FG only.
 
 These values can be used as hints at best, as motherboard manufacturers
 are free to use a completely different setup. As a matter of fact, the
index e379ac41c09c6708446f7e805d5bf0e6d26a0978..5a0763197e21a2a0542927b6fe77b16b5bc1f461 100644 (file)
@@ -142,11 +142,12 @@ config SENSORS_DS1621
          will be called ds1621.
 
 config SENSORS_F71805F
-       tristate "Fintek F71805F/FG"
+       tristate "Fintek F71805F/FG and F71872F/FG"
        depends on HWMON && EXPERIMENTAL
        help
          If you say yes here you get support for hardware monitoring
-         features of the Fintek F71805F/FG chips.
+         features of the Fintek F71805F/FG and F71872F/FG Super-I/O
+         chips.
 
          This driver can also be built as a module.  If so, the module
          will be called f71805f.
index 975c1cc2acf1217b5ed005f7b5ad464be9bfb541..ba5e7b70f8cc0f8aa6e2137e1ecac240c9fdd30b 100644 (file)
@@ -1,12 +1,15 @@
 /*
- * f71805f.c - driver for the Fintek F71805F/FG Super-I/O chip integrated
- *             hardware monitoring features
+ * f71805f.c - driver for the Fintek F71805F/FG and F71872F/FG Super-I/O
+ *             chips integrated hardware monitoring features
  * Copyright (C) 2005-2006  Jean Delvare <khali@linux-fr.org>
  *
  * The F71805F/FG is a LPC Super-I/O chip made by Fintek. It integrates
  * complete hardware monitoring features: voltage, fan and temperature
  * sensors, and manual and automatic fan speed control.
  *
+ * The F71872F/FG is almost the same, with two more voltages monitored,
+ * and 6 VID inputs.
+ *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
@@ -37,6 +40,7 @@
 static struct platform_device *pdev;
 
 #define DRVNAME "f71805f"
+enum kinds { f71805f, f71872f };
 
 /*
  * Super-I/O constants and functions
@@ -48,11 +52,13 @@ static struct platform_device *pdev;
 #define SIO_REG_DEVID          0x20    /* Device ID (2 bytes) */
 #define SIO_REG_DEVREV         0x22    /* Device revision */
 #define SIO_REG_MANID          0x23    /* Fintek ID (2 bytes) */
+#define SIO_REG_FNSEL1         0x29    /* Multi Function Select 1 (F71872F) */
 #define SIO_REG_ENABLE         0x30    /* Logical device enable */
 #define SIO_REG_ADDR           0x60    /* Logical device address (2 bytes) */
 
 #define SIO_FINTEK_ID          0x1934
 #define SIO_F71805F_ID         0x0406
+#define SIO_F71872F_ID         0x0341
 
 static inline int
 superio_inb(int base, int reg)
@@ -104,10 +110,10 @@ superio_exit(int base)
  * Registers
  */
 
-/* in nr from 0 to 8 (8-bit values) */
+/* in nr from 0 to 10 (8-bit values) */
 #define F71805F_REG_IN(nr)             (0x10 + (nr))
-#define F71805F_REG_IN_HIGH(nr)                (0x40 + 2 * (nr))
-#define F71805F_REG_IN_LOW(nr)         (0x41 + 2 * (nr))
+#define F71805F_REG_IN_HIGH(nr)                ((nr) < 10 ? 0x40 + 2 * (nr) : 0x2E)
+#define F71805F_REG_IN_LOW(nr)         ((nr) < 10 ? 0x41 + 2 * (nr) : 0x2F)
 /* fan nr from 0 to 2 (12-bit values, two registers) */
 #define F71805F_REG_FAN(nr)            (0x20 + 2 * (nr))
 #define F71805F_REG_FAN_LOW(nr)                (0x28 + 2 * (nr))
@@ -150,9 +156,10 @@ struct f71805f_data {
        unsigned long last_limits;      /* In jiffies */
 
        /* Register values */
-       u8 in[9];
-       u8 in_high[9];
-       u8 in_low[9];
+       u8 in[11];
+       u8 in_high[11];
+       u8 in_low[11];
+       u16 has_in;
        u16 fan[3];
        u16 fan_low[3];
        u16 fan_target[3];
@@ -166,6 +173,11 @@ struct f71805f_data {
        unsigned long alarms;
 };
 
+struct f71805f_sio_data {
+       enum kinds kind;
+       u8 fnsel1;
+};
+
 static inline long in_from_reg(u8 reg)
 {
        return (reg * 8);
@@ -316,7 +328,9 @@ static struct f71805f_data *f71805f_update_device(struct device *dev)
        /* Limit registers cache is refreshed after 60 seconds */
        if (time_after(jiffies, data->last_updated + 60 * HZ)
         || !data->valid) {
-               for (nr = 0; nr < 9; nr++) {
+               for (nr = 0; nr < 11; nr++) {
+                       if (!(data->has_in & (1 << nr)))
+                               continue;
                        data->in_high[nr] = f71805f_read8(data,
                                            F71805F_REG_IN_HIGH(nr));
                        data->in_low[nr] = f71805f_read8(data,
@@ -346,7 +360,9 @@ static struct f71805f_data *f71805f_update_device(struct device *dev)
        /* Measurement registers cache is refreshed after 1 second */
        if (time_after(jiffies, data->last_updated + HZ)
         || !data->valid) {
-               for (nr = 0; nr < 9; nr++) {
+               for (nr = 0; nr < 11; nr++) {
+                       if (!(data->has_in & (1 << nr)))
+                               continue;
                        data->in[nr] = f71805f_read8(data,
                                       F71805F_REG_IN(nr));
                }
@@ -385,35 +401,43 @@ static ssize_t show_in0(struct device *dev, struct device_attribute *devattr,
                        char *buf)
 {
        struct f71805f_data *data = f71805f_update_device(dev);
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+       int nr = attr->index;
 
-       return sprintf(buf, "%ld\n", in0_from_reg(data->in[0]));
+       return sprintf(buf, "%ld\n", in0_from_reg(data->in[nr]));
 }
 
 static ssize_t show_in0_max(struct device *dev, struct device_attribute
                            *devattr, char *buf)
 {
        struct f71805f_data *data = f71805f_update_device(dev);
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+       int nr = attr->index;
 
-       return sprintf(buf, "%ld\n", in0_from_reg(data->in_high[0]));
+       return sprintf(buf, "%ld\n", in0_from_reg(data->in_high[nr]));
 }
 
 static ssize_t show_in0_min(struct device *dev, struct device_attribute
                            *devattr, char *buf)
 {
        struct f71805f_data *data = f71805f_update_device(dev);
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+       int nr = attr->index;
 
-       return sprintf(buf, "%ld\n", in0_from_reg(data->in_low[0]));
+       return sprintf(buf, "%ld\n", in0_from_reg(data->in_low[nr]));
 }
 
 static ssize_t set_in0_max(struct device *dev, struct device_attribute
                           *devattr, const char *buf, size_t count)
 {
        struct f71805f_data *data = dev_get_drvdata(dev);
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+       int nr = attr->index;
        long val = simple_strtol(buf, NULL, 10);
 
        mutex_lock(&data->update_lock);
-       data->in_high[0] = in0_to_reg(val);
-       f71805f_write8(data, F71805F_REG_IN_HIGH(0), data->in_high[0]);
+       data->in_high[nr] = in0_to_reg(val);
+       f71805f_write8(data, F71805F_REG_IN_HIGH(nr), data->in_high[nr]);
        mutex_unlock(&data->update_lock);
 
        return count;
@@ -423,11 +447,13 @@ static ssize_t set_in0_min(struct device *dev, struct device_attribute
                           *devattr, const char *buf, size_t count)
 {
        struct f71805f_data *data = dev_get_drvdata(dev);
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+       int nr = attr->index;
        long val = simple_strtol(buf, NULL, 10);
 
        mutex_lock(&data->update_lock);
-       data->in_low[0] = in0_to_reg(val);
-       f71805f_write8(data, F71805F_REG_IN_LOW(0), data->in_low[0]);
+       data->in_low[nr] = in0_to_reg(val);
+       f71805f_write8(data, F71805F_REG_IN_LOW(nr), data->in_low[nr]);
        mutex_unlock(&data->update_lock);
 
        return count;
@@ -770,7 +796,7 @@ static ssize_t show_alarms_in(struct device *dev, struct device_attribute
 {
        struct f71805f_data *data = f71805f_update_device(dev);
 
-       return sprintf(buf, "%lu\n", data->alarms & 0x1ff);
+       return sprintf(buf, "%lu\n", data->alarms & 0x7ff);
 }
 
 static ssize_t show_alarms_fan(struct device *dev, struct device_attribute
@@ -807,9 +833,11 @@ static ssize_t show_name(struct device *dev, struct device_attribute
        return sprintf(buf, "%s\n", data->name);
 }
 
-static DEVICE_ATTR(in0_input, S_IRUGO, show_in0, NULL);
-static DEVICE_ATTR(in0_max, S_IRUGO| S_IWUSR, show_in0_max, set_in0_max);
-static DEVICE_ATTR(in0_min, S_IRUGO| S_IWUSR, show_in0_min, set_in0_min);
+static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_in0, NULL, 0);
+static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO| S_IWUSR,
+                         show_in0_max, set_in0_max, 0);
+static SENSOR_DEVICE_ATTR(in0_min, S_IRUGO| S_IWUSR,
+                         show_in0_min, set_in0_min, 0);
 static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in, NULL, 1);
 static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR,
                          show_in_max, set_in_max, 1);
@@ -850,6 +878,16 @@ static SENSOR_DEVICE_ATTR(in8_max, S_IRUGO | S_IWUSR,
                          show_in_max, set_in_max, 8);
 static SENSOR_DEVICE_ATTR(in8_min, S_IRUGO | S_IWUSR,
                          show_in_min, set_in_min, 8);
+static SENSOR_DEVICE_ATTR(in9_input, S_IRUGO, show_in0, NULL, 9);
+static SENSOR_DEVICE_ATTR(in9_max, S_IRUGO | S_IWUSR,
+                         show_in0_max, set_in0_max, 9);
+static SENSOR_DEVICE_ATTR(in9_min, S_IRUGO | S_IWUSR,
+                         show_in0_min, set_in0_min, 9);
+static SENSOR_DEVICE_ATTR(in10_input, S_IRUGO, show_in0, NULL, 10);
+static SENSOR_DEVICE_ATTR(in10_max, S_IRUGO | S_IWUSR,
+                         show_in0_max, set_in0_max, 10);
+static SENSOR_DEVICE_ATTR(in10_min, S_IRUGO | S_IWUSR,
+                         show_in0_min, set_in0_min, 10);
 
 static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0);
 static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR,
@@ -916,6 +954,8 @@ static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 5);
 static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 6);
 static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 7);
 static SENSOR_DEVICE_ATTR(in8_alarm, S_IRUGO, show_alarm, NULL, 8);
+static SENSOR_DEVICE_ATTR(in9_alarm, S_IRUGO, show_alarm, NULL, 9);
+static SENSOR_DEVICE_ATTR(in10_alarm, S_IRUGO, show_alarm, NULL, 10);
 static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 11);
 static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 12);
 static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 13);
@@ -929,9 +969,9 @@ static DEVICE_ATTR(alarms_temp, S_IRUGO, show_alarms_temp, NULL);
 static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
 
 static struct attribute *f71805f_attributes[] = {
-       &dev_attr_in0_input.attr,
-       &dev_attr_in0_max.attr,
-       &dev_attr_in0_min.attr,
+       &sensor_dev_attr_in0_input.dev_attr.attr,
+       &sensor_dev_attr_in0_max.dev_attr.attr,
+       &sensor_dev_attr_in0_min.dev_attr.attr,
        &sensor_dev_attr_in1_input.dev_attr.attr,
        &sensor_dev_attr_in1_max.dev_attr.attr,
        &sensor_dev_attr_in1_min.dev_attr.attr,
@@ -941,9 +981,6 @@ static struct attribute *f71805f_attributes[] = {
        &sensor_dev_attr_in3_input.dev_attr.attr,
        &sensor_dev_attr_in3_max.dev_attr.attr,
        &sensor_dev_attr_in3_min.dev_attr.attr,
-       &sensor_dev_attr_in4_input.dev_attr.attr,
-       &sensor_dev_attr_in4_max.dev_attr.attr,
-       &sensor_dev_attr_in4_min.dev_attr.attr,
        &sensor_dev_attr_in5_input.dev_attr.attr,
        &sensor_dev_attr_in5_max.dev_attr.attr,
        &sensor_dev_attr_in5_min.dev_attr.attr,
@@ -953,9 +990,6 @@ static struct attribute *f71805f_attributes[] = {
        &sensor_dev_attr_in7_input.dev_attr.attr,
        &sensor_dev_attr_in7_max.dev_attr.attr,
        &sensor_dev_attr_in7_min.dev_attr.attr,
-       &sensor_dev_attr_in8_input.dev_attr.attr,
-       &sensor_dev_attr_in8_max.dev_attr.attr,
-       &sensor_dev_attr_in8_min.dev_attr.attr,
 
        &sensor_dev_attr_temp1_input.dev_attr.attr,
        &sensor_dev_attr_temp1_max.dev_attr.attr,
@@ -974,11 +1008,9 @@ static struct attribute *f71805f_attributes[] = {
        &sensor_dev_attr_in1_alarm.dev_attr.attr,
        &sensor_dev_attr_in2_alarm.dev_attr.attr,
        &sensor_dev_attr_in3_alarm.dev_attr.attr,
-       &sensor_dev_attr_in4_alarm.dev_attr.attr,
        &sensor_dev_attr_in5_alarm.dev_attr.attr,
        &sensor_dev_attr_in6_alarm.dev_attr.attr,
        &sensor_dev_attr_in7_alarm.dev_attr.attr,
-       &sensor_dev_attr_in8_alarm.dev_attr.attr,
        &dev_attr_alarms_in.attr,
        &sensor_dev_attr_temp1_alarm.dev_attr.attr,
        &sensor_dev_attr_temp2_alarm.dev_attr.attr,
@@ -994,6 +1026,41 @@ static const struct attribute_group f71805f_group = {
        .attrs = f71805f_attributes,
 };
 
+static struct attribute *f71805f_attributes_optin[4][5] = {
+       {
+               &sensor_dev_attr_in4_input.dev_attr.attr,
+               &sensor_dev_attr_in4_max.dev_attr.attr,
+               &sensor_dev_attr_in4_min.dev_attr.attr,
+               &sensor_dev_attr_in4_alarm.dev_attr.attr,
+               NULL
+       }, {
+               &sensor_dev_attr_in8_input.dev_attr.attr,
+               &sensor_dev_attr_in8_max.dev_attr.attr,
+               &sensor_dev_attr_in8_min.dev_attr.attr,
+               &sensor_dev_attr_in8_alarm.dev_attr.attr,
+               NULL
+       }, {
+               &sensor_dev_attr_in9_input.dev_attr.attr,
+               &sensor_dev_attr_in9_max.dev_attr.attr,
+               &sensor_dev_attr_in9_min.dev_attr.attr,
+               &sensor_dev_attr_in9_alarm.dev_attr.attr,
+               NULL
+       }, {
+               &sensor_dev_attr_in10_input.dev_attr.attr,
+               &sensor_dev_attr_in10_max.dev_attr.attr,
+               &sensor_dev_attr_in10_min.dev_attr.attr,
+               &sensor_dev_attr_in10_alarm.dev_attr.attr,
+               NULL
+       }
+};
+
+static const struct attribute_group f71805f_group_optin[4] = {
+       { .attrs = f71805f_attributes_optin[0] },
+       { .attrs = f71805f_attributes_optin[1] },
+       { .attrs = f71805f_attributes_optin[2] },
+       { .attrs = f71805f_attributes_optin[3] },
+};
+
 static struct attribute *f71805f_attributes_fan[3][8] = {
        {
                &sensor_dev_attr_fan1_input.dev_attr.attr,
@@ -1084,10 +1151,16 @@ static void __devinit f71805f_init_device(struct f71805f_data *data)
 
 static int __devinit f71805f_probe(struct platform_device *pdev)
 {
+       struct f71805f_sio_data *sio_data = pdev->dev.platform_data;
        struct f71805f_data *data;
        struct resource *res;
        int i, err;
 
+       static const char *names[] = {
+               "f71805f",
+               "f71872f",
+       };
+
        if (!(data = kzalloc(sizeof(struct f71805f_data), GFP_KERNEL))) {
                err = -ENOMEM;
                printk(KERN_ERR DRVNAME ": Out of memory\n");
@@ -1097,17 +1170,51 @@ static int __devinit f71805f_probe(struct platform_device *pdev)
        res = platform_get_resource(pdev, IORESOURCE_IO, 0);
        data->addr = res->start;
        mutex_init(&data->lock);
-       data->name = "f71805f";
+       data->name = names[sio_data->kind];
        mutex_init(&data->update_lock);
 
        platform_set_drvdata(pdev, data);
 
+       /* Some voltage inputs depend on chip model and configuration */
+       switch (sio_data->kind) {
+       case f71805f:
+               data->has_in = 0x1ff;
+               break;
+       case f71872f:
+               data->has_in = 0x6ef;
+               if (sio_data->fnsel1 & 0x01)
+                       data->has_in |= (1 << 4); /* in4 */
+               if (sio_data->fnsel1 & 0x02)
+                       data->has_in |= (1 << 8); /* in8 */
+               break;
+       }
+
        /* Initialize the F71805F chip */
        f71805f_init_device(data);
 
        /* Register sysfs interface files */
        if ((err = sysfs_create_group(&pdev->dev.kobj, &f71805f_group)))
                goto exit_free;
+       if (data->has_in & (1 << 4)) { /* in4 */
+               if ((err = sysfs_create_group(&pdev->dev.kobj,
+                                             &f71805f_group_optin[0])))
+                       goto exit_remove_files;
+       }
+       if (data->has_in & (1 << 8)) { /* in8 */
+               if ((err = sysfs_create_group(&pdev->dev.kobj,
+                                             &f71805f_group_optin[1])))
+                       goto exit_remove_files;
+       }
+       if (data->has_in & (1 << 9)) { /* in9 (F71872F/FG only) */
+               if ((err = sysfs_create_group(&pdev->dev.kobj,
+                                             &f71805f_group_optin[2])))
+                       goto exit_remove_files;
+       }
+       if (data->has_in & (1 << 10)) { /* in9 (F71872F/FG only) */
+               if ((err = sysfs_create_group(&pdev->dev.kobj,
+                                             &f71805f_group_optin[3])))
+                       goto exit_remove_files;
+       }
        for (i = 0; i < 3; i++) {
                if (data->fan_ctrl[i] & FAN_CTRL_SKIP)
                        continue;
@@ -1143,6 +1250,8 @@ static int __devinit f71805f_probe(struct platform_device *pdev)
 
 exit_remove_files:
        sysfs_remove_group(&pdev->dev.kobj, &f71805f_group);
+       for (i = 0; i < 4; i++)
+               sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_optin[i]);
        for (i = 0; i < 3; i++)
                sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_fan[i]);
        sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq);
@@ -1161,6 +1270,8 @@ static int __devexit f71805f_remove(struct platform_device *pdev)
        platform_set_drvdata(pdev, NULL);
        hwmon_device_unregister(data->class_dev);
        sysfs_remove_group(&pdev->dev.kobj, &f71805f_group);
+       for (i = 0; i < 4; i++)
+               sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_optin[i]);
        for (i = 0; i < 3; i++)
                sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_fan[i]);
        sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq);
@@ -1178,7 +1289,8 @@ static struct platform_driver f71805f_driver = {
        .remove         = __devexit_p(f71805f_remove),
 };
 
-static int __init f71805f_device_add(unsigned short address)
+static int __init f71805f_device_add(unsigned short address,
+                                    const struct f71805f_sio_data *sio_data)
 {
        struct resource res = {
                .start  = address,
@@ -1202,26 +1314,45 @@ static int __init f71805f_device_add(unsigned short address)
                goto exit_device_put;
        }
 
+       pdev->dev.platform_data = kmalloc(sizeof(struct f71805f_sio_data),
+                                         GFP_KERNEL);
+       if (!pdev->dev.platform_data) {
+               err = -ENOMEM;
+               printk(KERN_ERR DRVNAME ": Platform data allocation failed\n");
+               goto exit_device_put;
+       }
+       memcpy(pdev->dev.platform_data, sio_data,
+              sizeof(struct f71805f_sio_data));
+
        err = platform_device_add(pdev);
        if (err) {
                printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n",
                       err);
-               goto exit_device_put;
+               goto exit_kfree_data;
        }
 
        return 0;
 
+exit_kfree_data:
+       kfree(pdev->dev.platform_data);
+       pdev->dev.platform_data = NULL;
 exit_device_put:
        platform_device_put(pdev);
 exit:
        return err;
 }
 
-static int __init f71805f_find(int sioaddr, unsigned short *address)
+static int __init f71805f_find(int sioaddr, unsigned short *address,
+                              struct f71805f_sio_data *sio_data)
 {
        int err = -ENODEV;
        u16 devid;
 
+       static const char *names[] = {
+               "F71805F/FG",
+               "F71872F/FG",
+       };
+
        superio_enter(sioaddr);
 
        devid = superio_inw(sioaddr, SIO_REG_MANID);
@@ -1229,7 +1360,15 @@ static int __init f71805f_find(int sioaddr, unsigned short *address)
                goto exit;
 
        devid = superio_inw(sioaddr, SIO_REG_DEVID);
-       if (devid != SIO_F71805F_ID) {
+       switch (devid) {
+       case SIO_F71805F_ID:
+               sio_data->kind = f71805f;
+               break;
+       case SIO_F71872F_ID:
+               sio_data->kind = f71872f;
+               sio_data->fnsel1 = superio_inb(sioaddr, SIO_REG_FNSEL1);
+               break;
+       default:
                printk(KERN_INFO DRVNAME ": Unsupported Fintek device, "
                       "skipping\n");
                goto exit;
@@ -1250,8 +1389,9 @@ static int __init f71805f_find(int sioaddr, unsigned short *address)
        }
 
        err = 0;
-       printk(KERN_INFO DRVNAME ": Found F71805F chip at %#x, revision %u\n",
-              *address, superio_inb(sioaddr, SIO_REG_DEVREV));
+       printk(KERN_INFO DRVNAME ": Found %s chip at %#x, revision %u\n",
+              names[sio_data->kind], *address,
+              superio_inb(sioaddr, SIO_REG_DEVREV));
 
 exit:
        superio_exit(sioaddr);
@@ -1262,9 +1402,10 @@ static int __init f71805f_init(void)
 {
        int err;
        unsigned short address;
+       struct f71805f_sio_data sio_data;
 
-       if (f71805f_find(0x2e, &address)
-        && f71805f_find(0x4e, &address))
+       if (f71805f_find(0x2e, &address, &sio_data)
+        && f71805f_find(0x4e, &address, &sio_data))
                return -ENODEV;
 
        err = platform_driver_register(&f71805f_driver);
@@ -1272,7 +1413,7 @@ static int __init f71805f_init(void)
                goto exit;
 
        /* Sets global pdev as a side effect */
-       err = f71805f_device_add(address);
+       err = f71805f_device_add(address, &sio_data);
        if (err)
                goto exit_driver;
 
@@ -1286,13 +1427,16 @@ exit:
 
 static void __exit f71805f_exit(void)
 {
+       kfree(pdev->dev.platform_data);
+       pdev->dev.platform_data = NULL;
        platform_device_unregister(pdev);
+
        platform_driver_unregister(&f71805f_driver);
 }
 
 MODULE_AUTHOR("Jean Delvare <khali@linux-fr>");
 MODULE_LICENSE("GPL");
-MODULE_DESCRIPTION("F71805F hardware monitoring driver");
+MODULE_DESCRIPTION("F71805F/F71872F hardware monitoring driver");
 
 module_init(f71805f_init);
 module_exit(f71805f_exit);