Merge branch 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jdelv...
authorLinus Torvalds <torvalds@linux-foundation.org>
Mon, 25 Jul 2011 21:10:34 +0000 (14:10 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Mon, 25 Jul 2011 21:10:34 +0000 (14:10 -0700)
* 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jdelvare/staging:
  hwmon: (lm78) Become the maintainer
  hwmon: (lm78) Make ISA interface depend on CONFIG_ISA
  hwmon: (lm78) Avoid forward declarations
  hwmon: (sht15) Correct a comment mistake
  hwmon: (max1111) Avoid extra memory allocations
  hwmon: (it87) Add chassis intrusion detection support
  hwmon: (via-cputemp) Add VID reporting support
  hwmon-vid: Add support for VIA family 6 model D CPU
  hwmon: New driver sch5636
  hwmon: (sch5627) Factor out some code shared with sch5636 driver

16 files changed:
Documentation/hwmon/it87
Documentation/hwmon/lm78
Documentation/hwmon/sch5636 [new file with mode: 0644]
MAINTAINERS
drivers/hwmon/Kconfig
drivers/hwmon/Makefile
drivers/hwmon/hwmon-vid.c
drivers/hwmon/it87.c
drivers/hwmon/lm78.c
drivers/hwmon/max1111.c
drivers/hwmon/sch5627.c
drivers/hwmon/sch5636.c [new file with mode: 0644]
drivers/hwmon/sch56xx-common.c [new file with mode: 0644]
drivers/hwmon/sch56xx-common.h [new file with mode: 0644]
drivers/hwmon/sht15.c
drivers/hwmon/via-cputemp.c

index 38425f0..6f496a5 100644 (file)
@@ -76,7 +76,8 @@ IT8718F, IT8720F, IT8721F, IT8726F, IT8758E and SiS950 chips.
 These chips are 'Super I/O chips', supporting floppy disks, infrared ports,
 joysticks and other miscellaneous stuff. For hardware monitoring, they
 include an 'environment controller' with 3 temperature sensors, 3 fan
-rotation speed sensors, 8 voltage sensors, and associated alarms.
+rotation speed sensors, 8 voltage sensors, associated alarms, and chassis
+intrusion detection.
 
 The IT8712F and IT8716F additionally feature VID inputs, used to report
 the Vcore voltage of the processor. The early IT8712F have 5 VID pins,
index 60932e2..2bdc881 100644 (file)
@@ -13,7 +13,8 @@ Supported chips:
     Datasheet: Publicly available at the National Semiconductor website
                http://www.national.com/
 
-Author: Frodo Looijaard <frodol@dds.nl>
+Authors: Frodo Looijaard <frodol@dds.nl>
+         Jean Delvare <khali@linux-fr.org>
 
 Description
 -----------
diff --git a/Documentation/hwmon/sch5636 b/Documentation/hwmon/sch5636
new file mode 100644 (file)
index 0000000..f83bd1c
--- /dev/null
@@ -0,0 +1,31 @@
+Kernel driver sch5636
+=====================
+
+Supported chips:
+  * SMSC SCH5636
+    Prefix: 'sch5636'
+    Addresses scanned: none, address read from Super I/O config space
+
+Author: Hans de Goede <hdegoede@redhat.com>
+
+
+Description
+-----------
+
+SMSC SCH5636 Super I/O chips include an embedded microcontroller for
+hardware monitoring solutions, allowing motherboard manufacturers to create
+their own custom hwmon solution based upon the SCH5636.
+
+Currently the sch5636 driver only supports the Fujitsu Theseus SCH5636 based
+hwmon solution. The sch5636 driver runs a sanity check on loading to ensure
+it is dealing with a Fujitsu Theseus and not with another custom SCH5636 based
+hwmon solution.
+
+The Fujitsu Theseus can monitor up to 5 voltages, 8 fans and 16
+temperatures. Note that the driver detects how many fan headers /
+temperature sensors are actually implemented on the motherboard, so you will
+likely see fewer temperature and fan inputs.
+
+An application note describing the Theseus' registers, as well as an
+application note describing the protocol for communicating with the
+microcontroller is available upon request. Please mail me if you want a copy.
index efe0a4b..91e5cc7 100644 (file)
@@ -3960,6 +3960,13 @@ L:       lm-sensors@lm-sensors.org
 S:     Maintained
 F:     drivers/hwmon/lm73.c
 
+LM78 HARDWARE MONITOR DRIVER
+M:     Jean Delvare <khali@linux-fr.org>
+L:     lm-sensors@lm-sensors.org
+S:     Maintained
+F:     Documentation/hwmon/lm78
+F:     drivers/hwmon/lm78.c
+
 LM83 HARDWARE MONITOR DRIVER
 M:     Jean Delvare <khali@linux-fr.org>
 L:     lm-sensors@lm-sensors.org
index 5f888f7..0598cd2 100644 (file)
@@ -1041,8 +1041,13 @@ config SENSORS_SMSC47B397
          This driver can also be built as a module.  If so, the module
          will be called smsc47b397.
 
+config SENSORS_SCH56XX_COMMON
+       tristate
+       default n
+
 config SENSORS_SCH5627
        tristate "SMSC SCH5627"
+       select SENSORS_SCH56XX_COMMON
        help
          If you say yes here you get support for the hardware monitoring
          features of the SMSC SCH5627 Super-I/O chip.
@@ -1050,6 +1055,21 @@ config SENSORS_SCH5627
          This driver can also be built as a module.  If so, the module
          will be called sch5627.
 
+config SENSORS_SCH5636
+       tristate "SMSC SCH5636"
+       select SENSORS_SCH56XX_COMMON
+       help
+         SMSC SCH5636 Super I/O chips include an embedded microcontroller for
+         hardware monitoring solutions, allowing motherboard manufacturers to
+         create their own custom hwmon solution based upon the SCH5636.
+
+         Currently this driver only supports the Fujitsu Theseus SCH5636 based
+         hwmon solution. Say yes here if you want support for the Fujitsu
+         Theseus' hardware monitoring features.
+
+         This driver can also be built as a module.  If so, the module
+         will be called sch5636.
+
 config SENSORS_ADS1015
        tristate "Texas Instruments ADS1015"
        depends on I2C
@@ -1142,6 +1162,7 @@ config SENSORS_TWL4030_MADC
 config SENSORS_VIA_CPUTEMP
        tristate "VIA CPU temperature sensor"
        depends on X86
+       select HWMON_VID
        help
          If you say yes here you get support for the temperature
          sensor inside your CPU. Supported are all known variants of
index 28061cf..d7995a1 100644 (file)
@@ -95,7 +95,9 @@ obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
 obj-$(CONFIG_SENSORS_PC87427)  += pc87427.o
 obj-$(CONFIG_SENSORS_PCF8591)  += pcf8591.o
 obj-$(CONFIG_SENSORS_S3C)      += s3c-hwmon.o
+obj-$(CONFIG_SENSORS_SCH56XX_COMMON)+= sch56xx-common.o
 obj-$(CONFIG_SENSORS_SCH5627)  += sch5627.o
+obj-$(CONFIG_SENSORS_SCH5636)  += sch5636.o
 obj-$(CONFIG_SENSORS_SHT15)    += sht15.o
 obj-$(CONFIG_SENSORS_SHT21)    += sht21.o
 obj-$(CONFIG_SENSORS_SIS5595)  += sis5595.o
index c8195a0..932da8a 100644 (file)
@@ -140,7 +140,11 @@ int vid_from_reg(int val, u8 vrm)
                return(val & 0x10 ? 975 - (val & 0xF) * 25 :
                                    1750 - val * 50);
        case 13:
+       case 131:
                val &= 0x3f;
+               /* Exception for Eden ULV 500 MHz */
+               if (vrm == 131 && val == 0x3f)
+                       val++;
                return(1708 - val * 16);
        case 14:                /* Intel Core */
                                /* compute in uV, round to mV */
@@ -205,11 +209,45 @@ static struct vrm_model vrm_models[] = {
        {X86_VENDOR_CENTAUR, 0x6, 0x9, 0x7, 85},        /* Nehemiah */
        {X86_VENDOR_CENTAUR, 0x6, 0x9, ANY, 17},        /* C3-M, Eden-N */
        {X86_VENDOR_CENTAUR, 0x6, 0xA, 0x7, 0},         /* No information */
-       {X86_VENDOR_CENTAUR, 0x6, 0xA, ANY, 13},        /* C7, Esther */
+       {X86_VENDOR_CENTAUR, 0x6, 0xA, ANY, 13},        /* C7-M, C7, Eden (Esther) */
+       {X86_VENDOR_CENTAUR, 0x6, 0xD, ANY, 134},       /* C7-D, C7-M, C7, Eden (Esther) */
 
        {X86_VENDOR_UNKNOWN, ANY, ANY, ANY, 0}          /* stop here */
 };
 
+/*
+ * Special case for VIA model D: there are two different possible
+ * VID tables, so we have to figure out first, which one must be
+ * used. This resolves temporary drm value 134 to 14 (Intel Core
+ * 7-bit VID), 13 (Pentium M 6-bit VID) or 131 (Pentium M 6-bit VID
+ * + quirk for Eden ULV 500 MHz).
+ * Note: something similar might be needed for model A, I'm not sure.
+ */
+static u8 get_via_model_d_vrm(void)
+{
+       unsigned int vid, brand, dummy;
+       static const char *brands[4] = {
+               "C7-M", "C7", "Eden", "C7-D"
+       };
+
+       rdmsr(0x198, dummy, vid);
+       vid &= 0xff;
+
+       rdmsr(0x1154, brand, dummy);
+       brand = ((brand >> 4) ^ (brand >> 2)) & 0x03;
+
+       if (vid > 0x3f) {
+               pr_info("Using %d-bit VID table for VIA %s CPU\n",
+                       7, brands[brand]);
+               return 14;
+       } else {
+               pr_info("Using %d-bit VID table for VIA %s CPU\n",
+                       6, brands[brand]);
+               /* Enable quirk for Eden */
+               return brand == 2 ? 131 : 13;
+       }
+}
+
 static u8 find_vrm(u8 eff_family, u8 eff_model, u8 eff_stepping, u8 vendor)
 {
        int i = 0;
@@ -247,6 +285,8 @@ u8 vid_which_vrm(void)
                eff_model += ((eax & 0x000F0000)>>16)<<4;
        }
        vrm_ret = find_vrm(eff_family, eff_model, eff_stepping, c->x86_vendor);
+       if (vrm_ret == 134)
+               vrm_ret = get_via_model_d_vrm();
        if (vrm_ret == 0)
                pr_info("Unknown VRM version of your x86 CPU\n");
        return vrm_ret;
index 5f52477..d912649 100644 (file)
@@ -1172,6 +1172,32 @@ static ssize_t show_alarm(struct device *dev, struct device_attribute *attr,
        struct it87_data *data = it87_update_device(dev);
        return sprintf(buf, "%u\n", (data->alarms >> bitnr) & 1);
 }
+
+static ssize_t clear_intrusion(struct device *dev, struct device_attribute
+               *attr, const char *buf, size_t count)
+{
+       struct it87_data *data = dev_get_drvdata(dev);
+       long val;
+       int config;
+
+       if (strict_strtol(buf, 10, &val) < 0 || val != 0)
+               return -EINVAL;
+
+       mutex_lock(&data->update_lock);
+       config = it87_read_value(data, IT87_REG_CONFIG);
+       if (config < 0) {
+               count = config;
+       } else {
+               config |= 1 << 5;
+               it87_write_value(data, IT87_REG_CONFIG, config);
+               /* Invalidate cache to force re-read */
+               data->valid = 0;
+       }
+       mutex_unlock(&data->update_lock);
+
+       return count;
+}
+
 static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 8);
 static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 9);
 static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 10);
