hwmon: Fix off-by-one kind values
[safe/jmp/linux-2.6] / drivers / hwmon / smsc47m1.c
index 2237088..8fa462f 100644 (file)
 #include <linux/jiffies.h>
 #include <linux/platform_device.h>
 #include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
 #include <linux/err.h>
 #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);
+MODULE_PARM_DESC(force_id, "Override the detected device ID");
 
 static struct platform_device *pdev;
 
@@ -80,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 */
 
@@ -115,7 +122,7 @@ struct smsc47m1_data {
        unsigned short addr;
        const char *name;
        enum chips type;
-       struct class_device *class_dev;
+       struct device *hwmon_dev;
 
        struct mutex update_lock;
        unsigned long last_updated;     /* In jiffies */
@@ -129,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 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);
 
@@ -153,15 +160,15 @@ static struct platform_driver smsc47m1_driver = {
                .owner  = THIS_MODULE,
                .name   = DRVNAME,
        },
-       .probe          = smsc47m1_probe,
-       .remove         = __devexit_p(smsc47m1_remove),
+       .remove         = __exit_p(smsc47m1_remove),
 };
 
-/* nr is 0 or 1 in the callback functions below */
-
-static ssize_t get_fan(struct device *dev, char *buf, int nr)
+static ssize_t get_fan(struct device *dev, struct device_attribute
+                      *devattr, char *buf)
 {
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
        struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
+       int nr = attr->index;
        /* This chip (stupidly) stops monitoring fan speed if PWM is
           enabled and duty cycle is 0%. This is fine if the monitoring
           and control concern the same fan, but troublesome if they are
@@ -173,42 +180,62 @@ static ssize_t get_fan(struct device *dev, char *buf, int nr)
        return sprintf(buf, "%d\n", rpm);
 }
 
-static ssize_t get_fan_min(struct device *dev, char *buf, int nr)
+static ssize_t get_fan_min(struct device *dev, struct device_attribute
+                          *devattr, char *buf)
 {
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
        struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
+       int nr = attr->index;
        int rpm = MIN_FROM_REG(data->fan_preload[nr],
                               DIV_FROM_REG(data->fan_div[nr]));
        return sprintf(buf, "%d\n", rpm);
 }
 
-static ssize_t get_fan_div(struct device *dev, char *buf, int nr)
+static ssize_t get_fan_div(struct device *dev, struct device_attribute
+                          *devattr, char *buf)
+{
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+       struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
+       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, "%d\n", DIV_FROM_REG(data->fan_div[nr]));
+       return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1);
 }
 
-static ssize_t get_pwm(struct device *dev, char *buf, int nr)
+static ssize_t get_pwm(struct device *dev, struct device_attribute
+                      *devattr, char *buf)
 {
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
        struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
-       return sprintf(buf, "%d\n", PWM_FROM_REG(data->pwm[nr]));
+       return sprintf(buf, "%d\n", PWM_FROM_REG(data->pwm[attr->index]));
 }
 
-static ssize_t get_pwm_en(struct device *dev, char *buf, int nr)
+static ssize_t get_pwm_en(struct device *dev, struct device_attribute
+                         *devattr, char *buf)
 {
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
        struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
-       return sprintf(buf, "%d\n", PWM_EN_FROM_REG(data->pwm[nr]));
+       return sprintf(buf, "%d\n", PWM_EN_FROM_REG(data->pwm[attr->index]));
 }
 
-static ssize_t get_alarms(struct device *dev, struct device_attribute *attr, char *buf)
+static ssize_t get_alarms(struct device *dev, struct device_attribute
+                         *devattr, char *buf)
 {
        struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);
        return sprintf(buf, "%d\n", data->alarms);
 }
 
-static ssize_t set_fan_min(struct device *dev, const char *buf,
-               size_t count, int nr)
+static ssize_t set_fan_min(struct device *dev, struct device_attribute
+                          *devattr, const char *buf, size_t count)
 {
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
        struct smsc47m1_data *data = dev_get_drvdata(dev);
+       int nr = attr->index;
        long rpmdiv, val = simple_strtol(buf, NULL, 10);
 
        mutex_lock(&data->update_lock);
@@ -231,11 +258,12 @@ static ssize_t set_fan_min(struct device *dev, const char *buf,
    determined in part by the fan clock divider.  This follows the principle
    of least surprise; the user doesn't expect the fan minimum to change just
    because the divider changed. */
-static ssize_t set_fan_div(struct device *dev, const char *buf,
-               size_t count, int nr)
+static ssize_t set_fan_div(struct device *dev, struct device_attribute
+                          *devattr, const char *buf, size_t count)
 {
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
        struct smsc47m1_data *data = dev_get_drvdata(dev);
-
+       int nr = attr->index;
        long new_div = simple_strtol(buf, NULL, 10), tmp;
        u8 old_div = DIV_FROM_REG(data->fan_div[nr]);
 
@@ -279,11 +307,12 @@ static ssize_t set_fan_div(struct device *dev, const char *buf,
        return count;
 }
 
-static ssize_t set_pwm(struct device *dev, const char *buf,
-               size_t count, int nr)
+static ssize_t set_pwm(struct device *dev, struct device_attribute
+                      *devattr, const char *buf, size_t count)
 {
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
        struct smsc47m1_data *data = dev_get_drvdata(dev);
-
+       int nr = attr->index;
        long val = simple_strtol(buf, NULL, 10);
 
        if (val < 0 || val > 255)
@@ -299,11 +328,12 @@ static ssize_t set_pwm(struct device *dev, const char *buf,
        return count;
 }
 
-static ssize_t set_pwm_en(struct device *dev, const char *buf,
-               size_t count, int nr)
+static ssize_t set_pwm_en(struct device *dev, struct device_attribute
+                         *devattr, const char *buf, size_t count)
 {
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
        struct smsc47m1_data *data = dev_get_drvdata(dev);
-
+       int nr = attr->index;
        long val = simple_strtol(buf, NULL, 10);
        
        if (val != 0 && val != 1)
@@ -320,56 +350,18 @@ static ssize_t set_pwm_en(struct device *dev, const char *buf,
 }
 
 #define fan_present(offset)                                            \
-static ssize_t get_fan##offset (struct device *dev, struct device_attribute *attr, char *buf)          \
-{                                                                      \
-       return get_fan(dev, buf, offset - 1);                           \
-}                                                                      \
-static ssize_t get_fan##offset##_min (struct device *dev, struct device_attribute *attr, char *buf)    \
-{                                                                      \
-       return get_fan_min(dev, buf, offset - 1);                       \
-}                                                                      \
-static ssize_t set_fan##offset##_min (struct device *dev, struct device_attribute *attr,               \
-               const char *buf, size_t count)                          \
-{                                                                      \
-       return set_fan_min(dev, buf, count, offset - 1);                \
-}                                                                      \
-static ssize_t get_fan##offset##_div (struct device *dev, struct device_attribute *attr, char *buf)    \
-{                                                                      \
-       return get_fan_div(dev, buf, offset - 1);                       \
-}                                                                      \
-static ssize_t set_fan##offset##_div (struct device *dev, struct device_attribute *attr,               \
-               const char *buf, size_t count)                          \
-{                                                                      \
-       return set_fan_div(dev, buf, count, offset - 1);                \
-}                                                                      \
-static ssize_t get_pwm##offset (struct device *dev, struct device_attribute *attr, char *buf)          \
-{                                                                      \
-       return get_pwm(dev, buf, offset - 1);                           \
-}                                                                      \
-static ssize_t set_pwm##offset (struct device *dev, struct device_attribute *attr,                     \
-               const char *buf, size_t count)                          \
-{                                                                      \
-       return set_pwm(dev, buf, count, offset - 1);                    \
-}                                                                      \
-static ssize_t get_pwm##offset##_en (struct device *dev, struct device_attribute *attr, char *buf)     \
-{                                                                      \
-       return get_pwm_en(dev, buf, offset - 1);                        \
-}                                                                      \
-static ssize_t set_pwm##offset##_en (struct device *dev, struct device_attribute *attr,                \
-               const char *buf, size_t count)                          \
-{                                                                      \
-       return set_pwm_en(dev, buf, count, offset - 1);                 \
-}                                                                      \
-static DEVICE_ATTR(fan##offset##_input, S_IRUGO, get_fan##offset,      \
-               NULL);                                                  \
-static DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR,               \
-               get_fan##offset##_min, set_fan##offset##_min);          \
-static DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR,               \
-               get_fan##offset##_div, set_fan##offset##_div);          \
-static DEVICE_ATTR(pwm##offset, S_IRUGO | S_IWUSR,                     \
-               get_pwm##offset, set_pwm##offset);                      \
-static DEVICE_ATTR(pwm##offset##_enable, S_IRUGO | S_IWUSR,            \
-               get_pwm##offset##_en, set_pwm##offset##_en);
+static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, get_fan,       \
+               NULL, offset - 1);                                      \
+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,     \
+               get_pwm_en, set_pwm_en, offset - 1)
 
 fan_present(1);
 fan_present(2);
