hwmon: Fix off-by-one kind values
[safe/jmp/linux-2.6] / drivers / hwmon / smsc47m1.c
index 0d7f0c4..8fa462f 100644 (file)
@@ -37,7 +37,8 @@
 #include <linux/init.h>
 #include <linux/mutex.h>
 #include <linux/sysfs.h>
-#include <asm/io.h>
+#include <linux/acpi.h>
+#include <linux/io.h>
 
 static unsigned short force_id;
 module_param(force_id, ushort, 0);
@@ -85,6 +86,7 @@ superio_exit(void)
 #define SUPERIO_REG_ACT                0x30
 #define SUPERIO_REG_BASE       0x60
 #define SUPERIO_REG_DEVID      0x20
+#define SUPERIO_REG_DEVREV     0x21
 
 /* Logical device registers */
 
@@ -134,11 +136,11 @@ struct smsc47m1_data {
 
 struct smsc47m1_sio_data {
        enum chips type;
+       u8 activate;            /* Remember initial device state */
 };
 
 
-static int smsc47m1_probe(struct platform_device *pdev);
-static int __devexit smsc47m1_remove(struct platform_device *pdev);
+static int __exit smsc47m1_remove(struct platform_device *pdev);
 static struct smsc47m1_data *smsc47m1_update_device(struct device *dev,
                int init);
 
@@ -158,8 +160,7 @@ static struct platform_driver smsc47m1_driver = {
                .owner  = THIS_MODULE,
                .name   = DRVNAME,
        },
-       .probe          = smsc47m1_probe,
-       .remove         = __devexit_p(smsc47m1_remove),
+       .remove         = __exit_p(smsc47m1_remove),
 };
 
 static ssize_t get_fan(struct device *dev, struct device_attribute
@@ -198,6 +199,14 @@ static ssize_t get_fan_div(struct device *dev, struct device_attribute
        return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[attr->index]));
 }
 
+static ssize_t get_fan_alarm(struct device *dev, struct device_attribute
+                            *devattr, char *buf)
+{
+       int bitnr = to_sensor_dev_attr(devattr)->index;
+       struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
+       return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1);
+}
+
 static ssize_t get_pwm(struct device *dev, struct device_attribute
                       *devattr, char *buf)
 {
@@ -347,6 +356,8 @@ static SENSOR_DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR,             \
                get_fan_min, set_fan_min, offset - 1);                  \
 static SENSOR_DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR,                \
                get_fan_div, set_fan_div, offset - 1);                  \