@@ -1188,6 +1214,8 @@ static SENSOR_DEVICE_ATTR(fan5_alarm, S_IRUGO, show_alarm, NULL, 6);
 static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 16);
 static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 17);
 static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 18);
+static SENSOR_DEVICE_ATTR(intrusion0_alarm, S_IRUGO | S_IWUSR,
+                         show_alarm, clear_intrusion, 4);
 
 static ssize_t show_beep(struct device *dev, struct device_attribute *attr,
                char *buf)
@@ -1350,6 +1378,7 @@ static struct attribute *it87_attributes[] = {
        &sensor_dev_attr_temp3_alarm.dev_attr.attr,
 
        &dev_attr_alarms.attr,
+       &sensor_dev_attr_intrusion0_alarm.dev_attr.attr,
        &dev_attr_name.attr,
        NULL
 };
index 4cb24ea..6df0b46 100644 (file)
@@ -2,7 +2,7 @@
     lm78.c - Part of lm_sensors, Linux kernel modules for hardware
              monitoring
     Copyright (c) 1998, 1999  Frodo Looijaard <frodol@dds.nl> 
-    Copyright (c) 2007        Jean Delvare <khali@linux-fr.org>
+    Copyright (c) 2007, 2011  Jean Delvare <khali@linux-fr.org>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
 #include <linux/slab.h>
 #include <linux/jiffies.h>
 #include <linux/i2c.h>
-#include <linux/platform_device.h>
-#include <linux/ioport.h>
 #include <linux/hwmon.h>
 #include <linux/hwmon-vid.h>
 #include <linux/hwmon-sysfs.h>
 #include <linux/err.h>
 #include <linux/mutex.h>
-#include <linux/io.h>
 
-/* ISA device, if found */
-static struct platform_device *pdev;
+#ifdef CONFIG_ISA
+#include <linux/platform_device.h>
+#include <linux/ioport.h>
+#include <linux/io.h>
+#endif
 
 /* Addresses to scan */
 static const unsigned short normal_i2c[] = { 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
                                                0x2e, 0x2f, I2C_CLIENT_END };
-static unsigned short isa_address = 0x290;
-
 enum chips { lm78, lm79 };
 
 /* Many LM78 constants specified below */
@@ -143,50 +141,12 @@ struct lm78_data {
 };
 
 
-static int lm78_i2c_detect(struct i2c_client *client,
-                          struct i2c_board_info *info);
-static int lm78_i2c_probe(struct i2c_client *client,
-                         const struct i2c_device_id *id);
-static int lm78_i2c_remove(struct i2c_client *client);
-
-static int __devinit lm78_isa_probe(struct platform_device *pdev);
-static int __devexit lm78_isa_remove(struct platform_device *pdev);
-
 static int lm78_read_value(struct lm78_data *data, u8 reg);
 static int lm78_write_value(struct lm78_data *data, u8 reg, u8 value);
 static struct lm78_data *lm78_update_device(struct device *dev);
 static void lm78_init_device(struct lm78_data *data);
 
 
-static const struct i2c_device_id lm78_i2c_id[] = {
-       { "lm78", lm78 },
-       { "lm79", lm79 },
-       { }
-};
-MODULE_DEVICE_TABLE(i2c, lm78_i2c_id);
-
-static struct i2c_driver lm78_driver = {
-       .class          = I2C_CLASS_HWMON,
-       .driver = {
-               .name   = "lm78",
-       },
-       .probe          = lm78_i2c_probe,
-       .remove         = lm78_i2c_remove,
-       .id_table       = lm78_i2c_id,
-       .detect         = lm78_i2c_detect,
-       .address_list   = normal_i2c,
-};
-
-static struct platform_driver lm78_isa_driver = {
-       .driver = {
-               .owner  = THIS_MODULE,
-               .name   = "lm78",
-       },
-       .probe          = lm78_isa_probe,
-       .remove         = __devexit_p(lm78_isa_remove),
-};
-
-
 /* 7 Voltages */
 static ssize_t show_in(struct device *dev, struct device_attribute *da,
                       char *buf)
@@ -514,6 +474,16 @@ static const struct attribute_group lm78_group = {
        .attrs = lm78_attributes,
 };
 
+/*
+ * ISA related code
+ */
+#ifdef CONFIG_ISA
+
+/* ISA device, if found */
+static struct platform_device *pdev;
+
+static unsigned short isa_address = 0x290;
+
 /* I2C devices get this name attribute automatically, but for ISA devices
    we must create it by ourselves. */
 static ssize_t show_name(struct device *dev, struct device_attribute
@@ -525,6 +495,11 @@ static ssize_t show_name(struct device *dev, struct device_attribute
 }
 static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
 
+static struct lm78_data *lm78_data_if_isa(void)
+{
+       return pdev ? platform_get_drvdata(pdev) : 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)
 {
@@ -558,12 +533,24 @@ static int lm78_alias_detect(struct i2c_client *client, u8 chipid)
 
        return 1;
 }
+#else /* !CONFIG_ISA */
+
+static int lm78_alias_detect(struct i2c_client *client, u8 chipid)
+{
+       return 0;
+}
+
+static struct lm78_data *lm78_data_if_isa(void)
+{
+       return NULL;
+}
+#endif /* CONFIG_ISA */
 
 static int lm78_i2c_detect(struct i2c_client *client,
                           struct i2c_board_info *info)
 {
        int i;
-       struct lm78_data *isa = pdev ? platform_get_drvdata(pdev) : NULL;
+       struct lm78_data *isa = lm78_data_if_isa();
        const char *client_name;
        struct i2c_adapter *adapter = client->adapter;
        int address = client->addr;
@@ -663,76 +650,24 @@ static int lm78_i2c_remove(struct i2c_client *client)
        return 0;
 }
 
-static int __devinit lm78_isa_probe(struct platform_device *pdev)
-{
-       int err;
-       struct lm78_data *data;
-       struct resource *res;
-
-       /* Reserve the ISA region */
-       res = platform_get_resource(pdev, IORESOURCE_IO, 0);
-       if (!request_region(res->start + LM78_ADDR_REG_OFFSET, 2, "lm78")) {
-               err = -EBUSY;
-               goto exit;
-       }
-
-       if (!(data = kzalloc(sizeof(struct lm78_data), GFP_KERNEL))) {
-               err = -ENOMEM;
-               goto exit_release_region;
-       }
-       mutex_init(&data->lock);
-       data->isa_addr = res->start;
-       platform_set_drvdata(pdev, data);
-
-       if (lm78_read_value(data, LM78_REG_CHIPID) & 0x80) {
-               data->type = lm79;
-               data->name = "lm79";
-       } else {
-               data->type = lm78;
-               data->name = "lm78";
-       }
-
-       /* Initialize the LM78 chip */
-       lm78_init_device(data);
-
-       /* Register sysfs hooks */
-       if ((err = sysfs_create_group(&pdev->dev.kobj, &lm78_group))
-        || (err = device_create_file(&pdev->dev, &dev_attr_name)))
-               goto exit_remove_files;
-
-       data->hwmon_dev = hwmon_device_register(&pdev->dev);
-       if (IS_ERR(data->hwmon_dev)) {
-               err = PTR_ERR(data->hwmon_dev);
-               goto exit_remove_files;
-       }
-
-       return 0;
-
- exit_remove_files:
-       sysfs_remove_group(&pdev->dev.kobj, &lm78_group);
-       device_remove_file(&pdev->dev, &dev_attr_name);
-       kfree(data);
- exit_release_region:
-       release_region(res->start + LM78_ADDR_REG_OFFSET, 2);
- exit:
-       return err;
-}
-
-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);
-       kfree(data);
-
-       res = platform_get_resource(pdev, IORESOURCE_IO, 0);
-       release_region(res->start + LM78_ADDR_REG_OFFSET, 2);
+static const struct i2c_device_id lm78_i2c_id[] = {
+       { "lm78", lm78 },
+       { "lm79", lm79 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, lm78_i2c_id);
 
-       return 0;
-}
+static struct i2c_driver lm78_driver = {
+       .class          = I2C_CLASS_HWMON,
+       .driver = {
+               .name   = "lm78",
+       },
+       .probe          = lm78_i2c_probe,
+       .remove         = lm78_i2c_remove,
+       .id_table       = lm78_i2c_id,
+       .detect         = lm78_i2c_detect,
+       .address_list   = normal_i2c,
+};
 
 /* The SMBus locks itself, but ISA access must be locked explicitly! 
    We don't want to lock the whole ISA bus, so we lock each client
@@ -743,6 +678,7 @@ static int lm78_read_value(struct lm78_data *data, u8 reg)
 {
        struct i2c_client *client = data->client;
 
+#ifdef CONFIG_ISA
        if (!client) { /* ISA device */
                int res;
                mutex_lock(&data->lock);
@@ -751,6 +687,7 @@ static int lm78_read_value(struct lm78_data *data, u8 reg)
                mutex_unlock(&data->lock);
                return res;
        } else
+#endif
                return i2c_smbus_read_byte_data(client, reg);
 }
 