@@ -390,22 +382,25 @@ static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
    setup so we create them individually. It is still convenient to define a
    group to remove them all at once. */
 static struct attribute *smsc47m1_attributes[] = {
-       &dev_attr_fan1_input.attr,
-       &dev_attr_fan1_min.attr,
-       &dev_attr_fan1_div.attr,
-       &dev_attr_fan2_input.attr,
-       &dev_attr_fan2_min.attr,
-       &dev_attr_fan2_div.attr,
-       &dev_attr_fan3_input.attr,
-       &dev_attr_fan3_min.attr,
-       &dev_attr_fan3_div.attr,
-
-       &dev_attr_pwm1.attr,
-       &dev_attr_pwm1_enable.attr,
-       &dev_attr_pwm2.attr,
-       &dev_attr_pwm2_enable.attr,
-       &dev_attr_pwm3.attr,
-       &dev_attr_pwm3_enable.attr,
+       &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,
+       &sensor_dev_attr_pwm2.dev_attr.attr,
+       &sensor_dev_attr_pwm2_enable.dev_attr.attr,
+       &sensor_dev_attr_pwm3.dev_attr.attr,
+       &sensor_dev_attr_pwm3_enable.dev_attr.attr,
 
        &dev_attr_alarms.attr,
        &dev_attr_name.attr,
@@ -422,7 +417,7 @@ static int __init smsc47m1_find(unsigned short *addr,
        u8 val;
 
        superio_enter();
-       val = superio_inb(SUPERIO_REG_DEVID);
+       val = force_id ? force_id : superio_inb(SUPERIO_REG_DEVID);
 
        /*
         * SMSC LPC47M10x/LPC47M112/LPC47M13x (device id 0x59), LPC47M14x
@@ -434,28 +429,36 @@ 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:
-               printk(KERN_INFO "smsc47m1: Found SMSC LPC47B27x\n");
+               pr_info(DRVNAME ": Found SMSC LPC47B27x\n");
                sio_data->type = smsc47m1;
                break;
        case 0x59:
-               printk(KERN_INFO "smsc47m1: Found SMSC "
-                      "LPC47M10x/LPC47M112/LPC47M13x\n");
+               pr_info(DRVNAME ": Found SMSC LPC47M10x/LPC47M112/LPC47M13x\n");
                sio_data->type = smsc47m1;
                break;
        case 0x5F:
-               printk(KERN_INFO "smsc47m1: Found SMSC LPC47M14x\n");
+               pr_info(DRVNAME ": Found SMSC LPC47M14x\n");
                sio_data->type = smsc47m1;
                break;
        case 0x60:
-               printk(KERN_INFO "smsc47m1: Found SMSC "
-                      "LPC47M15x/LPC47M192/LPC47M997\n");
+               pr_info(DRVNAME ": Found SMSC LPC47M15x/LPC47M192/LPC47M997\n");
                sio_data->type = smsc47m1;
                break;
        case 0x6B:
-               printk(KERN_INFO "smsc47m1: Found SMSC LPC47M292\n");
+               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;
        default:
@@ -466,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) {
-               printk(KERN_INFO "smsc47m1: 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[] = {
@@ -492,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;
@@ -549,56 +652,79 @@ static int __devinit smsc47m1_probe(struct platform_device *pdev)
 
        /* Register sysfs hooks */
        if (fan1) {
-               if ((err = device_create_file(dev, &dev_attr_fan1_input))
-                || (err = device_create_file(dev, &dev_attr_fan1_min))
-                || (err = device_create_file(dev, &dev_attr_fan1_div)))
+               if ((err = device_create_file(dev,
+                               &sensor_dev_attr_fan1_input.dev_attr))
+                || (err = device_create_file(dev,
+                               &sensor_dev_attr_fan1_min.dev_attr))
+                || (err = device_create_file(dev,
+                               &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");
 
        if (fan2) {
-               if ((err = device_create_file(dev, &dev_attr_fan2_input))
-                || (err = device_create_file(dev, &dev_attr_fan2_min))
-                || (err = device_create_file(dev, &dev_attr_fan2_div)))
+               if ((err = device_create_file(dev,
+                               &sensor_dev_attr_fan2_input.dev_attr))
+                || (err = device_create_file(dev,
+                               &sensor_dev_attr_fan2_min.dev_attr))
+                || (err = device_create_file(dev,
+                               &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");
 
        if (fan3) {
-               if ((err = device_create_file(dev, &dev_attr_fan3_input))
-                || (err = device_create_file(dev, &dev_attr_fan3_min))
-                || (err = device_create_file(dev, &dev_attr_fan3_div)))
+               if ((err = device_create_file(dev,
+                               &sensor_dev_attr_fan3_input.dev_attr))
+                || (err = device_create_file(dev,
+                               &sensor_dev_attr_fan3_min.dev_attr))
+                || (err = device_create_file(dev,
+                               &sensor_dev_attr_fan3_div.dev_attr))
+                || (err = device_create_file(dev,
+                               &sensor_dev_attr_fan3_alarm.dev_attr)))
                        goto error_remove_files;
-       } else
+       } else if (data->type == smsc47m2)
                dev_dbg(dev, "Fan 3 not enabled by hardware, skipping\n");
 
        if (pwm1) {
-               if ((err = device_create_file(dev, &dev_attr_pwm1))
-                || (err = device_create_file(dev, &dev_attr_pwm1_enable)))
+               if ((err = device_create_file(dev,
+                               &sensor_dev_attr_pwm1.dev_attr))
+                || (err = device_create_file(dev,
+                               &sensor_dev_attr_pwm1_enable.dev_attr)))
                        goto error_remove_files;
        } else
                dev_dbg(dev, "PWM 1 not enabled by hardware, skipping\n");
 
        if (pwm2) {
-               if ((err = device_create_file(dev, &dev_attr_pwm2))
-                || (err = device_create_file(dev, &dev_attr_pwm2_enable)))
+               if ((err = device_create_file(dev,
+                               &sensor_dev_attr_pwm2.dev_attr))
+                || (err = device_create_file(dev,
+                               &sensor_dev_attr_pwm2_enable.dev_attr)))
                        goto error_remove_files;
        } else
                dev_dbg(dev, "PWM 2 not enabled by hardware, skipping\n");
 
        if (pwm3) {
-               if ((err = device_create_file(dev, &dev_attr_pwm3))
-                || (err = device_create_file(dev, &dev_attr_pwm3_enable)))
+               if ((err = device_create_file(dev,
+                               &sensor_dev_attr_pwm3.dev_attr))
+                || (err = device_create_file(dev,
+                               &sensor_dev_attr_pwm3_enable.dev_attr)))
                        goto error_remove_files;
-       } else
+       } else if (data->type == smsc47m2)
                dev_dbg(dev, "PWM 3 not enabled by hardware, skipping\n");
 
        if ((err = device_create_file(dev, &dev_attr_alarms)))
                goto error_remove_files;
+       if ((err = device_create_file(dev, &dev_attr_name)))
+               goto error_remove_files;
 
-       data->class_dev = hwmon_device_register(dev);
-       if (IS_ERR(data->class_dev)) {
-               err = PTR_ERR(data->class_dev);
+       data->hwmon_dev = hwmon_device_register(dev);
+       if (IS_ERR(data->hwmon_dev)) {
+               err = PTR_ERR(data->hwmon_dev);
                goto error_remove_files;
        }
 
@@ -607,23 +733,24 @@ static int __devinit smsc47m1_probe(struct platform_device *pdev)
 error_remove_files:
        sysfs_remove_group(&dev->kobj, &smsc47m1_group);
 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;
 
-       platform_set_drvdata(pdev, NULL);
-       hwmon_device_unregister(data->class_dev);
+       hwmon_device_unregister(data->hwmon_dev);
        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);
 
        return 0;
@@ -689,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;
@@ -703,15 +834,12 @@ static int __init smsc47m1_device_add(unsigned short address,
                goto exit_device_put;
        }
 
-       pdev->dev.platform_data = kmalloc(sizeof(struct smsc47m1_sio_data),
-                                         GFP_KERNEL);
-       if (!pdev->dev.platform_data) {
-               err = -ENOMEM;
+       err = platform_device_add_data(pdev, sio_data,
+                                      sizeof(struct smsc47m1_sio_data));
+       if (err) {
                printk(KERN_ERR DRVNAME ": Platform data allocation failed\n");
                goto exit_device_put;
        }
-       memcpy(pdev->dev.platform_data, sio_data,
-              sizeof(struct smsc47m1_sio_data));
 
        err = platform_device_add(pdev);
        if (err) {
@@ -737,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>");