+static SENSOR_DEVICE_ATTR(fan##offset##_alarm, S_IRUGO, get_fan_alarm, \
+               NULL, offset - 1);                                      \
 static SENSOR_DEVICE_ATTR(pwm##offset, S_IRUGO | S_IWUSR,              \
                get_pwm, set_pwm, offset - 1);                          \
 static SENSOR_DEVICE_ATTR(pwm##offset##_enable, S_IRUGO | S_IWUSR,     \
@@ -374,12 +385,15 @@ static struct attribute *smsc47m1_attributes[] = {
        &sensor_dev_attr_fan1_input.dev_attr.attr,
        &sensor_dev_attr_fan1_min.dev_attr.attr,
        &sensor_dev_attr_fan1_div.dev_attr.attr,
+       &sensor_dev_attr_fan1_alarm.dev_attr.attr,
        &sensor_dev_attr_fan2_input.dev_attr.attr,
        &sensor_dev_attr_fan2_min.dev_attr.attr,
        &sensor_dev_attr_fan2_div.dev_attr.attr,
+       &sensor_dev_attr_fan2_alarm.dev_attr.attr,
        &sensor_dev_attr_fan3_input.dev_attr.attr,
        &sensor_dev_attr_fan3_min.dev_attr.attr,
        &sensor_dev_attr_fan3_div.dev_attr.attr,
+       &sensor_dev_attr_fan3_alarm.dev_attr.attr,
 
        &sensor_dev_attr_pwm1.dev_attr.attr,
        &sensor_dev_attr_pwm1_enable.dev_attr.attr,
@@ -415,6 +429,9 @@ static int __init smsc47m1_find(unsigned short *addr,
         * The LPC47M292 (device id 0x6B) is somewhat compatible, but it
         * supports a 3rd fan, and the pin configuration registers are
         * unfortunately different.
+        * The LPC47M233 has the same device id (0x6B) but is not compatible.
+        * We check the high bit of the device revision register to
+        * differentiate them.
         */
        switch (val) {
        case 0x51:
@@ -434,6 +451,13 @@ static int __init smsc47m1_find(unsigned short *addr,
                sio_data->type = smsc47m1;
                break;
        case 0x6B:
+               if (superio_inb(SUPERIO_REG_DEVREV) & 0x80) {
+                       pr_debug(DRVNAME ": "
+                                "Found SMSC LPC47M233, unsupported\n");
+                       superio_exit();
+                       return -ENODEV;
+               }
+
                pr_info(DRVNAME ": Found SMSC LPC47M292\n");
                sio_data->type = smsc47m2;
                break;
@@ -445,24 +469,126 @@ static int __init smsc47m1_find(unsigned short *addr,
        superio_select();
        *addr = (superio_inb(SUPERIO_REG_BASE) << 8)
              |  superio_inb(SUPERIO_REG_BASE + 1);
-       val = superio_inb(SUPERIO_REG_ACT);
-       if (*addr == 0 || (val & 0x01) == 0) {
-               pr_info(DRVNAME ": Device is disabled, will not use\n");
+       if (*addr == 0) {
+               pr_info(DRVNAME ": Device address not set, will not use\n");
                superio_exit();
                return -ENODEV;
        }
 
+       /* Enable only if address is set (needed at least on the
+        * Compaq Presario S4000NX) */
+       sio_data->activate = superio_inb(SUPERIO_REG_ACT);
+       if ((sio_data->activate & 0x01) == 0) {
+               pr_info(DRVNAME ": Enabling device\n");
+               superio_outb(SUPERIO_REG_ACT, sio_data->activate | 0x01);
+       }
+
        superio_exit();
        return 0;
 }
 
-static int __devinit smsc47m1_probe(struct platform_device *pdev)
+/* Restore device to its initial state */
+static void smsc47m1_restore(const struct smsc47m1_sio_data *sio_data)
+{
+       if ((sio_data->activate & 0x01) == 0) {
+               superio_enter();
+               superio_select();
+
+               pr_info(DRVNAME ": Disabling device\n");
+               superio_outb(SUPERIO_REG_ACT, sio_data->activate);
+
+               superio_exit();
+       }
+}
+
+#define CHECK          1
+#define REQUEST                2
+#define RELEASE                3
+
+/*
+ * This function can be used to:
+ *  - test for resource conflicts with ACPI
+ *  - request the resources
+ *  - release the resources
+ * We only allocate the I/O ports we really need, to minimize the risk of
+ * conflicts with ACPI or with other drivers.
+ */
+static int smsc47m1_handle_resources(unsigned short address, enum chips type,
+                                    int action, struct device *dev)
+{
+       static const u8 ports_m1[] = {
+               /* register, region length */
+               0x04, 1,
+               0x33, 4,
+               0x56, 7,
+       };
+
+       static const u8 ports_m2[] = {
+               /* register, region length */
+               0x04, 1,
+               0x09, 1,
+               0x2c, 2,
+               0x35, 4,
+               0x56, 7,
+               0x69, 4,
+       };
+
+       int i, ports_size, err;
+       const u8 *ports;
+
+       switch (type) {
+       case smsc47m1:
+       default:
+               ports = ports_m1;
+               ports_size = ARRAY_SIZE(ports_m1);
+               break;
+       case smsc47m2:
+               ports = ports_m2;
+               ports_size = ARRAY_SIZE(ports_m2);
+               break;
+       }
+
+       for (i = 0; i + 1 < ports_size; i += 2) {
+               unsigned short start = address + ports[i];
+               unsigned short len = ports[i + 1];
+
+               switch (action) {
+               case CHECK:
+                       /* Only check for conflicts */
+                       err = acpi_check_region(start, len, DRVNAME);
+                       if (err)
+                               return err;
+                       break;
+               case REQUEST:
+                       /* Request the resources */
+                       if (!request_region(start, len, DRVNAME)) {
+                               dev_err(dev, "Region 0x%hx-0x%hx already in "
+                                       "use!\n", start, start + len);
+
+                               /* Undo all requests */
+                               for (i -= 2; i >= 0; i -= 2)
+                                       release_region(address + ports[i],
+                                                      ports[i + 1]);
+                               return -EBUSY;
+                       }
+                       break;
+               case RELEASE:
+                       /* Release the resources */
+                       release_region(start, len);
+                       break;
+               }
+       }
+
+       return 0;
+}
+
+static int __init smsc47m1_probe(struct platform_device *pdev)
 {
        struct device *dev = &pdev->dev;
        struct smsc47m1_sio_data *sio_data = dev->platform_data;
        struct smsc47m1_data *data;
        struct resource *res;
-       int err = 0;
+       int err;
        int fan1, fan2, fan3, pwm1, pwm2, pwm3;
 
        static const char *names[] = {
@@ -471,12 +597,10 @@ static int __devinit smsc47m1_probe(struct platform_device *pdev)
        };
 
        res = platform_get_resource(pdev, IORESOURCE_IO, 0);
-       if (!request_region(res->start, SMSC_EXTENT, DRVNAME)) {
-               dev_err(dev, "Region 0x%lx-0x%lx already in use!\n",
-                       (unsigned long)res->start,
-                       (unsigned long)res->end);
-               return -EBUSY;
-       }
+       err = smsc47m1_handle_resources(res->start, sio_data->type,
+                                       REQUEST, dev);
+       if (err < 0)
+               return err;
 
        if (!(data = kzalloc(sizeof(struct smsc47m1_data), GFP_KERNEL))) {
                err = -ENOMEM;
@@ -533,7 +657,9 @@ static int __devinit smsc47m1_probe(struct platform_device *pdev)
                 || (err = device_create_file(dev,
                                &sensor_dev_attr_fan1_min.dev_attr))
                 || (err = device_create_file(dev,
-                               &sensor_dev_attr_fan1_div.dev_attr)))
+                               &sensor_dev_attr_fan1_div.dev_attr))
+                || (err = device_create_file(dev,
+                               &sensor_dev_attr_fan1_alarm.dev_attr)))
                        goto error_remove_files;
        } else
                dev_dbg(dev, "Fan 1 not enabled by hardware, skipping\n");
@@ -544,7 +670,9 @@ static int __devinit smsc47m1_probe(struct platform_device *pdev)
                 || (err = device_create_file(dev,
                                &sensor_dev_attr_fan2_min.dev_attr))
                 || (err = device_create_file(dev,
-                               &sensor_dev_attr_fan2_div.dev_attr)))
+                               &sensor_dev_attr_fan2_div.dev_attr))
+                || (err = device_create_file(dev,
+                               &sensor_dev_attr_fan2_alarm.dev_attr)))
                        goto error_remove_files;
        } else
                dev_dbg(dev, "Fan 2 not enabled by hardware, skipping\n");