@@ -765,6 +702,7 @@ static int lm78_write_value(struct lm78_data *data, u8 reg, u8 value)
 {
        struct i2c_client *client = data->client;
 
+#ifdef CONFIG_ISA
        if (!client) { /* ISA device */
                mutex_lock(&data->lock);
                outb_p(reg, data->isa_addr + LM78_ADDR_REG_OFFSET);
@@ -772,6 +710,7 @@ static int lm78_write_value(struct lm78_data *data, u8 reg, u8 value)
                mutex_unlock(&data->lock);
                return 0;
        } else
+#endif
                return i2c_smbus_write_byte_data(client, reg, value);
 }
 
@@ -849,6 +788,88 @@ static struct lm78_data *lm78_update_device(struct device *dev)
        return data;
 }
 
+#ifdef CONFIG_ISA
+static int __devinit lm78_isa_probe(struct platform_device *pdev)
+{
+       int err;
+       struct lm78_data *data;
+       struct resource *res;
+
+       /* Reserve the ISA region */
+       res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+       if (!request_region(res->start + LM78_ADDR_REG_OFFSET, 2, "lm78")) {
+               err = -EBUSY;
+               goto exit;
+       }
+
+       data = kzalloc(sizeof(struct lm78_data), GFP_KERNEL);
+       if (!data) {
+               err = -ENOMEM;
+               goto exit_release_region;
+       }
+       mutex_init(&data->lock);
+       data->isa_addr = res->start;
+       platform_set_drvdata(pdev, data);
+
+       if (lm78_read_value(data, LM78_REG_CHIPID) & 0x80) {
+               data->type = lm79;
+               data->name = "lm79";
+       } else {
+               data->type = lm78;
+               data->name = "lm78";
+       }
+
+       /* Initialize the LM78 chip */
+       lm78_init_device(data);
+
+       /* Register sysfs hooks */
+       if ((err = sysfs_create_group(&pdev->dev.kobj, &lm78_group))
+        || (err = device_create_file(&pdev->dev, &dev_attr_name)))
+               goto exit_remove_files;
+
+       data->hwmon_dev = hwmon_device_register(&pdev->dev);
+       if (IS_ERR(data->hwmon_dev)) {
+               err = PTR_ERR(data->hwmon_dev);
+               goto exit_remove_files;
+       }
+
+       return 0;
+
+ exit_remove_files:
+       sysfs_remove_group(&pdev->dev.kobj, &lm78_group);
+       device_remove_file(&pdev->dev, &dev_attr_name);
+       kfree(data);
+ exit_release_region:
+       release_region(res->start + LM78_ADDR_REG_OFFSET, 2);
+ exit:
+       return err;
+}
+
+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);
+       kfree(data);
+
+       res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+       release_region(res->start + LM78_ADDR_REG_OFFSET, 2);
+
+       return 0;
+}
+
+static struct platform_driver lm78_isa_driver = {
+       .driver = {
+               .owner  = THIS_MODULE,
+               .name   = "lm78",
+       },
+       .probe          = lm78_isa_probe,
+       .remove         = __devexit_p(lm78_isa_remove),
+};
+
 /* return 1 if a supported chip is found, 0 otherwise */
 static int __init lm78_isa_found(unsigned short address)
 {
@@ -969,12 +990,10 @@ static int __init lm78_isa_device_add(unsigned short address)
        return err;
 }
 
