]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/hwmon/lm78.c
hwmon: (lm78) Stop abusing struct i2c_client for ISA devices
[linux-2.6-omap-h63xx.git] / drivers / hwmon / lm78.c
index 565c4e679b8d4f1ebcf1cb04cc1faecd71fb31d9..177eaddde321d187cb0f8e3e59569267f8d555e9 100644 (file)
 static struct platform_device *pdev;
 
 /* Addresses to scan */
-static unsigned short normal_i2c[] = { 0x20, 0x21, 0x22, 0x23, 0x24,
-                                       0x25, 0x26, 0x27, 0x28, 0x29,
-                                       0x2a, 0x2b, 0x2c, 0x2d, 0x2e,
-                                       0x2f, I2C_CLIENT_END };
+static const unsigned short normal_i2c[] = { 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
+                                               0x2e, 0x2f, I2C_CLIENT_END };
 static unsigned short isa_address = 0x290;
 
 /* Insmod parameters */
@@ -116,25 +114,16 @@ static inline int TEMP_FROM_REG(s8 val)
 
 #define DIV_FROM_REG(val) (1 << (val))
 
-/* There are some complications in a module like this. First off, LM78 chips
-   may be both present on the SMBus and the ISA bus, and we have to handle
-   those cases separately at some places. Second, there might be several
-   LM78 chips available (well, actually, that is probably never done; but
-   it is a clean illustration of how to handle a case like that). Finally,
-   a specific chip may be attached to *both* ISA and SMBus, and we would
-   not like to detect it double. Fortunately, in the case of the LM78 at
-   least, a register tells us what SMBus address we are on, so that helps
-   a bit - except if there could be more than one SMBus. Groan. No solution
-   for this yet. */
-
-/* For ISA chips, we abuse the i2c_client addr and name fields. We also use
-   the driver field to differentiate between I2C and ISA chips. */
 struct lm78_data {
        struct i2c_client client;
-       struct class_device *class_dev;
+       struct device *hwmon_dev;
        struct mutex lock;
        enum chips type;
 
+       /* For ISA device only */
+       const char *name;
+       int isa_addr;
+
        struct mutex update_lock;
        char valid;             /* !=0 if following fields are valid */
        unsigned long last_updated;     /* In jiffies */
@@ -170,7 +159,6 @@ static struct i2c_driver lm78_driver = {
        .driver = {
                .name   = "lm78",
        },
-       .id             = I2C_DRIVERID_LM78,
        .attach_adapter = lm78_attach_adapter,
        .detach_client  = lm78_detach_client,
 };
@@ -438,51 +426,93 @@ static ssize_t show_alarms(struct device *dev, struct device_attribute *da,
 }
 static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
 
+static ssize_t show_alarm(struct device *dev, struct device_attribute *da,
+                         char *buf)
+{
+       struct lm78_data *data = lm78_update_device(dev);
+       int nr = to_sensor_dev_attr(da)->index;
+       return sprintf(buf, "%u\n", (data->alarms >> nr) & 1);
+}
+static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1);
+static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2);
+static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 3);
+static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 8);
+static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 9);
+static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 10);
+static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, 6);
+static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, 7);
+static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, 11);
+static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 4);
+
 /* This function is called when:
      * lm78_driver is inserted (when this module is loaded), for each
        available adapter
-     * when a new adapter is inserted (and lm78_driver is still present) */
+     * when a new adapter is inserted (and lm78_driver is still present)
+   We block updates of the ISA device to minimize the risk of concurrent
+   access to the same LM78 chip through different interfaces. */
 static int lm78_attach_adapter(struct i2c_adapter *adapter)
 {
+       struct lm78_data *data;
+       int err;
+
        if (!(adapter->class & I2C_CLASS_HWMON))
                return 0;
-       return i2c_probe(adapter, &addr_data, lm78_detect);
+
+       data = pdev ? platform_get_drvdata(pdev) : NULL;
+       if (data)
+               mutex_lock(&data->update_lock);
+       err = i2c_probe(adapter, &addr_data, lm78_detect);
+       if (data)
+               mutex_unlock(&data->update_lock);
+       return err;
 }
 
 static struct attribute *lm78_attributes[] = {
        &sensor_dev_attr_in0_input.dev_attr.attr,
        &sensor_dev_attr_in0_min.dev_attr.attr,
        &sensor_dev_attr_in0_max.dev_attr.attr,
+       &sensor_dev_attr_in0_alarm.dev_attr.attr,
        &sensor_dev_attr_in1_input.dev_attr.attr,
        &sensor_dev_attr_in1_min.dev_attr.attr,
        &sensor_dev_attr_in1_max.dev_attr.attr,
+       &sensor_dev_attr_in1_alarm.dev_attr.attr,
        &sensor_dev_attr_in2_input.dev_attr.attr,
        &sensor_dev_attr_in2_min.dev_attr.attr,
        &sensor_dev_attr_in2_max.dev_attr.attr,
+       &sensor_dev_attr_in2_alarm.dev_attr.attr,
        &sensor_dev_attr_in3_input.dev_attr.attr,
        &sensor_dev_attr_in3_min.dev_attr.attr,
        &sensor_dev_attr_in3_max.dev_attr.attr,
+       &sensor_dev_attr_in3_alarm.dev_attr.attr,
        &sensor_dev_attr_in4_input.dev_attr.attr,
        &sensor_dev_attr_in4_min.dev_attr.attr,
        &sensor_dev_attr_in4_max.dev_attr.attr,
+       &sensor_dev_attr_in4_alarm.dev_attr.attr,
        &sensor_dev_attr_in5_input.dev_attr.attr,
        &sensor_dev_attr_in5_min.dev_attr.attr,
        &sensor_dev_attr_in5_max.dev_attr.attr,
+       &sensor_dev_attr_in5_alarm.dev_attr.attr,
        &sensor_dev_attr_in6_input.dev_attr.attr,
        &sensor_dev_attr_in6_min.dev_attr.attr,
        &sensor_dev_attr_in6_max.dev_attr.attr,
+       &sensor_dev_attr_in6_alarm.dev_attr.attr,
        &dev_attr_temp1_input.attr,
        &dev_attr_temp1_max.attr,
        &dev_attr_temp1_max_hyst.attr,
+       &sensor_dev_attr_temp1_alarm.dev_attr.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,
        &dev_attr_alarms.attr,
        &dev_attr_cpu0_vid.attr,
 
@@ -500,10 +530,44 @@ static ssize_t show_name(struct device *dev, struct device_attribute
 {
        struct lm78_data *data = dev_get_drvdata(dev);
 
-       return sprintf(buf, "%s\n", data->client.name);
+       return sprintf(buf, "%s\n", data->name);
 }
 static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
 
+/* Returns 1 if the I2C chip appears to be an alias of the ISA chip */
+static int lm78_alias_detect(struct i2c_client *client, u8 chipid)
+{
+       struct lm78_data *i2c, *isa;
+       int i;
+
+       if (!pdev)      /* No ISA chip */
+               return 0;
+
+       i2c = i2c_get_clientdata(client);
+       isa = platform_get_drvdata(pdev);
+
+       if (lm78_read_value(isa, LM78_REG_I2C_ADDR) != client->addr)
+               return 0;       /* Address doesn't match */
+       if ((lm78_read_value(isa, LM78_REG_CHIPID) & 0xfe) != (chipid & 0xfe))
+               return 0;       /* Chip type doesn't match */
+
+       /* We compare all the limit registers, the config register and the
+        * interrupt mask registers */
+       for (i = 0x2b; i <= 0x3d; i++) {
+               if (lm78_read_value(isa, i) != lm78_read_value(i2c, i))
+                       return 0;
+       }
+       if (lm78_read_value(isa, LM78_REG_CONFIG) !=
+           lm78_read_value(i2c, LM78_REG_CONFIG))
+               return 0;
+       for (i = 0x43; i <= 0x46; i++) {
+               if (lm78_read_value(isa, i) != lm78_read_value(i2c, i))
+                       return 0;
+       }
+
+       return 1;
+}
+
 /* This function is called by i2c_probe */
 static int lm78_detect(struct i2c_adapter *adapter, int address, int kind)
 {
@@ -543,6 +607,12 @@ static int lm78_detect(struct i2c_adapter *adapter, int address, int kind)
                        err = -ENODEV;
                        goto ERROR2;
                }
+               /* Explicitly prevent the misdetection of Winbond chips */
+               i = lm78_read_value(data, 0x4f);
+               if (i == 0xa3 || i == 0x5c) {
+                       err = -ENODEV;
+                       goto ERROR2;
+               }
        }
 
        /* Determine the chip type. */
@@ -562,6 +632,13 @@ static int lm78_detect(struct i2c_adapter *adapter, int address, int kind)
                        err = -ENODEV;
                        goto ERROR2;
                }
