]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/hwmon/lm78.c
hwmon: (lm78) Detect alias chips
[linux-2.6-omap-h63xx.git] / drivers / hwmon / lm78.c
index 2c96d8a548f7ab4398b03ff04ac812445d91d90b..ec601bbf91b9cbd71cd36eb2aa7e5e5cb67e60f0 100644 (file)
@@ -120,10 +120,7 @@ static inline int TEMP_FROM_REG(s8 val)
    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. */
+   not like to detect it double. */
 
 /* 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. */
@@ -457,12 +454,24 @@ 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[] = {
@@ -531,6 +540,40 @@ static ssize_t show_name(struct device *dev, struct device_attribute
 }
 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)
 {
@@ -589,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) {
@@ -959,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);
@@ -974,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;
 }