@@ -555,7 +683,9 @@ static int __devinit smsc47m1_probe(struct platform_device *pdev)
                 || (err = device_create_file(dev,
                                &sensor_dev_attr_fan3_min.dev_attr))
                 || (err = device_create_file(dev,
-                               &sensor_dev_attr_fan3_div.dev_attr)))
+                               &sensor_dev_attr_fan3_div.dev_attr))
+                || (err = device_create_file(dev,
+                               &sensor_dev_attr_fan3_alarm.dev_attr)))
                        goto error_remove_files;
        } else if (data->type == smsc47m2)
                dev_dbg(dev, "Fan 3 not enabled by hardware, skipping\n");
@@ -606,11 +736,11 @@ error_free:
        platform_set_drvdata(pdev, NULL);
        kfree(data);
 error_release:
-       release_region(res->start, SMSC_EXTENT);
+       smsc47m1_handle_resources(res->start, sio_data->type, RELEASE, dev);
        return err;
 }
 
-static int __devexit smsc47m1_remove(struct platform_device *pdev)
+static int __exit smsc47m1_remove(struct platform_device *pdev)
 {
        struct smsc47m1_data *data = platform_get_drvdata(pdev);
        struct resource *res;
@@ -619,7 +749,7 @@ static int __devexit smsc47m1_remove(struct platform_device *pdev)
        sysfs_remove_group(&pdev->dev.kobj, &smsc47m1_group);
 
        res = platform_get_resource(pdev, IORESOURCE_IO, 0);
-       release_region(res->start, SMSC_EXTENT);
+       smsc47m1_handle_resources(res->start, data->type, RELEASE, &pdev->dev);
        platform_set_drvdata(pdev, NULL);
        kfree(data);
 
@@ -686,6 +816,10 @@ static int __init smsc47m1_device_add(unsigned short address,
        };
        int err;
 
+       err = smsc47m1_handle_resources(address, sio_data->type, CHECK, NULL);
+       if (err)
+               goto exit;
+
        pdev = platform_device_alloc(DRVNAME, address);
        if (!pdev) {
                err = -ENOMEM;
@@ -731,27 +865,29 @@ static int __init sm_smsc47m1_init(void)
        if (smsc47m1_find(&address, &sio_data))
                return -ENODEV;
 
-       err = platform_driver_register(&smsc47m1_driver);
+       /* Sets global pdev as a side effect */
+       err = smsc47m1_device_add(address, &sio_data);
        if (err)
                goto exit;
 
-       /* Sets global pdev as a side effect */
-       err = smsc47m1_device_add(address, &sio_data);
+       err = platform_driver_probe(&smsc47m1_driver, smsc47m1_probe);
        if (err)
-               goto exit_driver;
+               goto exit_device;
 
        return 0;
 
-exit_driver:
-       platform_driver_unregister(&smsc47m1_driver);
+exit_device:
+       platform_device_unregister(pdev);
+       smsc47m1_restore(&sio_data);
 exit:
        return err;
 }
 
 static void __exit sm_smsc47m1_exit(void)
 {
-       platform_device_unregister(pdev);
        platform_driver_unregister(&smsc47m1_driver);
+       smsc47m1_restore(pdev->dev.platform_data);
+       platform_device_unregister(pdev);
 }
 
 MODULE_AUTHOR("Mark D. Studebaker <mdsxyz123@yahoo.com>");