+
+               if (lm78_alias_detect(new_client, i)) {
+                       dev_dbg(&adapter->dev, "Device at 0x%02x appears to "
+                               "be the same as ISA device\n", address);
+                       err = -ENODEV;
+                       goto ERROR2;
+               }
        }
 
        if (kind == lm78) {
@@ -585,9 +662,9 @@ static int lm78_detect(struct i2c_adapter *adapter, int address, int kind)
        if ((err = sysfs_create_group(&new_client->dev.kobj, &lm78_group)))
                goto ERROR3;
 
-       data->class_dev = hwmon_device_register(&new_client->dev);
-       if (IS_ERR(data->class_dev)) {
-               err = PTR_ERR(data->class_dev);
+       data->hwmon_dev = hwmon_device_register(&new_client->dev);
+       if (IS_ERR(data->hwmon_dev)) {
+               err = PTR_ERR(data->hwmon_dev);
                goto ERROR4;
        }
 
@@ -608,7 +685,7 @@ static int lm78_detach_client(struct i2c_client *client)
        struct lm78_data *data = i2c_get_clientdata(client);
        int err;
 
-       hwmon_device_unregister(data->class_dev);
+       hwmon_device_unregister(data->hwmon_dev);
        sysfs_remove_group(&client->dev.kobj, &lm78_group);
 
        if ((err = i2c_detach_client(client)))
@@ -624,11 +701,10 @@ static int __devinit lm78_isa_probe(struct platform_device *pdev)
        int err;
        struct lm78_data *data;
        struct resource *res;
-       const char *name;
 
        /* Reserve the ISA region */
        res = platform_get_resource(pdev, IORESOURCE_IO, 0);
-       if (!request_region(res->start, LM78_EXTENT, "lm78")) {
+       if (!request_region(res->start + LM78_ADDR_REG_OFFSET, 2, "lm78")) {
                err = -EBUSY;
                goto exit;
        }
@@ -638,18 +714,16 @@ static int __devinit lm78_isa_probe(struct platform_device *pdev)
                goto exit_release_region;
        }
        mutex_init(&data->lock);
-       data->client.addr = res->start;
-       i2c_set_clientdata(&data->client, data);
+       data->isa_addr = res->start;
        platform_set_drvdata(pdev, data);
 
        if (lm78_read_value(data, LM78_REG_CHIPID) & 0x80) {
                data->type = lm79;
-               name = "lm79";
+               data->name = "lm79";
        } else {
                data->type = lm78;
-               name = "lm78";
+               data->name = "lm78";
        }
-       strlcpy(data->client.name, name, I2C_NAME_SIZE);
 
        /* Initialize the LM78 chip */
        lm78_init_device(data);
@@ -659,9 +733,9 @@ static int __devinit lm78_isa_probe(struct platform_device *pdev)
         || (err = device_create_file(&pdev->dev, &dev_attr_name)))
                goto exit_remove_files;
 
-       data->class_dev = hwmon_device_register(&pdev->dev);
-       if (IS_ERR(data->class_dev)) {
-               err = PTR_ERR(data->class_dev);
+       data->hwmon_dev = hwmon_device_register(&pdev->dev);
+       if (IS_ERR(data->hwmon_dev)) {
+               err = PTR_ERR(data->hwmon_dev);
                goto exit_remove_files;
        }
 
@@ -672,7 +746,7 @@ static int __devinit lm78_isa_probe(struct platform_device *pdev)
        device_remove_file(&pdev->dev, &dev_attr_name);
        kfree(data);
  exit_release_region:
-       release_region(res->start, LM78_EXTENT);
+       release_region(res->start + LM78_ADDR_REG_OFFSET, 2);
  exit:
        return err;
 }
@@ -680,13 +754,16 @@ static int __devinit lm78_isa_probe(struct platform_device *pdev)
 static int __devexit lm78_isa_remove(struct platform_device *pdev)
 {
        struct lm78_data *data = platform_get_drvdata(pdev);
+       struct resource *res;
 
-       hwmon_device_unregister(data->class_dev);
+       hwmon_device_unregister(data->hwmon_dev);
        sysfs_remove_group(&pdev->dev.kobj, &lm78_group);
        device_remove_file(&pdev->dev, &dev_attr_name);
-       release_region(data->client.addr, LM78_EXTENT);
        kfree(data);
 
+       res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+       release_region(res->start + LM78_ADDR_REG_OFFSET, 2);
+
        return 0;
 }
 
@@ -702,8 +779,8 @@ static int lm78_read_value(struct lm78_data *data, u8 reg)
        if (!client->driver) { /* ISA device */
                int res;
                mutex_lock(&data->lock);
-               outb_p(reg, client->addr + LM78_ADDR_REG_OFFSET);
-               res = inb_p(client->addr + LM78_DATA_REG_OFFSET);
+               outb_p(reg, data->isa_addr + LM78_ADDR_REG_OFFSET);
+               res = inb_p(data->isa_addr + LM78_DATA_REG_OFFSET);
                mutex_unlock(&data->lock);
                return res;
        } else
@@ -723,8 +800,8 @@ static int lm78_write_value(struct lm78_data *data, u8 reg, u8 value)
 
        if (!client->driver) { /* ISA device */
                mutex_lock(&data->lock);
-               outb_p(reg, client->addr + LM78_ADDR_REG_OFFSET);
-               outb_p(value, client->addr + LM78_DATA_REG_OFFSET);
+               outb_p(reg, data->isa_addr + LM78_ADDR_REG_OFFSET);
+               outb_p(value, data->isa_addr + LM78_DATA_REG_OFFSET);
                mutex_unlock(&data->lock);
                return 0;
        } else
@@ -810,8 +887,17 @@ static int __init lm78_isa_found(unsigned short address)
 {
        int val, save, found = 0;
 
-       if (!request_region(address, LM78_EXTENT, "lm78"))
+       /* We have to request the region in two parts because some
+          boards declare base+4 to base+7 as a PNP device */
+       if (!request_region(address, 4, "lm78")) {
+               pr_debug("lm78: Failed to request low part of region\n");
                return 0;
+       }
+       if (!request_region(address + 4, 4, "lm78")) {
+               pr_debug("lm78: Failed to request high part of region\n");
+               release_region(address, 4);
+               return 0;
+       }
 
 #define REALLY_SLOW_IO
        /* We need the timeouts for at least some LM78-like
@@ -874,7 +960,8 @@ static int __init lm78_isa_found(unsigned short address)
                        val & 0x80 ? "LM79" : "LM78", (int)address);
 
  release:
-       release_region(address, LM78_EXTENT);
+       release_region(address + 4, 4);
+       release_region(address, 4);
        return found;
 }
 
@@ -882,7 +969,7 @@ static int __init lm78_isa_device_add(unsigned short address)
 {
        struct resource res = {
                .start  = address,
-               .end    = address + LM78_EXTENT,
+               .end    = address + LM78_EXTENT - 1,
                .name   = "lm78",
                .flags  = IORESOURCE_IO,
        };
@@ -922,14 +1009,12 @@ static int __init sm_lm78_init(void)
 {
        int res;
 
-       res = i2c_add_driver(&lm78_driver);
-       if (res)
-               goto exit;
-
+       /* We register the ISA device first, so that we can skip the
+        * registration of an I2C interface to the same device. */
        if (lm78_isa_found(isa_address)) {
                res = platform_driver_register(&lm78_isa_driver);
                if (res)
-                       goto exit_unreg_i2c_driver;
+                       goto exit;
 
                /* Sets global pdev as a side effect */
                res = lm78_isa_device_add(isa_address);
@@ -937,12 +1022,16 @@ static int __init sm_lm78_init(void)
                        goto exit_unreg_isa_driver;
        }
 
+       res = i2c_add_driver(&lm78_driver);
+       if (res)
+               goto exit_unreg_isa_device;
+
        return 0;
 
+ exit_unreg_isa_device:
+       platform_device_unregister(pdev);
  exit_unreg_isa_driver:
        platform_driver_unregister(&lm78_isa_driver);
- exit_unreg_i2c_driver:
-       i2c_del_driver(&lm78_driver);
  exit:
        return res;
 }