#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 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 */
/* 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[] = {
{
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)
{
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. */
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) {
int err;
struct lm78_data *data;
struct resource *res;
- const char *name;
/* Reserve the ISA region */
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
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);
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->hwmon_dev);
sysfs_remove_group(&pdev->dev.kobj, &lm78_group);
device_remove_file(&pdev->dev, &dev_attr_name);
- release_region(data->client.addr + LM78_ADDR_REG_OFFSET, 2);
kfree(data);
+ res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ release_region(res->start + LM78_ADDR_REG_OFFSET, 2);
+
return 0;
}
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
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
{
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);
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;
}