diff options
-rw-r--r-- | drivers/hwmon/w83781d.c | 79 |
1 files changed, 66 insertions, 13 deletions
diff --git a/drivers/hwmon/w83781d.c b/drivers/hwmon/w83781d.c index 136bec3fd645..1c00d9f7c14d 100644 --- a/drivers/hwmon/w83781d.c +++ b/drivers/hwmon/w83781d.c @@ -205,10 +205,7 @@ DIV_TO_REG(long val, enum chips type) W83781D 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 W83781D 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. */ @@ -852,13 +849,25 @@ static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); /* This function is called when: * w83781d_driver is inserted (when this module is loaded), for each available adapter - * when a new adapter is inserted (and w83781d_driver is still present) */ + * when a new adapter is inserted (and w83781d_driver is still present) + We block updates of the ISA device to minimize the risk of concurrent + access to the same W83781D chip through different interfaces. */ static int w83781d_attach_adapter(struct i2c_adapter *adapter) { + struct w83781d_data *data; + int err; + if (!(adapter->class & I2C_CLASS_HWMON)) return 0; - return i2c_probe(adapter, &addr_data, w83781d_detect); + + data = pdev ? platform_get_drvdata(pdev) : NULL; + if (data) + mutex_lock(&data->update_lock); + err = i2c_probe(adapter, &addr_data, w83781d_detect); + if (data) + mutex_unlock(&data->update_lock); + return err; } /* Assumes that adapter is of I2C, not ISA variety. @@ -1028,6 +1037,40 @@ static const struct attribute_group w83781d_group_opt = { .attrs = w83781d_attributes_opt, }; +/* Returns 1 if the I2C chip appears to be an alias of the ISA chip */ +static int w83781d_alias_detect(struct i2c_client *client, u8 chipid) +{ + struct w83781d_data *i2c, *isa; + int i; + + if (!pdev) /* No ISA chip */ + return 0; + + i2c = i2c_get_clientdata(client); + isa = platform_get_drvdata(pdev); + + if (w83781d_read_value(isa, W83781D_REG_I2C_ADDR) != client->addr) + return 0; /* Address doesn't match */ + if (w83781d_read_value(isa, W83781D_REG_WCHIPID) != chipid) + 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 (w83781d_read_value(isa, i) != w83781d_read_value(i2c, i)) + return 0; + } + if (w83781d_read_value(isa, W83781D_REG_CONFIG) != + w83781d_read_value(i2c, W83781D_REG_CONFIG)) + return 0; + for (i = 0x43; i <= 0x46; i++) { + if (w83781d_read_value(isa, i) != w83781d_read_value(i2c, i)) + return 0; + } + + return 1; +} + /* No clean up is done on error, it's up to the caller */ static int w83781d_create_files(struct device *dev, int kind, int is_isa) @@ -1242,6 +1285,14 @@ w83781d_detect(struct i2c_adapter *adapter, int address, int kind) err = -EINVAL; goto ERROR2; } + + if ((kind == w83781d || kind == w83782d) + && w83781d_alias_detect(client, val1)) { + dev_dbg(&adapter->dev, "Device at 0x%02x appears to " + "be the same as ISA device\n", address); + err = -ENODEV; + goto ERROR2; + } } if (kind == w83781d) { @@ -1904,14 +1955,12 @@ sensors_w83781d_init(void) { int res; - res = i2c_add_driver(&w83781d_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 (w83781d_isa_found(isa_address)) { res = platform_driver_register(&w83781d_isa_driver); if (res) - goto exit_unreg_i2c_driver; + goto exit; /* Sets global pdev as a side effect */ res = w83781d_isa_device_add(isa_address); @@ -1919,12 +1968,16 @@ sensors_w83781d_init(void) goto exit_unreg_isa_driver; } + res = i2c_add_driver(&w83781d_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(&w83781d_isa_driver); - exit_unreg_i2c_driver: - i2c_del_driver(&w83781d_driver); exit: return res; } |