-static int __init sm_lm78_init(void)
+static int __init lm78_isa_register(void)
 {
        int res;
 
-       /* 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)
@@ -986,32 +1005,62 @@ 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:
        return res;
 }
 
-static void __exit sm_lm78_exit(void)
+static void lm78_isa_unregister(void)
 {
        if (pdev) {
                platform_device_unregister(pdev);
                platform_driver_unregister(&lm78_isa_driver);
        }
-       i2c_del_driver(&lm78_driver);
 }
+#else /* !CONFIG_ISA */
 
+static int __init lm78_isa_register(void)
+{
+       return 0;
+}
+
+static void lm78_isa_unregister(void)
+{
+}
+#endif /* CONFIG_ISA */
 
+static int __init sm_lm78_init(void)
+{
+       int res;
+
+       /* We register the ISA device first, so that we can skip the
+        * registration of an I2C interface to the same device. */
+       res = lm78_isa_register();
+       if (res)
+               goto exit;
+
+       res = i2c_add_driver(&lm78_driver);
+       if (res)
+               goto exit_unreg_isa_device;
+
+       return 0;
+
+ exit_unreg_isa_device:
+       lm78_isa_unregister();
+ exit:
+       return res;
+}
+
+static void __exit sm_lm78_exit(void)
+{
+       lm78_isa_unregister();
+       i2c_del_driver(&lm78_driver);
+}
 
-MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>");
+MODULE_AUTHOR("Frodo Looijaard, Jean Delvare <khali@linux-fr.org>");
 MODULE_DESCRIPTION("LM78/LM79 driver");
 MODULE_LICENSE("GPL");
 
index 14335bb..c97b78e 100644 (file)
@@ -38,8 +38,8 @@ struct max1111_data {
        struct device           *hwmon_dev;
        struct spi_message      msg;
        struct spi_transfer     xfer[2];
-       uint8_t *tx_buf;
-       uint8_t *rx_buf;
+       uint8_t tx_buf[MAX1111_TX_BUF_SIZE];
+       uint8_t rx_buf[MAX1111_RX_BUF_SIZE];
        struct mutex            drvdata_lock;
        /* protect msg, xfer and buffers from multiple access */
 };
@@ -131,33 +131,23 @@ static const struct attribute_group max1111_attr_group = {
        .attrs  = max1111_attributes,
 };
 
-static int setup_transfer(struct max1111_data *data)
+static int __devinit setup_transfer(struct max1111_data *data)
 {
        struct spi_message *m;
        struct spi_transfer *x;
 
-       data->tx_buf = kmalloc(MAX1111_TX_BUF_SIZE, GFP_KERNEL);
-       if (!data->tx_buf)
-               return -ENOMEM;
-
-       data->rx_buf = kmalloc(MAX1111_RX_BUF_SIZE, GFP_KERNEL);
-       if (!data->rx_buf) {
-               kfree(data->tx_buf);
-               return -ENOMEM;
-       }
-
        m = &data->msg;
        x = &data->xfer[0];
 
        spi_message_init(m);
 
        x->tx_buf = &data->tx_buf[0];
-       x->len = 1;
+       x->len = MAX1111_TX_BUF_SIZE;
        spi_message_add_tail(x, m);
 
        x++;
        x->rx_buf = &data->rx_buf[0];
-       x->len = 2;
+       x->len = MAX1111_RX_BUF_SIZE;
        spi_message_add_tail(x, m);
 
        return 0;
@@ -192,7 +182,7 @@ static int __devinit max1111_probe(struct spi_device *spi)
        err = sysfs_create_group(&spi->dev.kobj, &max1111_attr_group);
        if (err) {
                dev_err(&spi->dev, "failed to create attribute group\n");
-               goto err_free_all;
+               goto err_free_data;
        }
 
        data->hwmon_dev = hwmon_device_register(&spi->dev);
@@ -209,9 +199,6 @@ static int __devinit max1111_probe(struct spi_device *spi)
 
 err_remove:
        sysfs_remove_group(&spi->dev.kobj, &max1111_attr_group);
-err_free_all:
-       kfree(data->rx_buf);
-       kfree(data->tx_buf);
 err_free_data:
        kfree(data);
        return err;
@@ -224,8 +211,6 @@ static int __devexit max1111_remove(struct spi_device *spi)
        hwmon_device_unregister(data->hwmon_dev);
        sysfs_remove_group(&spi->dev.kobj, &max1111_attr_group);
        mutex_destroy(&data->drvdata_lock);
-       kfree(data->rx_buf);
-       kfree(data->tx_buf);
        kfree(data);
        return 0;
 }
index 3494a4c..e3b5c60 100644 (file)
 #include <linux/hwmon-sysfs.h>
 #include <linux/err.h>
 #include <linux/mutex.h>
-#include <linux/io.h>
-#include <linux/acpi.h>
-#include <linux/delay.h>
+#include "sch56xx-common.h"
 
 #define DRVNAME "sch5627"
 #define DEVNAME DRVNAME /* We only support one model */
 
-#define SIO_SCH5627_EM_LD      0x0C    /* Embedded Microcontroller LD */
-#define SIO_UNLOCK_KEY         0x55    /* Key to enable Super-I/O */
-#define SIO_LOCK_KEY           0xAA    /* Key to disable Super-I/O */
-
-#define SIO_REG_LDSEL          0x07    /* Logical device select */
-#define SIO_REG_DEVID          0x20    /* Device ID */
-#define SIO_REG_ENABLE         0x30    /* Logical device enable */
-#define SIO_REG_ADDR           0x66    /* Logical device address (2 bytes) */
-
-#define SIO_SCH5627_ID         0xC6    /* Chipset ID */
-
-#define REGION_LENGTH          9
-
 #define SCH5627_HWMON_ID               0xa5
 #define SCH5627_COMPANY_ID             0x5c
 #define SCH5627_PRIMARY_ID             0xa0
 
-#define SCH5627_CMD_READ               0x02
-#define SCH5627_CMD_WRITE              0x03
-
 #define SCH5627_REG_BUILD_CODE         0x39
 #define SCH5627_REG_BUILD_ID           0x3a
 #define SCH5627_REG_HWMON_ID           0x3c
@@ -111,182 +93,6 @@ struct sch5627_data {
        u16 in[SCH5627_NO_IN];
 };
 
-static struct platform_device *sch5627_pdev;
-
-/* Super I/O functions */
-static inline int superio_inb(int base, int reg)
-{
-       outb(reg, base);
-       return inb(base + 1);
-}
-
-static inline int superio_enter(int base)
-{
-       /* Don't step on other drivers' I/O space by accident */
-       if (!request_muxed_region(base, 2, DRVNAME)) {
-               pr_err("I/O address 0x%04x already in use\n", base);
-               return -EBUSY;
-       }
-
-       outb(SIO_UNLOCK_KEY, base);
-
-       return 0;
-}
-
-static inline void superio_select(int base, int ld)
-{
-       outb(SIO_REG_LDSEL, base);
-       outb(ld, base + 1);
-}
-
-static inline void superio_exit(int base)
-{
-       outb(SIO_LOCK_KEY, base);
-       release_region(base, 2);
-}
-
-static int sch5627_send_cmd(struct sch5627_data *data, u8 cmd, u16 reg, u8 v)
-{
-       u8 val;
-       int i;
-       /*
-        * According to SMSC for the commands we use the maximum time for
-        * the EM to respond is 15 ms, but testing shows in practice it
-        * responds within 15-32 reads, so we first busy poll, and if
-        * that fails sleep a bit and try again until we are way past
-        * the 15 ms maximum response time.
-        */
-       const int max_busy_polls = 64;
-       const int max_lazy_polls = 32;
-
-       /* (Optional) Write-Clear the EC to Host Mailbox Register */
-       val = inb(data->addr + 1);
-       outb(val, data->addr + 1);
-
-       /* Set Mailbox Address Pointer to first location in Region 1 */
-       outb(0x00, data->addr + 2);
-       outb(0x80, data->addr + 3);
-
-       /* Write Request Packet Header */
-       outb(cmd, data->addr + 4); /* VREG Access Type read:0x02 write:0x03 */
-       outb(0x01, data->addr + 5); /* # of Entries: 1 Byte (8-bit) */
-       outb(0x04, data->addr + 2); /* Mailbox AP to first data entry loc. */
-
-       /* Write Value field */
-       if (cmd == SCH5627_CMD_WRITE)
-               outb(v, data->addr + 4);
-
-       /* Write Address field */
-       outb(reg & 0xff, data->addr + 6);
-       outb(reg >> 8, data->addr + 7);
-
-       /* Execute the Random Access Command */
-       outb(0x01, data->addr); /* Write 01h to the Host-to-EC register */
-
-       /* EM Interface Polling "Algorithm" */
-       for (i = 0; i < max_busy_polls + max_lazy_polls; i++) {
-               if (i >= max_busy_polls)
-                       msleep(1);
-               /* Read Interrupt source Register */
-               val = inb(data->addr + 8);
-               /* Write Clear the interrupt source bits */
-               if (val)
-                       outb(val, data->addr + 8);
-               /* Command Completed ? */
-               if (val & 0x01)
-                       break;
-       }
-       if (i == max_busy_polls + max_lazy_polls) {
-               pr_err("Max retries exceeded reading virtual "
-                      "register 0x%04hx (%d)\n", reg, 1);
-               return -EIO;
-       }
-
-       /*
-        * According to SMSC we may need to retry this, but sofar I've always
-        * seen this succeed in 1 try.
-        */
-       for (i = 0; i < max_busy_polls; i++) {
-               /* Read EC-to-Host Register */
-               val = inb(data->addr + 1);
-               /* Command Completed ? */
-               if (val == 0x01)
-                       break;
-
-               if (i == 0)
-                       pr_warn("EC reports: 0x%02x reading virtual register "
-                               "0x%04hx\n", (unsigned int)val, reg);
-       }
-       if (i == max_busy_polls) {
-               pr_err("Max retries exceeded reading virtual "
-                      "register 0x%04hx (%d)\n", reg, 2);
-               return -EIO;
-       }
-
-       /*
-        * According to the SMSC app note we should now do:
-        *
-        * Set Mailbox Address Pointer to first location in Region 1 *
-        * outb(0x00, data->addr + 2);
-        * outb(0x80, data->addr + 3);
-        *
-        * But if we do that things don't work, so let's not.
-        */
-
-       /* Read Value field */
-       if (cmd == SCH5627_CMD_READ)
-               return inb(data->addr + 4);
-
-       return 0;
-}
-
-static int sch5627_read_virtual_reg(struct sch5627_data *data, u16 reg)
-{
-       return sch5627_send_cmd(data, SCH5627_CMD_READ, reg, 0);
-}
-
-static int sch5627_write_virtual_reg(struct sch5627_data *data,
-                                    u16 reg, u8 val)
-{
-       return sch5627_send_cmd(data, SCH5627_CMD_WRITE, reg, val);
-}
-
-static int sch5627_read_virtual_reg16(struct sch5627_data *data, u16 reg)
-{
-       int lsb, msb;
-
-       /* Read LSB first, this will cause the matching MSB to be latched */
-       lsb = sch5627_read_virtual_reg(data, reg);
-       if (lsb < 0)
-               return lsb;
-
-       msb = sch5627_read_virtual_reg(data, reg + 1);
-       if (msb < 0)
-               return msb;
-
-       return lsb | (msb << 8);
-}
-
-static int sch5627_read_virtual_reg12(struct sch5627_data *data, u16 msb_reg,
-                                     u16 lsn_reg, int high_nibble)
-{
-       int msb, lsn;
-
-       /* Read MSB first, this will cause the matching LSN to be latched */
-       msb = sch5627_read_virtual_reg(data, msb_reg);
-       if (msb < 0)
-               return msb;
-
-       lsn = sch5627_read_virtual_reg(data, lsn_reg);
-       if (lsn < 0)
-               return lsn;
-
-       if (high_nibble)
-               return (msb << 4) | (lsn >> 4);
-       else
-               return (msb << 4) | (lsn & 0x0f);
-}
-
 static struct sch5627_data *sch5627_update_device(struct device *dev)
 {
        struct sch5627_data *data = dev_get_drvdata(dev);
@@ -297,7 +103,7 @@ static struct sch5627_data *sch5627_update_device(struct device *dev)
 
        /* Trigger a Vbat voltage measurement every 5 minutes */
        if (time_after(jiffies, data->last_battery + 300 * HZ)) {
-               sch5627_write_virtual_reg(data, SCH5627_REG_CTRL,
+               sch56xx_write_virtual_reg(data->addr, SCH5627_REG_CTRL,
                                          data->control | 0x10);
                data->last_battery = jiffies;
        }
@@ -305,7 +111,7 @@ static struct sch5627_data *sch5627_update_device(struct device *dev)
        /* Cache the values for 1 second */
        if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
                for (i = 0; i < SCH5627_NO_TEMPS; i++) {
-                       val = sch5627_read_virtual_reg12(data,
+                       val = sch56xx_read_virtual_reg12(data->addr,
                                SCH5627_REG_TEMP_MSB[i],
                                SCH5627_REG_TEMP_LSN[i],
                                SCH5627_REG_TEMP_HIGH_NIBBLE[i]);
@@ -317,7 +123,7 @@ static struct sch5627_data *sch5627_update_device(struct device *dev)
                }
 
                for (i = 0; i < SCH5627_NO_FANS; i++) {
-                       val = sch5627_read_virtual_reg16(data,
+                       val = sch56xx_read_virtual_reg16(data->addr,
                                                         SCH5627_REG_FAN[i]);
                        if (unlikely(val < 0)) {
                                ret = ERR_PTR(val);
@@ -327,7 +133,7 @@ static struct sch5627_data *sch5627_update_device(struct device *dev)
                }
 
                for (i = 0; i < SCH5627_NO_IN; i++) {
-                       val = sch5627_read_virtual_reg12(data,
+                       val = sch56xx_read_virtual_reg12(data->addr,
                                SCH5627_REG_IN_MSB[i],
                                SCH5627_REG_IN_LSN[i],
                                SCH5627_REG_IN_HIGH_NIBBLE[i]);
@@ -355,18 +161,21 @@ static int __devinit sch5627_read_limits(struct sch5627_data *data)
                 * Note what SMSC calls ABS, is what lm_sensors calls max
                 * (aka high), and HIGH is what lm_sensors calls crit.
                 */
-               val = sch5627_read_virtual_reg(data, SCH5627_REG_TEMP_ABS[i]);
+               val = sch56xx_read_virtual_reg(data->addr,
+                                              SCH5627_REG_TEMP_ABS[i]);
                if (val < 0)
                        return val;
                data->temp_max[i] = val;
 
-               val = sch5627_read_virtual_reg(data, SCH5627_REG_TEMP_HIGH[i]);
+               val = sch56xx_read_virtual_reg(data->addr,
+                                              SCH5627_REG_TEMP_HIGH[i]);
                if (val < 0)
                        return val;
                data->temp_crit[i] = val;
        }
        for (i = 0; i < SCH5627_NO_FANS; i++) {
-               val = sch5627_read_virtual_reg16(data, SCH5627_REG_FAN_MIN[i]);
+               val = sch56xx_read_virtual_reg16(data->addr,
+                                                SCH5627_REG_FAN_MIN[i]);
                if (val < 0)
                        return val;
                data->fan_min[i] = val;
@@ -667,7 +476,7 @@ static int __devinit sch5627_probe(struct platform_device *pdev)
        mutex_init(&data->update_lock);
        platform_set_drvdata(pdev, data);
 
-       val = sch5627_read_virtual_reg(data, SCH5627_REG_HWMON_ID);
+       val = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_HWMON_ID);
        if (val < 0) {
                err = val;
                goto error;
@@ -679,7 +488,7 @@ static int __devinit sch5627_probe(struct platform_device *pdev)
                goto error;
        }
 
-       val = sch5627_read_virtual_reg(data, SCH5627_REG_COMPANY_ID);
+       val = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_COMPANY_ID);
        if (val < 0) {
                err = val;
                goto error;
@@ -691,7 +500,7 @@ static int __devinit sch5627_probe(struct platform_device *pdev)
                goto error;
        }
 
-       val = sch5627_read_virtual_reg(data, SCH5627_REG_PRIMARY_ID);
+       val = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_PRIMARY_ID);
        if (val < 0) {
                err = val;
                goto error;
@@ -703,25 +512,28 @@ static int __devinit sch5627_probe(struct platform_device *pdev)
                goto error;
        }
 
-       build_code = sch5627_read_virtual_reg(data, SCH5627_REG_BUILD_CODE);
+       build_code = sch56xx_read_virtual_reg(data->addr,
+                                             SCH5627_REG_BUILD_CODE);
        if (build_code < 0) {
                err = build_code;
                goto error;
        }
 
-       build_id = sch5627_read_virtual_reg16(data, SCH5627_REG_BUILD_ID);
+       build_id = sch56xx_read_virtual_reg16(data->addr,
+                                             SCH5627_REG_BUILD_ID);
        if (build_id < 0) {
                err = build_id;
                goto error;
        }
 
-       hwmon_rev = sch5627_read_virtual_reg(data, SCH5627_REG_HWMON_REV);
+       hwmon_rev = sch56xx_read_virtual_reg(data->addr,
+                                            SCH5627_REG_HWMON_REV);
        if (hwmon_rev < 0) {
                err = hwmon_rev;
                goto error;
        }
 
-       val = sch5627_read_virtual_reg(data, SCH5627_REG_CTRL);
+       val = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_CTRL);
        if (val < 0) {
                err = val;
                goto error;
@@ -734,7 +546,7 @@ static int __devinit sch5627_probe(struct platform_device *pdev)
        }
        /* Trigger a Vbat voltage measurement, so that we get a valid reading
           the first time we read Vbat */
-       sch5627_write_virtual_reg(data, SCH5627_REG_CTRL,
+       sch56xx_write_virtual_reg(data->addr, SCH5627_REG_CTRL,
                                  data->control | 0x10);
        data->last_battery = jiffies;
 
@@ -746,6 +558,7 @@ static int __devinit sch5627_probe(struct platform_device *pdev)
        if (err)
                goto error;
 
+       pr_info("found %s chip at %#hx\n", DEVNAME, data->addr);
        pr_info("firmware build: code 0x%02X, id 0x%04X, hwmon: rev 0x%02X\n",
                build_code, build_id, hwmon_rev);
 
@@ -768,85 +581,6 @@ error:
        return err;
 }
 
-static int __init sch5627_find(int sioaddr, unsigned short *address)
-{
-       u8 devid;
-       int err = superio_enter(sioaddr);
-       if (err)
-               return err;
-
-       devid = superio_inb(sioaddr, SIO_REG_DEVID);
-       if (devid != SIO_SCH5627_ID) {
-               pr_debug("Unsupported device id: 0x%02x\n",
-                        (unsigned int)devid);
-               err = -ENODEV;
-               goto exit;
-       }
-
-       superio_select(sioaddr, SIO_SCH5627_EM_LD);
-
-       if (!(superio_inb(sioaddr, SIO_REG_ENABLE) & 0x01)) {
-               pr_warn("Device not activated\n");
-               err = -ENODEV;
-               goto exit;
-       }
-
-       /*
-        * Warning the order of the low / high byte is the other way around
-        * as on most other superio devices!!
-        */
-       *address = superio_inb(sioaddr, SIO_REG_ADDR) |
-                  superio_inb(sioaddr, SIO_REG_ADDR + 1) << 8;
-       if (*address == 0) {
-               pr_warn("Base address not set\n");
-               err = -ENODEV;
-               goto exit;
-       }
-
-       pr_info("Found %s chip at %#hx\n", DEVNAME, *address);
-exit:
-       superio_exit(sioaddr);
-       return err;
-}
-
-static int __init sch5627_device_add(unsigned short address)
-{
-       struct resource res = {
-               .start  = address,
-               .end    = address + REGION_LENGTH - 1,
-               .flags  = IORESOURCE_IO,
-       };
-       int err;
-
-       sch5627_pdev = platform_device_alloc(DRVNAME, address);
-       if (!sch5627_pdev)
-               return -ENOMEM;
-
-       res.name = sch5627_pdev->name;
-       err = acpi_check_resource_conflict(&res);
-       if (err)
-               goto exit_device_put;
-
-       err = platform_device_add_resources(sch5627_pdev, &res, 1);
-       if (err) {
-               pr_err("Device resource addition failed\n");
-               goto exit_device_put;
-       }
-
-       err = platform_device_add(sch5627_pdev);
-       if (err) {
-               pr_err("Device addition failed\n");
-               goto exit_device_put;
-       }
-
-       return 0;
-
-exit_device_put:
-       platform_device_put(sch5627_pdev);
-
-       return err;
-}
-
 static struct platform_driver sch5627_driver = {
        .driver = {
                .owner  = THIS_MODULE,
@@ -858,31 +592,11 @@ static struct platform_driver sch5627_driver = {
 
 static int __init sch5627_init(void)
 {
-       int err = -ENODEV;
-       unsigned short address;
-
-       if (sch5627_find(0x4e, &address) && sch5627_find(0x2e, &address))
-               goto exit;
-
-       err = platform_driver_register(&sch5627_driver);
-       if (err)
-               goto exit;
-
-       err = sch5627_device_add(address);
-       if (err)
-               goto exit_driver;
-
-       return 0;
-
-exit_driver:
-       platform_driver_unregister(&sch5627_driver);
-exit:
-       return err;
+       return platform_driver_register(&sch5627_driver);
 }
 
 static void __exit sch5627_exit(void)
 {
-       platform_device_unregister(sch5627_pdev);
        platform_driver_unregister(&sch5627_driver);
 }
 
diff --git a/drivers/hwmon/sch5636.c b/drivers/hwmon/sch5636.c
new file mode 100644 (file)
index 0000000..244407a
--- /dev/null
@@ -0,0 +1,539 @@
+/***************************************************************************
+ *   Copyright (C) 2011 Hans de Goede <hdegoede@redhat.com>                *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+ ***************************************************************************/
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/platform_device.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include "sch56xx-common.h"
+
+#define DRVNAME "sch5636"
+#define DEVNAME "theseus" /* We only support one model for now */
+
+#define SCH5636_REG_FUJITSU_ID         0x780
+#define SCH5636_REG_FUJITSU_REV                0x783
+
+#define SCH5636_NO_INS                 5
+#define SCH5636_NO_TEMPS               16
+#define SCH5636_NO_FANS                        8
+
+static const u16 SCH5636_REG_IN_VAL[SCH5636_NO_INS] = {
+       0x22, 0x23, 0x24, 0x25, 0x189 };
+static const u16 SCH5636_REG_IN_FACTORS[SCH5636_NO_INS] = {
+       4400, 1500, 4000, 4400, 16000 };
+static const char * const SCH5636_IN_LABELS[SCH5636_NO_INS] = {
+       "3.3V", "VREF", "VBAT", "3.3AUX", "12V" };
+
+static const u16 SCH5636_REG_TEMP_VAL[SCH5636_NO_TEMPS] = {
+       0x2B, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x180, 0x181,
+       0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C };
+#define SCH5636_REG_TEMP_CTRL(i)       (0x790 + (i))
+#define SCH5636_TEMP_WORKING           0x01
+#define SCH5636_TEMP_ALARM             0x02
+#define SCH5636_TEMP_DEACTIVATED       0x80
+
+static const u16 SCH5636_REG_FAN_VAL[SCH5636_NO_FANS] = {
+       0x2C, 0x2E, 0x30, 0x32, 0x62, 0x64, 0x66, 0x68 };
+#define SCH5636_REG_FAN_CTRL(i)                (0x880 + (i))
+/* FAULT in datasheet, but acts as an alarm */
+#define SCH5636_FAN_ALARM              0x04
+#define SCH5636_FAN_NOT_PRESENT                0x08
+#define SCH5636_FAN_DEACTIVATED                0x80
+
+
+struct sch5636_data {
+       unsigned short addr;
+       struct device *hwmon_dev;
+
+       struct mutex update_lock;
+       char valid;                     /* !=0 if following fields are valid */
+       unsigned long last_updated;     /* In jiffies */
+       u8 in[SCH5636_NO_INS];
+       u8 temp_val[SCH5636_NO_TEMPS];
+       u8 temp_ctrl[SCH5636_NO_TEMPS];
+       u16 fan_val[SCH5636_NO_FANS];
+       u8 fan_ctrl[SCH5636_NO_FANS];
+};
+
+static struct sch5636_data *sch5636_update_device(struct device *dev)
+{
+       struct sch5636_data *data = dev_get_drvdata(dev);
+       struct sch5636_data *ret = data;
+       int i, val;
+
+       mutex_lock(&data->update_lock);
+
+       /* Cache the values for 1 second */
+       if (data->valid && !time_after(jiffies, data->last_updated + HZ))
+               goto abort;
+
+       for (i = 0; i < SCH5636_NO_INS; i++) {
+               val = sch56xx_read_virtual_reg(data->addr,
+                                              SCH5636_REG_IN_VAL[i]);
+               if (unlikely(val < 0)) {
+                       ret = ERR_PTR(val);
+                       goto abort;
+               }
+               data->in[i] = val;
+       }
+
+       for (i = 0; i < SCH5636_NO_TEMPS; i++) {
+               if (data->temp_ctrl[i] & SCH5636_TEMP_DEACTIVATED)
+                       continue;
+
+               val = sch56xx_read_virtual_reg(data->addr,
+                                              SCH5636_REG_TEMP_VAL[i]);
+               if (unlikely(val < 0)) {
+                       ret = ERR_PTR(val);
+                       goto abort;
+               }
+               data->temp_val[i] = val;
+
+               val = sch56xx_read_virtual_reg(data->addr,
+                                              SCH5636_REG_TEMP_CTRL(i));
+               if (unlikely(val < 0)) {
+                       ret = ERR_PTR(val);
+                       goto abort;
+               }
+               data->temp_ctrl[i] = val;
+               /* Alarms need to be explicitly write-cleared */
+               if (val & SCH5636_TEMP_ALARM) {
+                       sch56xx_write_virtual_reg(data->addr,
+                                               SCH5636_REG_TEMP_CTRL(i), val);
+               }
+       }
+
+       for (i = 0; i < SCH5636_NO_FANS; i++) {
+               if (data->fan_ctrl[i] & SCH5636_FAN_DEACTIVATED)
+                       continue;
+
+               val = sch56xx_read_virtual_reg16(data->addr,
+                                                SCH5636_REG_FAN_VAL[i]);
+               if (unlikely(val < 0)) {
+                       ret = ERR_PTR(val);
+                       goto abort;
+               }
+               data->fan_val[i] = val;
+
+               val = sch56xx_read_virtual_reg(data->addr,
+                                              SCH5636_REG_FAN_CTRL(i));
+               if (unlikely(val < 0)) {
+                       ret = ERR_PTR(val);
+                       goto abort;
+               }
+               data->fan_ctrl[i] = val;
+               /* Alarms need to be explicitly write-cleared */
+               if (val & SCH5636_FAN_ALARM) {
+                       sch56xx_write_virtual_reg(data->addr,
+                                               SCH5636_REG_FAN_CTRL(i), val);
+               }
+       }
+
+       data->last_updated = jiffies;
+       data->valid = 1;
+abort:
+       mutex_unlock(&data->update_lock);
+       return ret;
+}
+
+static int reg_to_rpm(u16 reg)
+{
+       if (reg == 0)
+               return -EIO;
+       if (reg == 0xffff)
+               return 0;
+
+       return 5400540 / reg;
+}
+
+static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
+       char *buf)
+{
+       return snprintf(buf, PAGE_SIZE, "%s\n", DEVNAME);
+}
+
+static ssize_t show_in_value(struct device *dev, struct device_attribute
+       *devattr, char *buf)
+{
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+       struct sch5636_data *data = sch5636_update_device(dev);
+       int val;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       val = DIV_ROUND_CLOSEST(
+               data->in[attr->index] * SCH5636_REG_IN_FACTORS[attr->index],
+               255);
+       return snprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t show_in_label(struct device *dev, struct device_attribute
+       *devattr, char *buf)
+{
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+
+       return snprintf(buf, PAGE_SIZE, "%s\n",
+                       SCH5636_IN_LABELS[attr->index]);
+}
+
+static ssize_t show_temp_value(struct device *dev, struct device_attribute
+       *devattr, char *buf)
+{
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+       struct sch5636_data *data = sch5636_update_device(dev);
+       int val;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       val = (data->temp_val[attr->index] - 64) * 1000;
+       return snprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t show_temp_fault(struct device *dev, struct device_attribute
+       *devattr, char *buf)
+{
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+       struct sch5636_data *data = sch5636_update_device(dev);
+       int val;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       val = (data->temp_ctrl[attr->index] & SCH5636_TEMP_WORKING) ? 0 : 1;
+       return snprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t show_temp_alarm(struct device *dev, struct device_attribute
+       *devattr, char *buf)
+{
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+       struct sch5636_data *data = sch5636_update_device(dev);
+       int val;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       val = (data->temp_ctrl[attr->index] & SCH5636_TEMP_ALARM) ? 1 : 0;
+       return snprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t show_fan_value(struct device *dev, struct device_attribute
+       *devattr, char *buf)
+{
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+       struct sch5636_data *data = sch5636_update_device(dev);
+       int val;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       val = reg_to_rpm(data->fan_val[attr->index]);
+       if (val < 0)
+               return val;
+
+       return snprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t show_fan_fault(struct device *dev, struct device_attribute
+       *devattr, char *buf)
+{
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+       struct sch5636_data *data = sch5636_update_device(dev);
+       int val;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       val = (data->fan_ctrl[attr->index] & SCH5636_FAN_NOT_PRESENT) ? 1 : 0;
+       return snprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t show_fan_alarm(struct device *dev, struct device_attribute
+       *devattr, char *buf)
+{
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+       struct sch5636_data *data = sch5636_update_device(dev);
+       int val;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       val = (data->fan_ctrl[attr->index] & SCH5636_FAN_ALARM) ? 1 : 0;
+       return snprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static struct sensor_device_attribute sch5636_attr[] = {
+       SENSOR_ATTR(name, 0444, show_name, NULL, 0),
+       SENSOR_ATTR(in0_input, 0444, show_in_value, NULL, 0),
+       SENSOR_ATTR(in0_label, 0444, show_in_label, NULL, 0),
+       SENSOR_ATTR(in1_input, 0444, show_in_value, NULL, 1),
+       SENSOR_ATTR(in1_label, 0444, show_in_label, NULL, 1),
+       SENSOR_ATTR(in2_input, 0444, show_in_value, NULL, 2),
+       SENSOR_ATTR(in2_label, 0444, show_in_label, NULL, 2),
+       SENSOR_ATTR(in3_input, 0444, show_in_value, NULL, 3),
+       SENSOR_ATTR(in3_label, 0444, show_in_label, NULL, 3),
+       SENSOR_ATTR(in4_input, 0444, show_in_value, NULL, 4),
+       SENSOR_ATTR(in4_label, 0444, show_in_label, NULL, 4),
+};
+
+static struct sensor_device_attribute sch5636_temp_attr[] = {
+       SENSOR_ATTR(temp1_input, 0444, show_temp_value, NULL, 0),
+       SENSOR_ATTR(temp1_fault, 0444, show_temp_fault, NULL, 0),
+       SENSOR_ATTR(temp1_alarm, 0444, show_temp_alarm, NULL, 0),
+       SENSOR_ATTR(temp2_input, 0444, show_temp_value, NULL, 1),
+       SENSOR_ATTR(temp2_fault, 0444, show_temp_fault, NULL, 1),
+       SENSOR_ATTR(temp2_alarm, 0444, show_temp_alarm, NULL, 1),
+       SENSOR_ATTR(temp3_input, 0444, show_temp_value, NULL, 2),
+       SENSOR_ATTR(temp3_fault, 0444, show_temp_fault, NULL, 2),
+       SENSOR_ATTR(temp3_alarm, 0444, show_temp_alarm, NULL, 2),
+       SENSOR_ATTR(temp4_input, 0444, show_temp_value, NULL, 3),
+       SENSOR_ATTR(temp4_fault, 0444, show_temp_fault, NULL, 3),
+       SENSOR_ATTR(temp4_alarm, 0444, show_temp_alarm, NULL, 3),
+       SENSOR_ATTR(temp5_input, 0444, show_temp_value, NULL, 4),
+       SENSOR_ATTR(temp5_fault, 0444, show_temp_fault, NULL, 4),
+       SENSOR_ATTR(temp5_alarm, 0444, show_temp_alarm, NULL, 4),
+       SENSOR_ATTR(temp6_input, 0444, show_temp_value, NULL, 5),
+       SENSOR_ATTR(temp6_fault, 0444, show_temp_fault, NULL, 5),
+       SENSOR_ATTR(temp6_alarm, 0444, show_temp_alarm, NULL, 5),
+       SENSOR_ATTR(temp7_input, 0444, show_temp_value, NULL, 6),
+       SENSOR_ATTR(temp7_fault, 0444, show_temp_fault, NULL, 6),
+       SENSOR_ATTR(temp7_alarm, 0444, show_temp_alarm, NULL, 6),
+       SENSOR_ATTR(temp8_input, 0444, show_temp_value, NULL, 7),
+       SENSOR_ATTR(temp8_fault, 0444, show_temp_fault, NULL, 7),
+       SENSOR_ATTR(temp8_alarm, 0444, show_temp_alarm, NULL, 7),
+       SENSOR_ATTR(temp9_input, 0444, show_temp_value, NULL, 8),
+       SENSOR_ATTR(temp9_fault, 0444, show_temp_fault, NULL, 8),
+       SENSOR_ATTR(temp9_alarm, 0444, show_temp_alarm, NULL, 8),
+       SENSOR_ATTR(temp10_input, 0444, show_temp_value, NULL, 9),
+       SENSOR_ATTR(temp10_fault, 0444, show_temp_fault, NULL, 9),
+       SENSOR_ATTR(temp10_alarm, 0444, show_temp_alarm, NULL, 9),
+       SENSOR_ATTR(temp11_input, 0444, show_temp_value, NULL, 10),
+       SENSOR_ATTR(temp11_fault, 0444, show_temp_fault, NULL, 10),
+       SENSOR_ATTR(temp11_alarm, 0444, show_temp_alarm, NULL, 10),
+       SENSOR_ATTR(temp12_input, 0444, show_temp_value, NULL, 11),
+       SENSOR_ATTR(temp12_fault, 0444, show_temp_fault, NULL, 11),
+       SENSOR_ATTR(temp12_alarm, 0444, show_temp_alarm, NULL, 11),
+       SENSOR_ATTR(temp13_input, 0444, show_temp_value, NULL, 12),
+       SENSOR_ATTR(temp13_fault, 0444, show_temp_fault, NULL, 12),
+       SENSOR_ATTR(temp13_alarm, 0444, show_temp_alarm, NULL, 12),
+       SENSOR_ATTR(temp14_input, 0444, show_temp_value, NULL, 13),
+       SENSOR_ATTR(temp14_fault, 0444, show_temp_fault, NULL, 13),
+       SENSOR_ATTR(temp14_alarm, 0444, show_temp_alarm, NULL, 13),
+       SENSOR_ATTR(temp15_input, 0444, show_temp_value, NULL, 14),
+       SENSOR_ATTR(temp15_fault, 0444, show_temp_fault, NULL, 14),
+       SENSOR_ATTR(temp15_alarm, 0444, show_temp_alarm, NULL, 14),
+       SENSOR_ATTR(temp16_input, 0444, show_temp_value, NULL, 15),
+       SENSOR_ATTR(temp16_fault, 0444, show_temp_fault, NULL, 15),
+       SENSOR_ATTR(temp16_alarm, 0444, show_temp_alarm, NULL, 15),
+};
+
+static struct sensor_device_attribute sch5636_fan_attr[] = {
+       SENSOR_ATTR(fan1_input, 0444, show_fan_value, NULL, 0),
+       SENSOR_ATTR(fan1_fault, 0444, show_fan_fault, NULL, 0),
+       SENSOR_ATTR(fan1_alarm, 0444, show_fan_alarm, NULL, 0),
+       SENSOR_ATTR(fan2_input, 0444, show_fan_value, NULL, 1),
+       SENSOR_ATTR(fan2_fault, 0444, show_fan_fault, NULL, 1),
+       SENSOR_ATTR(fan2_alarm, 0444, show_fan_alarm, NULL, 1),
+       SENSOR_ATTR(fan3_input, 0444, show_fan_value, NULL, 2),
+       SENSOR_ATTR(fan3_fault, 0444, show_fan_fault, NULL, 2),
+       SENSOR_ATTR(fan3_alarm, 0444, show_fan_alarm, NULL, 2),
+       SENSOR_ATTR(fan4_input, 0444, show_fan_value, NULL, 3),
+       SENSOR_ATTR(fan4_fault, 0444, show_fan_fault, NULL, 3),
+       SENSOR_ATTR(fan4_alarm, 0444, show_fan_alarm, NULL, 3),
+       SENSOR_ATTR(fan5_input, 0444, show_fan_value, NULL, 4),
+       SENSOR_ATTR(fan5_fault, 0444, show_fan_fault, NULL, 4),
+       SENSOR_ATTR(fan5_alarm, 0444, show_fan_alarm, NULL, 4),
+       SENSOR_ATTR(fan6_input, 0444, show_fan_value, NULL, 5),
+       SENSOR_ATTR(fan6_fault, 0444, show_fan_fault, NULL, 5),
+       SENSOR_ATTR(fan6_alarm, 0444, show_fan_alarm, NULL, 5),
+       SENSOR_ATTR(fan7_input, 0444, show_fan_value, NULL, 6),
+       SENSOR_ATTR(fan7_fault, 0444, show_fan_fault, NULL, 6),
+       SENSOR_ATTR(fan7_alarm, 0444, show_fan_alarm, NULL, 6),
+       SENSOR_ATTR(fan8_input, 0444, show_fan_value, NULL, 7),
+       SENSOR_ATTR(fan8_fault, 0444, show_fan_fault, NULL, 7),
+       SENSOR_ATTR(fan8_alarm, 0444, show_fan_alarm, NULL, 7),
+};
+
+static int sch5636_remove(struct platform_device *pdev)
+{
+       struct sch5636_data *data = platform_get_drvdata(pdev);
+       int i;
+
+       if (data->hwmon_dev)
+               hwmon_device_unregister(data->hwmon_dev);
+
+       for (i = 0; i < ARRAY_SIZE(sch5636_attr); i++)
+               device_remove_file(&pdev->dev, &sch5636_attr[i].dev_attr);
+
+       for (i = 0; i < SCH5636_NO_TEMPS * 3; i++)
+               device_remove_file(&pdev->dev,
+                                  &sch5636_temp_attr[i].dev_attr);
+
+       for (i = 0; i < SCH5636_NO_FANS * 3; i++)
+               device_remove_file(&pdev->dev,
+                                  &sch5636_fan_attr[i].dev_attr);
+
+       platform_set_drvdata(pdev, NULL);
+       kfree(data);
+
+       return 0;
+}
+
+static int __devinit sch5636_probe(struct platform_device *pdev)
+{
+       struct sch5636_data *data;
+       int i, err, val, revision[2];
+       char id[4];
+
+       data = kzalloc(sizeof(struct sch5636_data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       data->addr = platform_get_resource(pdev, IORESOURCE_IO, 0)->start;
+       mutex_init(&data->update_lock);
+       platform_set_drvdata(pdev, data);
+
+       for (i = 0; i < 3; i++) {
+               val = sch56xx_read_virtual_reg(data->addr,
+                                              SCH5636_REG_FUJITSU_ID + i);
+               if (val < 0) {
+                       pr_err("Could not read Fujitsu id byte at %#x\n",
+                               SCH5636_REG_FUJITSU_ID + i);
+                       err = val;
+                       goto error;
+               }
+               id[i] = val;
+       }
+       id[i] = '\0';
+
+       if (strcmp(id, "THS")) {
+               pr_err("Unknown Fujitsu id: %02x%02x%02x\n",
+                      id[0], id[1], id[2]);
+               err = -ENODEV;
+               goto error;
+       }
+
+       for (i = 0; i < 2; i++) {
+               val = sch56xx_read_virtual_reg(data->addr,
+                                              SCH5636_REG_FUJITSU_REV + i);
+               if (val < 0) {
+                       err = val;
+                       goto error;
+               }
+               revision[i] = val;
+       }
+       pr_info("Found %s chip at %#hx, revison: %d.%02d\n", DEVNAME,
+               data->addr, revision[0], revision[1]);
+
+       /* Read all temp + fan ctrl registers to determine which are active */
+       for (i = 0; i < SCH5636_NO_TEMPS; i++) {
+               val = sch56xx_read_virtual_reg(data->addr,
+                                              SCH5636_REG_TEMP_CTRL(i));
+               if (unlikely(val < 0)) {
+                       err = val;
+                       goto error;
+               }
+               data->temp_ctrl[i] = val;
+       }
+
+       for (i = 0; i < SCH5636_NO_FANS; i++) {
+               val = sch56xx_read_virtual_reg(data->addr,
+                                              SCH5636_REG_FAN_CTRL(i));
+               if (unlikely(val < 0)) {
+                       err = val;
+                       goto error;
+               }
+               data->fan_ctrl[i] = val;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(sch5636_attr); i++) {
+               err = device_create_file(&pdev->dev,
+                                        &sch5636_attr[i].dev_attr);
+               if (err)
+                       goto error;
+       }
+
+       for (i = 0; i < (SCH5636_NO_TEMPS * 3); i++) {
+               if (data->temp_ctrl[i/3] & SCH5636_TEMP_DEACTIVATED)
+                       continue;
+
+               err = device_create_file(&pdev->dev,
+                                       &sch5636_temp_attr[i].dev_attr);
+               if (err)
+                       goto error;
+       }
+
+       for (i = 0; i < (SCH5636_NO_FANS * 3); i++) {
+               if (data->fan_ctrl[i/3] & SCH5636_FAN_DEACTIVATED)
+                       continue;
+
+               err = device_create_file(&pdev->dev,
+                                       &sch5636_fan_attr[i].dev_attr);
+               if (err)
+                       goto error;
+       }
+
+       data->hwmon_dev = hwmon_device_register(&pdev->dev);
+       if (IS_ERR(data->hwmon_dev)) {
+               err = PTR_ERR(data->hwmon_dev);
+               data->hwmon_dev = NULL;
+               goto error;
+       }
+
+       return 0;
+
+error:
+       sch5636_remove(pdev);
+       return err;
+}
+
+static struct platform_driver sch5636_driver = {
+       .driver = {
+               .owner  = THIS_MODULE,
+               .name   = DRVNAME,
+       },
+       .probe          = sch5636_probe,
+       .remove         = sch5636_remove,
+};
+
+static int __init sch5636_init(void)
+{
+       return platform_driver_register(&sch5636_driver);
+}
+
+static void __exit sch5636_exit(void)
+{
+       platform_driver_unregister(&sch5636_driver);
+}
+
+MODULE_DESCRIPTION("SMSC SCH5636 Hardware Monitoring Driver");
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_LICENSE("GPL");
+
+module_init(sch5636_init);
+module_exit(sch5636_exit);
diff --git a/drivers/hwmon/sch56xx-common.c b/drivers/hwmon/sch56xx-common.c
new file mode 100644 (file)
index 0000000..fac32ee
--- /dev/null
@@ -0,0 +1,340 @@
+/***************************************************************************
+ *   Copyright (C) 2010-2011 Hans de Goede <hdegoede@redhat.com>           *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+ ***************************************************************************/
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include "sch56xx-common.h"
+
+#define SIO_SCH56XX_LD_EM      0x0C    /* Embedded uController Logical Dev */
+#define SIO_UNLOCK_KEY         0x55    /* Key to enable Super-I/O */
+#define SIO_LOCK_KEY           0xAA    /* Key to disable Super-I/O */
+
+#define SIO_REG_LDSEL          0x07    /* Logical device select */
+#define SIO_REG_DEVID          0x20    /* Device ID */
+#define SIO_REG_ENABLE         0x30    /* Logical device enable */
+#define SIO_REG_ADDR           0x66    /* Logical device address (2 bytes) */
+
+#define SIO_SCH5627_ID         0xC6    /* Chipset ID */
+#define SIO_SCH5636_ID         0xC7    /* Chipset ID */
+
+#define REGION_LENGTH          9
+
+#define SCH56XX_CMD_READ       0x02
+#define SCH56XX_CMD_WRITE      0x03
+
+static struct platform_device *sch56xx_pdev;
+
+/* Super I/O functions */
+static inline int superio_inb(int base, int reg)
+{
+       outb(reg, base);
+       return inb(base + 1);
+}
+
+static inline int superio_enter(int base)
+{
+       /* Don't step on other drivers' I/O space by accident */
+       if (!request_muxed_region(base, 2, "sch56xx")) {
+               pr_err("I/O address 0x%04x already in use\n", base);
+               return -EBUSY;
+       }
+
+       outb(SIO_UNLOCK_KEY, base);
+
+       return 0;
+}
+
+static inline void superio_select(int base, int ld)
+{
+       outb(SIO_REG_LDSEL, base);
+       outb(ld, base + 1);
+}
+
+static inline void superio_exit(int base)
+{
+       outb(SIO_LOCK_KEY, base);
+       release_region(base, 2);
+}
+
+static int sch56xx_send_cmd(u16 addr, u8 cmd, u16 reg, u8 v)
+{
+       u8 val;
+       int i;
+       /*
+        * According to SMSC for the commands we use the maximum time for
+        * the EM to respond is 15 ms, but testing shows in practice it
+        * responds within 15-32 reads, so we first busy poll, and if
+        * that fails sleep a bit and try again until we are way past
+        * the 15 ms maximum response time.
+        */
+       const int max_busy_polls = 64;
+       const int max_lazy_polls = 32;
+
+       /* (Optional) Write-Clear the EC to Host Mailbox Register */
+       val = inb(addr + 1);
+       outb(val, addr + 1);
+
+       /* Set Mailbox Address Pointer to first location in Region 1 */
+       outb(0x00, addr + 2);
+       outb(0x80, addr + 3);
+
+       /* Write Request Packet Header */
+       outb(cmd, addr + 4); /* VREG Access Type read:0x02 write:0x03 */
+       outb(0x01, addr + 5); /* # of Entries: 1 Byte (8-bit) */
+       outb(0x04, addr + 2); /* Mailbox AP to first data entry loc. */
+
+       /* Write Value field */
+       if (cmd == SCH56XX_CMD_WRITE)
+               outb(v, addr + 4);
+
+       /* Write Address field */
+       outb(reg & 0xff, addr + 6);
+       outb(reg >> 8, addr + 7);
+
+       /* Execute the Random Access Command */
+       outb(0x01, addr); /* Write 01h to the Host-to-EC register */
+
+       /* EM Interface Polling "Algorithm" */
+       for (i = 0; i < max_busy_polls + max_lazy_polls; i++) {
+               if (i >= max_busy_polls)
+                       msleep(1);
+               /* Read Interrupt source Register */
+               val = inb(addr + 8);
+               /* Write Clear the interrupt source bits */
+               if (val)
+                       outb(val, addr + 8);
+               /* Command Completed ? */
+               if (val & 0x01)
+                       break;
+       }
+       if (i == max_busy_polls + max_lazy_polls) {
+               pr_err("Max retries exceeded reading virtual "
+                      "register 0x%04hx (%d)\n", reg, 1);
+               return -EIO;
+       }
+
+       /*
+        * According to SMSC we may need to retry this, but sofar I've always
+        * seen this succeed in 1 try.
+        */
+       for (i = 0; i < max_busy_polls; i++) {
+               /* Read EC-to-Host Register */
+               val = inb(addr + 1);
+               /* Command Completed ? */
+               if (val == 0x01)
+                       break;
+
+               if (i == 0)
+                       pr_warn("EC reports: 0x%02x reading virtual register "
+                               "0x%04hx\n", (unsigned int)val, reg);
+       }
+       if (i == max_busy_polls) {
+               pr_err("Max retries exceeded reading virtual "
+                      "register 0x%04hx (%d)\n", reg, 2);
+               return -EIO;
+       }
+
+       /*
+        * According to the SMSC app note we should now do:
+        *
+        * Set Mailbox Address Pointer to first location in Region 1 *
+        * outb(0x00, addr + 2);
+        * outb(0x80, addr + 3);
+        *
+        * But if we do that things don't work, so let's not.
+        */
+
+       /* Read Value field */
+       if (cmd == SCH56XX_CMD_READ)
+               return inb(addr + 4);
+
+       return 0;
+}
+
+int sch56xx_read_virtual_reg(u16 addr, u16 reg)
+{
+       return sch56xx_send_cmd(addr, SCH56XX_CMD_READ, reg, 0);
+}
+EXPORT_SYMBOL(sch56xx_read_virtual_reg);
+
+int sch56xx_write_virtual_reg(u16 addr, u16 reg, u8 val)
+{
+       return sch56xx_send_cmd(addr, SCH56XX_CMD_WRITE, reg, val);
+}
+EXPORT_SYMBOL(sch56xx_write_virtual_reg);
+
+int sch56xx_read_virtual_reg16(u16 addr, u16 reg)
+{
+       int lsb, msb;
+
+       /* Read LSB first, this will cause the matching MSB to be latched */
+       lsb = sch56xx_read_virtual_reg(addr, reg);
+       if (lsb < 0)
+               return lsb;
+
+       msb = sch56xx_read_virtual_reg(addr, reg + 1);
+       if (msb < 0)
+               return msb;
+
+       return lsb | (msb << 8);
+}
+EXPORT_SYMBOL(sch56xx_read_virtual_reg16);
+
+int sch56xx_read_virtual_reg12(u16 addr, u16 msb_reg, u16 lsn_reg,
+                              int high_nibble)
+{
+       int msb, lsn;
+
+       /* Read MSB first, this will cause the matching LSN to be latched */
+       msb = sch56xx_read_virtual_reg(addr, msb_reg);
+       if (msb < 0)
+               return msb;
+
+       lsn = sch56xx_read_virtual_reg(addr, lsn_reg);
+       if (lsn < 0)
+               return lsn;
+
+       if (high_nibble)
+               return (msb << 4) | (lsn >> 4);
+       else
+               return (msb << 4) | (lsn & 0x0f);
+}
+EXPORT_SYMBOL(sch56xx_read_virtual_reg12);
+
+static int __init sch56xx_find(int sioaddr, unsigned short *address,
+                              const char **name)
+{
+       u8 devid;
+       int err;
+
+       err = superio_enter(sioaddr);
+       if (err)
+               return err;
+
+       devid = superio_inb(sioaddr, SIO_REG_DEVID);
+       switch (devid) {
+       case SIO_SCH5627_ID:
+               *name = "sch5627";
+               break;
+       case SIO_SCH5636_ID:
+               *name = "sch5636";
+               break;
+       default:
+               pr_debug("Unsupported device id: 0x%02x\n",
+                        (unsigned int)devid);
+               err = -ENODEV;
+               goto exit;
+       }
+
+       superio_select(sioaddr, SIO_SCH56XX_LD_EM);
+
+       if (!(superio_inb(sioaddr, SIO_REG_ENABLE) & 0x01)) {
+               pr_warn("Device not activated\n");
+               err = -ENODEV;
+               goto exit;
+       }
+
+       /*
+        * Warning the order of the low / high byte is the other way around
+        * as on most other superio devices!!
+        */
+       *address = superio_inb(sioaddr, SIO_REG_ADDR) |
+                  superio_inb(sioaddr, SIO_REG_ADDR + 1) << 8;
+       if (*address == 0) {
+               pr_warn("Base address not set\n");
+               err = -ENODEV;
+               goto exit;
+       }
+
+exit:
+       superio_exit(sioaddr);
+       return err;
+}
+
+static int __init sch56xx_device_add(unsigned short address, const char *name)
+{
+       struct resource res = {
+               .start  = address,
+               .end    = address + REGION_LENGTH - 1,
+               .flags  = IORESOURCE_IO,
+       };
+       int err;
+
+       sch56xx_pdev = platform_device_alloc(name, address);
+       if (!sch56xx_pdev)
+               return -ENOMEM;
+
+       res.name = sch56xx_pdev->name;
+       err = acpi_check_resource_conflict(&res);
+       if (err)
+               goto exit_device_put;
+
+       err = platform_device_add_resources(sch56xx_pdev, &res, 1);
+       if (err) {
+               pr_err("Device resource addition failed\n");
+               goto exit_device_put;
+       }
+
+       err = platform_device_add(sch56xx_pdev);
+       if (err) {
+               pr_err("Device addition failed\n");
+               goto exit_device_put;
+       }
+
+       return 0;
+
+exit_device_put:
+       platform_device_put(sch56xx_pdev);
+
+       return err;
+}
+
+static int __init sch56xx_init(void)
+{
+       int err;
+       unsigned short address;
+       const char *name;
+
+       err = sch56xx_find(0x4e, &address, &name);
+       if (err)
+               err = sch56xx_find(0x2e, &address, &name);
+       if (err)
+               return err;
+
+       return sch56xx_device_add(address, name);
+}
+
+static void __exit sch56xx_exit(void)
+{
+       platform_device_unregister(sch56xx_pdev);
+}
+
+MODULE_DESCRIPTION("SMSC SCH56xx Hardware Monitoring Common Code");
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_LICENSE("GPL");
+
+module_init(sch56xx_init);
+module_exit(sch56xx_exit);
diff --git a/drivers/hwmon/sch56xx-common.h b/drivers/hwmon/sch56xx-common.h
new file mode 100644 (file)
index 0000000..d5eaf3b
--- /dev/null
@@ -0,0 +1,24 @@
+/***************************************************************************
+ *   Copyright (C) 2010-2011 Hans de Goede <hdegoede@redhat.com>           *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+ ***************************************************************************/
+
+int sch56xx_read_virtual_reg(u16 addr, u16 reg);
+int sch56xx_write_virtual_reg(u16 addr, u16 reg, u8 val);
+int sch56xx_read_virtual_reg16(u16 addr, u16 reg);
+int sch56xx_read_virtual_reg12(u16 addr, u16 msb_reg, u16 lsn_reg,
+                              int high_nibble);
index cf4330b..7d231cf 100644 (file)
@@ -671,7 +671,7 @@ static ssize_t sht15_show_status(struct device *dev,
  * @buf:       sysfs buffer to read the new heater state from.
  * @count:     length of the data.
  *
- * Will be called on read access to heater_enable sysfs attribute.
+ * Will be called on write access to heater_enable sysfs attribute.
  * Returns number of bytes actually decoded, negative errno on error.
  */
 static ssize_t sht15_store_heater(struct device *dev,
index 0d18de4..8eac67d 100644 (file)
@@ -27,6 +27,7 @@
 #include <linux/init.h>
 #include <linux/slab.h>
 #include <linux/hwmon.h>
+#include <linux/hwmon-vid.h>
 #include <linux/sysfs.h>
 #include <linux/hwmon-sysfs.h>
 #include <linux/err.h>
@@ -48,8 +49,10 @@ enum { SHOW_TEMP, SHOW_LABEL, SHOW_NAME };
 struct via_cputemp_data {
        struct device *hwmon_dev;
        const char *name;
+       u8 vrm;
        u32 id;
-       u32 msr;
+       u32 msr_temp;
+       u32 msr_vid;
 };
 
 /*
@@ -77,13 +80,27 @@ static ssize_t show_temp(struct device *dev,
        u32 eax, edx;
        int err;
 
-       err = rdmsr_safe_on_cpu(data->id, data->msr, &eax, &edx);
+       err = rdmsr_safe_on_cpu(data->id, data->msr_temp, &eax, &edx);
        if (err)
                return -EAGAIN;
 
        return sprintf(buf, "%lu\n", ((unsigned long)eax & 0xffffff) * 1000);
 }
 
+static ssize_t show_cpu_vid(struct device *dev,
+                           struct device_attribute *devattr, char *buf)
+{
+       struct via_cputemp_data *data = dev_get_drvdata(dev);
+       u32 eax, edx;
+       int err;
+
+       err = rdmsr_safe_on_cpu(data->id, data->msr_vid, &eax, &edx);
+       if (err)
+               return -EAGAIN;
+
+       return sprintf(buf, "%d\n", vid_from_reg(~edx & 0x7f, data->vrm));
+}
+
 static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL,
                          SHOW_TEMP);
 static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_name, NULL, SHOW_LABEL);
@@ -100,6 +117,9 @@ static const struct attribute_group via_cputemp_group = {
        .attrs = via_cputemp_attributes,
 };
 
+/* Optional attributes */
+static DEVICE_ATTR(cpu0_vid, S_IRUGO, show_cpu_vid, NULL);
+
 static int __devinit via_cputemp_probe(struct platform_device *pdev)
 {
        struct via_cputemp_data *data;
@@ -122,11 +142,12 @@ static int __devinit via_cputemp_probe(struct platform_device *pdev)
                /* C7 A */
        case 0xD:
                /* C7 D */
-               data->msr = 0x1169;
+               data->msr_temp = 0x1169;
+               data->msr_vid = 0x198;
                break;
        case 0xF:
                /* Nano */
-               data->msr = 0x1423;
+               data->msr_temp = 0x1423;
                break;
        default:
                err = -ENODEV;
@@ -134,7 +155,7 @@ static int __devinit via_cputemp_probe(struct platform_device *pdev)
        }
 
        /* test if we can access the TEMPERATURE MSR */
-       err = rdmsr_safe_on_cpu(data->id, data->msr, &eax, &edx);
+       err = rdmsr_safe_on_cpu(data->id, data->msr_temp, &eax, &edx);
        if (err) {
                dev_err(&pdev->dev,
                        "Unable to access TEMPERATURE MSR, giving up\n");
@@ -147,6 +168,15 @@ static int __devinit via_cputemp_probe(struct platform_device *pdev)
        if (err)
                goto exit_free;
 
+       if (data->msr_vid)
+               data->vrm = vid_which_vrm();
+
+       if (data->vrm) {
+               err = device_create_file(&pdev->dev, &dev_attr_cpu0_vid);
+               if (err)
+                       goto exit_remove;
+       }
+
        data->hwmon_dev = hwmon_device_register(&pdev->dev);
        if (IS_ERR(data->hwmon_dev)) {
                err = PTR_ERR(data->hwmon_dev);
@@ -158,6 +188,8 @@ static int __devinit via_cputemp_probe(struct platform_device *pdev)
        return 0;
 
 exit_remove:
+       if (data->vrm)
+               device_remove_file(&pdev->dev, &dev_attr_cpu0_vid);
        sysfs_remove_group(&pdev->dev.kobj, &via_cputemp_group);
 exit_free:
        platform_set_drvdata(pdev, NULL);
@@ -171,6 +203,8 @@ static int __devexit via_cputemp_remove(struct platform_device *pdev)
        struct via_cputemp_data *data = platform_get_drvdata(pdev);
 
        hwmon_device_unregister(data->hwmon_dev);
+       if (data->vrm)
+               device_remove_file(&pdev->dev, &dev_attr_cpu0_vid);
        sysfs_remove_group(&pdev->dev.kobj, &via_cputemp_group);
        platform_set_drvdata(pdev, NULL);
        kfree(data);