]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/hwmon/w83781d.c
hwmon: (w83781d) Detect alias chips
[linux-2.6-omap-h63xx.git] / drivers / hwmon / w83781d.c
index 5c85670e2d1643cef897f1fc97c71fcf3380778c..1c00d9f7c14df620ff1c5e94b2fa907bcd42f502 100644 (file)
@@ -178,9 +178,9 @@ FAN_FROM_REG(u8 val, int div)
 #define TEMP_FROM_REG(val)             ((val) * 1000)
 
 #define BEEP_MASK_FROM_REG(val,type)   ((type) == as99127f ? \
-                                        (val) ^ 0x7fff : (val))
+                                        (~(val)) & 0x7fff : (val) & 0xff7fff)
 #define BEEP_MASK_TO_REG(val,type)     ((type) == as99127f ? \
-                                        (~(val)) & 0x7fff : (val) & 0xffffff)
+                                        (~(val)) & 0x7fff : (val) & 0xff7fff)
 
 #define DIV_FROM_REG(val)              (1 << (val))
 
@@ -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. */
@@ -240,7 +237,6 @@ struct w83781d_data {
        u8 vid;                 /* Register encoding, combined */
        u32 alarms;             /* Register encoding, combined */
        u32 beep_mask;          /* Register encoding, combined */
-       u8 beep_enable;         /* Boolean */
        u8 pwm[4];              /* Register value */
        u8 pwm2_enable;         /* Boolean */
        u16 sens[3];            /* 782D/783S only.
@@ -513,11 +509,6 @@ static ssize_t show_beep_mask (struct device *dev, struct device_attribute *attr
        return sprintf(buf, "%ld\n",
                       (long)BEEP_MASK_FROM_REG(data->beep_mask, data->type));
 }
-static ssize_t show_beep_enable (struct device *dev, struct device_attribute *attr, char *buf)
-{
-       struct w83781d_data *data = w83781d_update_device(dev);
-       return sprintf(buf, "%ld\n", (long)data->beep_enable);
-}
 
 static ssize_t
 store_beep_mask(struct device *dev, struct device_attribute *attr,
@@ -529,12 +520,12 @@ store_beep_mask(struct device *dev, struct device_attribute *attr,
        val = simple_strtoul(buf, NULL, 10);
 
        mutex_lock(&data->update_lock);
-       data->beep_mask = BEEP_MASK_TO_REG(val, data->type);
+       data->beep_mask &= 0x8000; /* preserve beep enable */
+       data->beep_mask |= BEEP_MASK_TO_REG(val, data->type);
        w83781d_write_value(data, W83781D_REG_BEEP_INTS1,
                            data->beep_mask & 0xff);
        w83781d_write_value(data, W83781D_REG_BEEP_INTS2,
-                           ((data->beep_mask >> 8) & 0x7f)
-                           | data->beep_enable << 7);
+                           (data->beep_mask >> 8) & 0xff);
        if (data->type != w83781d && data->type != as99127f) {
                w83781d_write_value(data, W83781D_REG_BEEP_INTS3,
                                    ((data->beep_mask) >> 16) & 0xff);
@@ -544,31 +535,8 @@ store_beep_mask(struct device *dev, struct device_attribute *attr,
        return count;
 }
 
-static ssize_t
-store_beep_enable(struct device *dev, struct device_attribute *attr,
-               const char *buf, size_t count)
-{
-       struct w83781d_data *data = dev_get_drvdata(dev);
-       u32 val;
-
-       val = simple_strtoul(buf, NULL, 10);
-       if (val != 0 && val != 1)
-               return -EINVAL;
-
-       mutex_lock(&data->update_lock);
-       data->beep_enable = val;
-       val = w83781d_read_value(data, W83781D_REG_BEEP_INTS2) & 0x7f;
-       val |= data->beep_enable << 7;
-       w83781d_write_value(data, W83781D_REG_BEEP_INTS2, val);
-       mutex_unlock(&data->update_lock);
-
-       return count;
-}
-
 static DEVICE_ATTR(beep_mask, S_IRUGO | S_IWUSR,
                show_beep_mask, store_beep_mask);
-static DEVICE_ATTR(beep_enable, S_IRUGO | S_IWUSR,
-               show_beep_enable, store_beep_enable);
 
 static ssize_t show_beep(struct device *dev, struct device_attribute *attr,
                char *buf)
@@ -663,6 +631,8 @@ static SENSOR_DEVICE_ATTR(temp2_beep, S_IRUGO | S_IWUSR,
                        show_beep, store_beep, 5);
 static SENSOR_DEVICE_ATTR(temp3_beep, S_IRUGO,
                        show_temp3_beep, store_beep, 13);
+static SENSOR_DEVICE_ATTR(beep_enable, S_IRUGO | S_IWUSR,
+                       show_beep, store_beep, 15);
 
 static ssize_t
 show_fan_div(struct device *dev, struct device_attribute *da, char *buf)
@@ -879,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.
@@ -1029,7 +1011,7 @@ static struct attribute* w83781d_attributes[] = {
        &dev_attr_vrm.attr,
        &dev_attr_alarms.attr,
        &dev_attr_beep_mask.attr,
-       &dev_attr_beep_enable.attr,
+       &sensor_dev_attr_beep_enable.dev_attr.attr,
        NULL
 };
 static const struct attribute_group w83781d_group = {
@@ -1055,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)
@@ -1269,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) {
@@ -1367,7 +1391,8 @@ w83781d_isa_probe(struct platform_device *pdev)
 
        /* Reserve the ISA region */
        res = platform_get_resource(pdev, IORESOURCE_IO, 0);
-       if (!request_region(res->start, W83781D_EXTENT, "w83781d")) {
+       if (!request_region(res->start + W83781D_ADDR_REG_OFFSET, 2,
+                           "w83781d")) {
                err = -EBUSY;
                goto exit;
        }
@@ -1415,7 +1440,7 @@ w83781d_isa_probe(struct platform_device *pdev)
        device_remove_file(&pdev->dev, &dev_attr_name);
        kfree(data);
  exit_release_region:
-       release_region(res->start, W83781D_EXTENT);
+       release_region(res->start + W83781D_ADDR_REG_OFFSET, 2);
  exit:
        return err;
 }
@@ -1429,7 +1454,7 @@ w83781d_isa_remove(struct platform_device *pdev)
        sysfs_remove_group(&pdev->dev.kobj, &w83781d_group);
        sysfs_remove_group(&pdev->dev.kobj, &w83781d_group_opt);
        device_remove_file(&pdev->dev, &dev_attr_name);
-       release_region(data->client.addr, W83781D_EXTENT);
+       release_region(data->client.addr + W83781D_ADDR_REG_OFFSET, 2);
        kfree(data);
 
        return 0;
@@ -1774,8 +1799,7 @@ static struct w83781d_data *w83781d_update_device(struct device *dev)
                                                W83781D_REG_ALARM2) << 8);
                }
                i = w83781d_read_value(data, W83781D_REG_BEEP_INTS2);
-               data->beep_enable = i >> 7;
-               data->beep_mask = ((i & 0x7f) << 8) +
+               data->beep_mask = (i << 8) +
                    w83781d_read_value(data, W83781D_REG_BEEP_INTS1);
                if ((data->type != w83781d) && (data->type != as99127f)) {
                        data->beep_mask |=
@@ -1797,8 +1821,17 @@ w83781d_isa_found(unsigned short address)
 {
        int val, save, found = 0;
 
-       if (!request_region(address, W83781D_EXTENT, "w83781d"))
+       /* 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, "w83781d")) {
+               pr_debug("w83781d: Failed to request low part of region\n");
+               return 0;
+       }
+       if (!request_region(address + 4, 4, "w83781d")) {
+               pr_debug("w83781d: 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 W83781D-like
@@ -1871,7 +1904,8 @@ w83781d_isa_found(unsigned short address)
                        val == 0x30 ? "W83782D" : "W83781D", (int)address);
 
  release:
-       release_region(address, W83781D_EXTENT);
+       release_region(address + 4, 4);
+       release_region(address, 4);
        return found;
 }
 
@@ -1921,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);
@@ -1936,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;
 }