Merge branch 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jdelv...
authorLinus Torvalds <torvalds@linux-foundation.org>
Sat, 14 Aug 2010 21:57:58 +0000 (14:57 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sat, 14 Aug 2010 21:57:58 +0000 (14:57 -0700)
* 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jdelvare/staging: (22 commits)
  hwmon: (via-cputemp) Remove bogus "SHOW" global variable
  hwmon: jc42 depends on I2C
  hwmon: (pc87427) Add a maintainer
  hwmon: (pc87427) Move sysfs file removal to a separate function
  hwmon: (pc87427) Add temperature monitoring support
  hwmon: (pc87427) Add support for the second logical device
  hwmon: (pc87427) Add support for manual fan speed control
  hwmon: (pc87427) Minor style cleanups
  hwmon: (pc87427) Handle disabled fan inputs properly
  hwmon: (w83627ehf) Add support for W83667HG-B
  hwmon: (w83627ehf) Driver cleanup
  hwmon: Add driver for SMSC EMC2103 temperature monitor and fan controller
  hwmon: Remove in[0-*]_fault from sysfs-interface
  hwmon: Add 4 current alarm/beep attributes to sysfs-interface
  hwmon: Add 3 critical limit attributes to sysfs-interface
  hwmon: (asc7621) Clean up and improve detect function
  hwmon: (it87) Export labels for internal sensors
  hwmon: (lm75) Add suspend/resume feature
  hwmon: (emc1403) Add power support
  hwmon: (ltc4245) Expose all GPIO pins as analog voltages
  ...

21 files changed:
Documentation/hwmon/emc2103 [new file with mode: 0644]
Documentation/hwmon/ltc4245
Documentation/hwmon/pc87427
Documentation/hwmon/sysfs-interface
Documentation/hwmon/w83627ehf
MAINTAINERS
drivers/hwmon/Kconfig
drivers/hwmon/Makefile
drivers/hwmon/asc7621.c
drivers/hwmon/emc1403.c
drivers/hwmon/emc2103.c [new file with mode: 0644]
drivers/hwmon/it87.c
drivers/hwmon/k8temp.c
drivers/hwmon/lm75.c
drivers/hwmon/lm75.h
drivers/hwmon/ltc4245.c
drivers/hwmon/pc87360.c
drivers/hwmon/pc87427.c
drivers/hwmon/via-cputemp.c
drivers/hwmon/w83627ehf.c
include/linux/i2c/ltc4245.h [new file with mode: 0644]

diff --git a/Documentation/hwmon/emc2103 b/Documentation/hwmon/emc2103
new file mode 100644 (file)
index 0000000..a12b2c1
--- /dev/null
@@ -0,0 +1,33 @@
+Kernel driver emc2103
+======================
+
+Supported chips:
+  * SMSC EMC2103
+    Addresses scanned: I2C 0x2e
+    Prefix: 'emc2103'
+    Datasheet: Not public
+
+Authors:
+        Steve Glendinning <steve.glendinning@smsc.com>
+
+Description
+-----------
+
+The Standard Microsystems Corporation (SMSC) EMC2103 chips
+contain up to 4 temperature sensors and a single fan controller.
+
+Fan rotation speeds are reported in RPM (rotations per minute). An alarm is
+triggered if the rotation speed has dropped below a programmable limit. Fan
+readings can be divided by a programmable divider (1, 2, 4 or 8) to give
+the readings more range or accuracy. Not all RPM values can accurately be
+represented, so some rounding is done. With a divider of 1, the lowest
+representable value is 480 RPM.
+
+This driver supports RPM based control, to use this a fan target
+should be written to fan1_target and pwm1_enable should be set to 3.
+
+The 2103-2 and 2103-4 variants have a third temperature sensor, which can
+be connected to two anti-parallel diodes.  These values can be read
+as temp3 and temp4.  If only one diode is attached to this channel, temp4
+will show as "fault".  The module parameter "apd=0" can be used to suppress
+this 4th channel when anti-parallel diodes are not fitted.
index 86b5880d8502b123e508c94330e61ea255db1292..b478b08649658693697d3c6f8ee582c5524a8073 100644 (file)
@@ -72,9 +72,31 @@ in6_min_alarm                5v  output undervoltage alarm
 in7_min_alarm          3v  output undervoltage alarm
 in8_min_alarm          Vee (-12v) output undervoltage alarm
 
-in9_input              GPIO voltage data
+in9_input              GPIO voltage data (see note 1)
+in10_input             GPIO voltage data (see note 1)
+in11_input             GPIO voltage data (see note 1)
 
 power1_input           12v power usage (mW)
 power2_input           5v  power usage (mW)
 power3_input           3v  power usage (mW)
 power4_input           Vee (-12v) power usage (mW)
+
+
+Note 1
+------
+
+If you have NOT configured the driver to sample all GPIO pins as analog
+voltages, then the in10_input and in11_input sysfs attributes will not be
+created. The driver will sample the GPIO pin that is currently connected to the
+ADC as an analog voltage, and report the value in in9_input.
+
+If you have configured the driver to sample all GPIO pins as analog voltages,
+then they will be sampled in round-robin fashion. If userspace reads too
+slowly, -EAGAIN will be returned when you read the sysfs attribute containing
+the sensor reading.
+
+The LTC4245 chip can be configured to sample all GPIO pins with two methods:
+1) platform data -- see include/linux/i2c/ltc4245.h
+2) OF device tree -- add the "ltc4245,use-extra-gpios" property to each chip
+
+The default mode of operation is to sample a single GPIO pin.
index db5cc1227a83949a50146cbe01dd887c8610bcea..8fdd08c9e48b5ceac48b100ee4cedbdf78fcc242 100644 (file)
@@ -18,10 +18,11 @@ Description
 
 The National Semiconductor Super I/O chip includes complete hardware
 monitoring capabilities. It can monitor up to 18 voltages, 8 fans and
-6 temperature sensors. Only the fans are supported at the moment.
+6 temperature sensors. Only the fans and temperatures are supported at
+the moment, voltages aren't.
 
-This chip also has fan controlling features, which are not yet supported
-by this driver either.
+This chip also has fan controlling features (up to 4 PWM outputs),
+which are partly supported by this driver.
 
 The driver assumes that no more than one chip is present, which seems
 reasonable.
@@ -36,3 +37,23 @@ signal. Speeds down to 83 RPM can be measured.
 An alarm is triggered if the rotation speed drops below a programmable
 limit. Another alarm is triggered if the speed is too low to be measured
 (including stalled or missing fan).
+
+
+Fan Speed Control
+-----------------
+
+Fan speed can be controlled by PWM outputs. There are 4 possible modes:
+always off, always on, manual and automatic. The latter isn't supported
+by the driver: you can only return to that mode if it was the original
+setting, and the configuration interface is missing.
+
+
+Temperature Monitoring
+----------------------
+
+The PC87427 relies on external sensors (following the SensorPath
+standard), so the resolution and range depend on the type of sensor
+connected. The integer part can be 8-bit or 9-bit, and can be signed or
+not. I couldn't find a way to figure out the external sensor data
+temperature format, so user-space adjustment (typically by a factor 2)
+may be required.
index d4e2917c6f18b69d9e9a36e39d228ab36bd3d87b..ff45d1f837c89ab6706726ae525083e22d05af16 100644 (file)
@@ -107,10 +107,24 @@ in[0-*]_min       Voltage min value.
                Unit: millivolt
                RW
                
+in[0-*]_lcrit  Voltage critical min value.
+               Unit: millivolt
+               RW
+               If voltage drops to or below this limit, the system may
+               take drastic action such as power down or reset. At the very
+               least, it should report a fault.
+
 in[0-*]_max    Voltage max value.
                Unit: millivolt
                RW
                
+in[0-*]_crit   Voltage critical max value.
+               Unit: millivolt
+               RW
+               If voltage reaches or exceeds this limit, the system may
+               take drastic action such as power down or reset. At the very
+               least, it should report a fault.
+
 in[0-*]_input  Voltage input value.
                Unit: millivolt
                RO
@@ -284,7 +298,7 @@ temp[1-*]_input Temperature input value.
                Unit: millidegree Celsius
                RO
 
-temp[1-*]_crit Temperature critical value, typically greater than
+temp[1-*]_crit Temperature critical max value, typically greater than
                corresponding temp_max values.
                Unit: millidegree Celsius
                RW
@@ -296,6 +310,11 @@ temp[1-*]_crit_hyst
                from the critical value.
                RW
 
+temp[1-*]_lcrit        Temperature critical min value, typically lower than
+               corresponding temp_min values.
+               Unit: millidegree Celsius
+               RW
+
 temp[1-*]_offset
                Temperature offset which is added to the temperature reading
                by the chip.
@@ -344,9 +363,6 @@ Also see the Alarms section for status flags associated with temperatures.
 * Currents *
 ************
 
-Note that no known chip provides current measurements as of writing,
-so this part is theoretical, so to say.
-
 curr[1-*]_max  Current max value
                Unit: milliampere
                RW
@@ -471,6 +487,7 @@ limit-related alarms, not both. The driver should just reflect the hardware
 implementation.
 
 in[0-*]_alarm
+curr[1-*]_alarm
 fan[1-*]_alarm
 temp[1-*]_alarm
                Channel alarm
@@ -482,6 +499,8 @@ OR
 
 in[0-*]_min_alarm
 in[0-*]_max_alarm
+curr[1-*]_min_alarm
+curr[1-*]_max_alarm
 fan[1-*]_min_alarm
 fan[1-*]_max_alarm
 temp[1-*]_min_alarm
@@ -497,7 +516,6 @@ to notify open diodes, unconnected fans etc. where the hardware
 supports it. When this boolean has value 1, the measurement for that
 channel should not be trusted.
 
-in[0-*]_fault
 fan[1-*]_fault
 temp[1-*]_fault
                Input fault condition
@@ -513,6 +531,7 @@ beep_enable Master beep enable
                RW
 
 in[0-*]_beep
+curr[1-*]_beep
 fan[1-*]_beep
 temp[1-*]_beep
                Channel beep
index b7e42ec4b26ba4465691710ba4071ebebb4f61b6..13d556112fc0645f0dceb7a8bb75ef691d40c8b1 100644 (file)
@@ -20,6 +20,10 @@ Supported chips:
     Prefix: 'w83667hg'
     Addresses scanned: ISA address retrieved from Super I/O registers
     Datasheet: not available
+  * Winbond W83667HG-B
+    Prefix: 'w83667hg'
+    Addresses scanned: ISA address retrieved from Super I/O registers
+    Datasheet: Available from Nuvoton upon request
 
 Authors:
         Jean Delvare <khali@linux-fr.org>
@@ -32,8 +36,8 @@ Description
 -----------
 
 This driver implements support for the Winbond W83627EHF, W83627EHG,
-W83627DHG, W83627DHG-P and W83667HG super I/O chips. We will refer to them
-collectively as Winbond chips.
+W83627DHG, W83627DHG-P, W83667HG and W83667HG-B super I/O chips.
+We will refer to them collectively as Winbond chips.
 
 The chips implement three temperature sensors, five fan rotation
 speed sensors, ten analog voltage sensors (only nine for the 627DHG), one
@@ -68,14 +72,15 @@ follows:
 temp1 -> pwm1
 temp2 -> pwm2
 temp3 -> pwm3
-prog  -> pwm4 (not on 667HG; the programmable setting is not supported by
-              the driver)
+prog  -> pwm4 (not on 667HG and 667HG-B; the programmable setting is not
+              supported by the driver)
 
 /sys files
 ----------
 
 name - this is a standard hwmon device entry. For the W83627EHF and W83627EHG,
-       it is set to "w83627ehf" and for the W83627DHG it is set to "w83627dhg"
+       it is set to "w83627ehf", for the W83627DHG it is set to "w83627dhg",
+       and for the W83667HG it is set to "w83667hg".
 
 pwm[1-4] - this file stores PWM duty cycle or DC value (fan speed) in range:
           0 (stop) to 255 (full)
index 39d8c5ce99139dd23c7435263ce68d1ebcf2095b..99b6f8203a50e0680db33c00a50b8814eda88277 100644 (file)
@@ -4402,6 +4402,13 @@ M:       Jim Cromie <jim.cromie@gmail.com>
 S:     Maintained
 F:     drivers/char/pc8736x_gpio.c
 
+PC87427 HARDWARE MONITORING DRIVER
+M:     Jean Delvare <khali@linux-fr.org>
+L:     lm-sensors@lm-sensors.org
+S:     Maintained
+F:     Documentation/hwmon/pc87427
+F:     drivers/hwmon/pc87427.c
+
 PCA9532 LED DRIVER
 M:     Riku Voipio <riku.voipio@iki.fi>
 S:     Maintained
@@ -5279,6 +5286,13 @@ S:       Maintained
 F:     Documentation/hwmon/smm665
 F:     drivers/hwmon/smm665.c
 
+SMSC EMC2103 HARDWARE MONITOR DRIVER
+M:     Steve Glendinning <steve.glendinning@smsc.com>
+L:     lm-sensors@lm-sensors.org
+S:     Supported
+F:     Documentation/hwmon/emc2103
+F:     drivers/hwmon/emc2103.c
+
 SMSC47B397 HARDWARE MONITOR DRIVER
 M:     "Mark M. Hoffman" <mhoffman@lightlink.com>
 L:     lm-sensors@lm-sensors.org
index f3adf18bfa05e6ace43c55fbd74ad8825fc58fc5..0fba829431258c5122b56f794a662096541d41a5 100644 (file)
@@ -465,6 +465,7 @@ config SENSORS_JZ4740
 
 config SENSORS_JC42
        tristate "JEDEC JC42.4 compliant temperature sensors"
+       depends on I2C
        help
          If you say yes here you get support for Jedec JC42.4 compliant
          temperature sensors. Support will include, but not be limited to,
@@ -711,7 +712,8 @@ config SENSORS_PC87427
          functions of the National Semiconductor PC87427 Super-I/O chip.
          The chip has two distinct logical devices, one for fan speed
          monitoring and control, and one for voltage and temperature
-         monitoring. Only fan speed monitoring is supported right now.
+         monitoring. Fan speed monitoring and control are supported, as
+         well as temperature monitoring. Voltages aren't supported yet.
 
          This driver can also be built as a module.  If so, the module
          will be called pc87427.
@@ -804,6 +806,16 @@ config SENSORS_EMC1403
          Threshold values can be configured using sysfs.
          Data from the different diodes are accessible via sysfs.
 
+config SENSORS_EMC2103
+       tristate "SMSC EMC2103"
+       depends on I2C
+       help
+         If you say yes here you get support for the temperature
+         and fan sensors of the SMSC EMC2103 chips.
+
+         This driver can also be built as a module.  If so, the module
+         will be called emc2103.
+
 config SENSORS_SMSC47M1
        tristate "SMSC LPC47M10x and compatibles"
        help
index 13d913e34dbf1a55b740f50386ee7a855daa5177..e3c2484f6c5f4adb009712f3ae0bd5be032db16f 100644 (file)
@@ -43,6 +43,7 @@ obj-$(CONFIG_SENSORS_PKGTEMP) += pkgtemp.o
 obj-$(CONFIG_SENSORS_DME1737)  += dme1737.o
 obj-$(CONFIG_SENSORS_DS1621)   += ds1621.o
 obj-$(CONFIG_SENSORS_EMC1403)  += emc1403.o
+obj-$(CONFIG_SENSORS_EMC2103)  += emc2103.o
 obj-$(CONFIG_SENSORS_F71805F)  += f71805f.o
 obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o
 obj-$(CONFIG_SENSORS_F75375S)  += f75375s.o
index 3b973f30b1f6495a89029b4002816ee8ff299353..89b4f3babe87319e77e4982abde8756aa2188682 100644 (file)
@@ -1150,9 +1150,6 @@ static int asc7621_detect(struct i2c_client *client,
 {
        struct i2c_adapter *adapter = client->adapter;
        int company, verstep, chip_index;
-       struct device *dev;
-
-       dev = &client->dev;
 
        if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
                return -ENODEV;
@@ -1169,13 +1166,11 @@ static int asc7621_detect(struct i2c_client *client,
 
                if (company == asc7621_chips[chip_index].company_id &&
                    verstep == asc7621_chips[chip_index].verstep_id) {
-                       strlcpy(client->name, asc7621_chips[chip_index].name,
-                               I2C_NAME_SIZE);
                        strlcpy(info->type, asc7621_chips[chip_index].name,
                                I2C_NAME_SIZE);
 
-                       dev_info(&adapter->dev, "Matched %s\n",
-                                asc7621_chips[chip_index].name);
+                       dev_info(&adapter->dev, "Matched %s at 0x%02x\n",
+                                asc7621_chips[chip_index].name, client->addr);
                        return 0;
                }
        }
index 0e4b5642638dd5bba6ca52720aa69e8fb8153f73..5b58b20dead1fa92b79af5c05036d45de8bf88ca 100644 (file)
@@ -89,6 +89,35 @@ static ssize_t store_temp(struct device *dev,
        return count;
 }
 
+static ssize_t store_bit(struct device *dev,
+               struct device_attribute *attr, const char *buf, size_t count)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct thermal_data *data = i2c_get_clientdata(client);
+       struct sensor_device_attribute_2 *sda = to_sensor_dev_attr_2(attr);
+       unsigned long val;
+       int retval;
+
+       if (strict_strtoul(buf, 10, &val))
+               return -EINVAL;
+
+       mutex_lock(&data->mutex);
+       retval = i2c_smbus_read_byte_data(client, sda->nr);
+       if (retval < 0)
+               goto fail;
+
+       retval &= ~sda->index;
+       if (val)
+               retval |= sda->index;
+
+       retval = i2c_smbus_write_byte_data(client, sda->index, retval);
+       if (retval == 0)
+               retval = count;
+fail:
+       mutex_unlock(&data->mutex);
+       return retval;
+}
+
 static ssize_t show_hyst(struct device *dev,
                        struct device_attribute *attr, char *buf)
 {
@@ -200,6 +229,9 @@ static SENSOR_DEVICE_ATTR_2(temp3_crit_alarm, S_IRUGO,
 static SENSOR_DEVICE_ATTR(temp3_crit_hyst, S_IRUGO | S_IWUSR,
        show_hyst, store_hyst, 0x1A);
 
+static SENSOR_DEVICE_ATTR_2(power_state, S_IRUGO | S_IWUSR,
+       show_bit, store_bit, 0x03, 0x40);
+
 static struct attribute *mid_att_thermal[] = {
        &sensor_dev_attr_temp1_min.dev_attr.attr,
        &sensor_dev_attr_temp1_max.dev_attr.attr,
@@ -225,6 +257,7 @@ static struct attribute *mid_att_thermal[] = {
        &sensor_dev_attr_temp3_max_alarm.dev_attr.attr,
        &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr,
        &sensor_dev_attr_temp3_crit_hyst.dev_attr.attr,
+       &sensor_dev_attr_power_state.dev_attr.attr,
        NULL
 };
 
diff --git a/drivers/hwmon/emc2103.c b/drivers/hwmon/emc2103.c
new file mode 100644 (file)
index 0000000..af914ad
--- /dev/null
@@ -0,0 +1,740 @@
+/*
+    emc2103.c - Support for SMSC EMC2103
+    Copyright (c) 2010 SMSC
+
+    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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+
+/* Addresses scanned */
+static const unsigned short normal_i2c[] = { 0x2E, I2C_CLIENT_END };
+
+static const u8 REG_TEMP[4] = { 0x00, 0x02, 0x04, 0x06 };
+static const u8 REG_TEMP_MIN[4] = { 0x3c, 0x38, 0x39, 0x3a };
+static const u8 REG_TEMP_MAX[4] = { 0x34, 0x30, 0x31, 0x32 };
+
+#define REG_CONF1              0x20
+#define REG_TEMP_MAX_ALARM     0x24
+#define REG_TEMP_MIN_ALARM     0x25
+#define REG_FAN_CONF1          0x42
+#define REG_FAN_TARGET_LO      0x4c
+#define REG_FAN_TARGET_HI      0x4d
+#define REG_FAN_TACH_HI                0x4e
+#define REG_FAN_TACH_LO                0x4f
+#define REG_PRODUCT_ID         0xfd
+#define REG_MFG_ID             0xfe
+
+/* equation 4 from datasheet: rpm = (3932160 * multipler) / count */
+#define FAN_RPM_FACTOR         3932160
+
+/* 2103-2 and 2103-4's 3rd temperature sensor can be connected to two diodes
+ * in anti-parallel mode, and in this configuration both can be read
+ * independently (so we have 4 temperature inputs).  The device can't
+ * detect if it's connected in this mode, so we have to manually enable
+ * it.  Default is to leave the device in the state it's already in (-1).
+ * This parameter allows APD mode to be optionally forced on or off */
+static int apd = -1;
+module_param(apd, bool, 0);
+MODULE_PARM_DESC(init, "Set to zero to disable anti-parallel diode mode");
+
+struct temperature {
+       s8      degrees;
+       u8      fraction;       /* 0-7 multiples of 0.125 */
+};
+
+struct emc2103_data {
+       struct device           *hwmon_dev;
+       struct mutex            update_lock;
+       bool                    valid;          /* registers are valid */
+       bool                    fan_rpm_control;
+       int                     temp_count;     /* num of temp sensors */
+       unsigned long           last_updated;   /* in jiffies */
+       struct temperature      temp[4];        /* internal + 3 external */
+       s8                      temp_min[4];    /* no fractional part */
+       s8                      temp_max[4];    /* no fractional part */
+       u8                      temp_min_alarm;
+       u8                      temp_max_alarm;
+       u8                      fan_multiplier;
+       u16                     fan_tach;
+       u16                     fan_target;
+};
+
+static int read_u8_from_i2c(struct i2c_client *client, u8 i2c_reg, u8 *output)
+{
+       int status = i2c_smbus_read_byte_data(client, i2c_reg);
+       if (status < 0) {
+               dev_warn(&client->dev, "reg 0x%02x, err %d\n",
+                       i2c_reg, status);
+       } else {
+               *output = status;
+       }
+       return status;
+}
+
+static void read_temp_from_i2c(struct i2c_client *client, u8 i2c_reg,
+                              struct temperature *temp)
+{
+       u8 degrees, fractional;
+
+       if (read_u8_from_i2c(client, i2c_reg, &degrees) < 0)
+               return;
+
+       if (read_u8_from_i2c(client, i2c_reg + 1, &fractional) < 0)
+               return;
+
+       temp->degrees = degrees;
+       temp->fraction = (fractional & 0xe0) >> 5;
+}
+
+static void read_fan_from_i2c(struct i2c_client *client, u16 *output,
+                             u8 hi_addr, u8 lo_addr)
+{
+       u8 high_byte, lo_byte;
+
+       if (read_u8_from_i2c(client, hi_addr, &high_byte) < 0)
+               return;
+
+       if (read_u8_from_i2c(client, lo_addr, &lo_byte) < 0)
+               return;
+
+       *output = ((u16)high_byte << 5) | (lo_byte >> 3);
+}
+
+static void write_fan_target_to_i2c(struct i2c_client *client, u16 new_target)
+{
+       u8 high_byte = (new_target & 0x1fe0) >> 5;
+       u8 low_byte = (new_target & 0x001f) << 3;
+       i2c_smbus_write_byte_data(client, REG_FAN_TARGET_LO, low_byte);
+       i2c_smbus_write_byte_data(client, REG_FAN_TARGET_HI, high_byte);
+}
+
+static void read_fan_config_from_i2c(struct i2c_client *client)
+
+{
+       struct emc2103_data *data = i2c_get_clientdata(client);
+       u8 conf1;
+
+       if (read_u8_from_i2c(client, REG_FAN_CONF1, &conf1) < 0)
+               return;
+
+       data->fan_multiplier = 1 << ((conf1 & 0x60) >> 5);
+       data->fan_rpm_control = (conf1 & 0x80) != 0;
+}
+
+static struct emc2103_data *emc2103_update_device(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct emc2103_data *data = i2c_get_clientdata(client);
+
+       mutex_lock(&data->update_lock);
+
+       if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
+           || !data->valid) {
+               int i;
+
+               for (i = 0; i < data->temp_count; i++) {
+                       read_temp_from_i2c(client, REG_TEMP[i], &data->temp[i]);
+                       read_u8_from_i2c(client, REG_TEMP_MIN[i],
+                               &data->temp_min[i]);
+                       read_u8_from_i2c(client, REG_TEMP_MAX[i],
+                               &data->temp_max[i]);
+               }
+
+               read_u8_from_i2c(client, REG_TEMP_MIN_ALARM,
+                       &data->temp_min_alarm);
+               read_u8_from_i2c(client, REG_TEMP_MAX_ALARM,
+                       &data->temp_max_alarm);
+
+               read_fan_from_i2c(client, &data->fan_tach,
+                       REG_FAN_TACH_HI, REG_FAN_TACH_LO);
+               read_fan_from_i2c(client, &data->fan_target,
+                       REG_FAN_TARGET_HI, REG_FAN_TARGET_LO);
+               read_fan_config_from_i2c(client);
+
+               data->last_updated = jiffies;
+               data->valid = true;
+       }
+
+       mutex_unlock(&data->update_lock);
+
+       return data;
+}
+
+static ssize_t
+show_temp(struct device *dev, struct device_attribute *da, char *buf)
+{
+       int nr = to_sensor_dev_attr(da)->index;
+       struct emc2103_data *data = emc2103_update_device(dev);
+       int millidegrees = data->temp[nr].degrees * 1000
+               + data->temp[nr].fraction * 125;
+       return sprintf(buf, "%d\n", millidegrees);
+}
+
+static ssize_t
+show_temp_min(struct device *dev, struct device_attribute *da, char *buf)
+{
+       int nr = to_sensor_dev_attr(da)->index;
+       struct emc2103_data *data = emc2103_update_device(dev);
+       int millidegrees = data->temp_min[nr] * 1000;
+       return sprintf(buf, "%d\n", millidegrees);
+}
+
+static ssize_t
+show_temp_max(struct device *dev, struct device_attribute *da, char *buf)
+{
+       int nr = to_sensor_dev_attr(da)->index;
+       struct emc2103_data *data = emc2103_update_device(dev);
+       int millidegrees = data->temp_max[nr] * 1000;
+       return sprintf(buf, "%d\n", millidegrees);
+}
+
+static ssize_t
+show_temp_fault(struct device *dev, struct device_attribute *da, char *buf)
+{
+       int nr = to_sensor_dev_attr(da)->index;
+       struct emc2103_data *data = emc2103_update_device(dev);
+       bool fault = (data->temp[nr].degrees == -128);
+       return sprintf(buf, "%d\n", fault ? 1 : 0);
+}
+
+static ssize_t
+show_temp_min_alarm(struct device *dev, struct device_attribute *da, char *buf)
+{
+       int nr = to_sensor_dev_attr(da)->index;
+       struct emc2103_data *data = emc2103_update_device(dev);
+       bool alarm = data->temp_min_alarm & (1 << nr);
+       return sprintf(buf, "%d\n", alarm ? 1 : 0);
+}
+
+static ssize_t
+show_temp_max_alarm(struct device *dev, struct device_attribute *da, char *buf)
+{
+       int nr = to_sensor_dev_attr(da)->index;
+       struct emc2103_data *data = emc2103_update_device(dev);
+       bool alarm = data->temp_max_alarm & (1 << nr);
+       return sprintf(buf, "%d\n", alarm ? 1 : 0);
+}
+
+static ssize_t set_temp_min(struct device *dev, struct device_attribute *da,
+                           const char *buf, size_t count)
+{
+       int nr = to_sensor_dev_attr(da)->index;
+       struct i2c_client *client = to_i2c_client(dev);
+       struct emc2103_data *data = i2c_get_clientdata(client);
+       long val;
+
+       int result = strict_strtol(buf, 10, &val);
+       if (result < 0)
+               return -EINVAL;
+
+       val = DIV_ROUND_CLOSEST(val, 1000);
+       if ((val < -63) || (val > 127))
+               return -EINVAL;
+
+       mutex_lock(&data->update_lock);
+       data->temp_min[nr] = val;
+       i2c_smbus_write_byte_data(client, REG_TEMP_MIN[nr], val);
+       mutex_unlock(&data->update_lock);
+
+       return count;
+}
+
+static ssize_t set_temp_max(struct device *dev, struct device_attribute *da,
+                           const char *buf, size_t count)
+{
+       int nr = to_sensor_dev_attr(da)->index;
+       struct i2c_client *client = to_i2c_client(dev);
+       struct emc2103_data *data = i2c_get_clientdata(client);
+       long val;
+
+       int result = strict_strtol(buf, 10, &val);
+       if (result < 0)
+               return -EINVAL;
+
+       val = DIV_ROUND_CLOSEST(val, 1000);
+       if ((val < -63) || (val > 127))
+               return -EINVAL;
+
+       mutex_lock(&data->update_lock);
+       data->temp_max[nr] = val;
+       i2c_smbus_write_byte_data(client, REG_TEMP_MAX[nr], val);
+       mutex_unlock(&data->update_lock);
+
+       return count;
+}
+
+static ssize_t
+show_fan(struct device *dev, struct device_attribute *da, char *buf)
+{
+       struct emc2103_data *data = emc2103_update_device(dev);
+       int rpm = 0;
+       if (data->fan_tach != 0)
+               rpm = (FAN_RPM_FACTOR * data->fan_multiplier) / data->fan_tach;
+       return sprintf(buf, "%d\n", rpm);
+}
+
+static ssize_t
+show_fan_div(struct device *dev, struct device_attribute *da, char *buf)
+{
+       struct emc2103_data *data = emc2103_update_device(dev);
+       int fan_div = 8 / data->fan_multiplier;
+       return sprintf(buf, "%d\n", fan_div);
+}
+
+/* Note: we also update the fan target here, because its value is
+   determined in part by the fan clock divider.  This follows the principle
+   of least surprise; the user doesn't expect the fan target to change just
+   because the divider changed. */
+static ssize_t set_fan_div(struct device *dev, struct device_attribute *da,
+                          const char *buf, size_t count)
+{
+       struct emc2103_data *data = emc2103_update_device(dev);
+       struct i2c_client *client = to_i2c_client(dev);
+       int new_range_bits, old_div = 8 / data->fan_multiplier;
+       long new_div;
+
+       int status = strict_strtol(buf, 10, &new_div);
+       if (status < 0)
+               return -EINVAL;
+
+       if (new_div == old_div) /* No change */
+               return count;
+
+       switch (new_div) {
+       case 1:
+               new_range_bits = 3;
+               break;
+       case 2:
+               new_range_bits = 2;
+               break;
+       case 4:
+               new_range_bits = 1;
+               break;
+       case 8:
+               new_range_bits = 0;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       mutex_lock(&data->update_lock);
+
+       status = i2c_smbus_read_byte_data(client, REG_FAN_CONF1);
+       if (status < 0) {
+               dev_dbg(&client->dev, "reg 0x%02x, err %d\n",
+                       REG_FAN_CONF1, status);
+               mutex_unlock(&data->update_lock);
+               return -EIO;
+       }
+       status &= 0x9F;
+       status |= (new_range_bits << 5);
+       i2c_smbus_write_byte_data(client, REG_FAN_CONF1, status);
+
+       data->fan_multiplier = 8 / new_div;
+
+       /* update fan target if high byte is not disabled */
+       if ((data->fan_target & 0x1fe0) != 0x1fe0) {
+               u16 new_target = (data->fan_target * old_div) / new_div;
+               data->fan_target = min(new_target, (u16)0x1fff);
+               write_fan_target_to_i2c(client, data->fan_target);
+       }
+
+       /* invalidate data to force re-read from hardware */
+       data->valid = false;
+
+       mutex_unlock(&data->update_lock);
+       return count;
+}
+
+static ssize_t
+show_fan_target(struct device *dev, struct device_attribute *da, char *buf)
+{
+       struct emc2103_data *data = emc2103_update_device(dev);
+       int rpm = 0;
+
+       /* high byte of 0xff indicates disabled so return 0 */
+       if ((data->fan_target != 0) && ((data->fan_target & 0x1fe0) != 0x1fe0))
+               rpm = (FAN_RPM_FACTOR * data->fan_multiplier)
+                       / data->fan_target;
+
+       return sprintf(buf, "%d\n", rpm);
+}
+
+static ssize_t set_fan_target(struct device *dev, struct device_attribute *da,
+                             const char *buf, size_t count)
+{
+       struct emc2103_data *data = emc2103_update_device(dev);
+       struct i2c_client *client = to_i2c_client(dev);
+       long rpm_target;
+
+       int result = strict_strtol(buf, 10, &rpm_target);
+       if (result < 0)
+               return -EINVAL;
+
+       /* Datasheet states 16384 as maximum RPM target (table 3.2) */
+       if ((rpm_target < 0) || (rpm_target > 16384))
+               return -EINVAL;
+
+       mutex_lock(&data->update_lock);
+
+       if (rpm_target == 0)
+               data->fan_target = 0x1fff;
+       else
+               data->fan_target = SENSORS_LIMIT(
+                       (FAN_RPM_FACTOR * data->fan_multiplier) / rpm_target,
+                       0, 0x1fff);
+
+       write_fan_target_to_i2c(client, data->fan_target);
+
+       mutex_unlock(&data->update_lock);
+       return count;
+}
+
+static ssize_t
+show_fan_fault(struct device *dev, struct device_attribute *da, char *buf)
+{
+       struct emc2103_data *data = emc2103_update_device(dev);
+       bool fault = ((data->fan_tach & 0x1fe0) == 0x1fe0);
+       return sprintf(buf, "%d\n", fault ? 1 : 0);
+}
+
+static ssize_t
+show_pwm_enable(struct device *dev, struct device_attribute *da, char *buf)
+{
+       struct emc2103_data *data = emc2103_update_device(dev);
+       return sprintf(buf, "%d\n", data->fan_rpm_control ? 3 : 0);
+}
+
+static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *da,
+                             const char *buf, size_t count)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct emc2103_data *data = i2c_get_clientdata(client);
+       long new_value;
+       u8 conf_reg;
+
+       int result = strict_strtol(buf, 10, &new_value);
+       if (result < 0)
+               return -EINVAL;
+
+       mutex_lock(&data->update_lock);
+       switch (new_value) {
+       case 0:
+               data->fan_rpm_control = false;
+               break;
+       case 3:
+               data->fan_rpm_control = true;
+               break;
+       default:
+               mutex_unlock(&data->update_lock);
+               return -EINVAL;
+       }
+
+       read_u8_from_i2c(client, REG_FAN_CONF1, &conf_reg);
+
+       if (data->fan_rpm_control)
+               conf_reg |= 0x80;
+       else
+               conf_reg &= ~0x80;
+
+       i2c_smbus_write_byte_data(client, REG_FAN_CONF1, conf_reg);
+
+       mutex_unlock(&data->update_lock);
+       return count;
+}
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, show_temp_min,
+       set_temp_min, 0);
+static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, show_temp_max,
+       set_temp_max, 0);
+static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_temp_min_alarm,
+       NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_temp_max_alarm,
+       NULL, 0);
+
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO | S_IWUSR, show_temp_min,
+       set_temp_min, 1);
+static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO | S_IWUSR, show_temp_max,
+       set_temp_max, 1);
+static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_temp_min_alarm,
+       NULL, 1);
+static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_temp_max_alarm,
+       NULL, 1);
+
+static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp3_min, S_IRUGO | S_IWUSR, show_temp_min,
+       set_temp_min, 2);
+static SENSOR_DEVICE_ATTR(temp3_max, S_IRUGO | S_IWUSR, show_temp_max,
+       set_temp_max, 2);
+static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, show_temp_min_alarm,
+       NULL, 2);
+static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_temp_max_alarm,
+       NULL, 2);
+
+static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp4_min, S_IRUGO | S_IWUSR, show_temp_min,
+       set_temp_min, 3);
+static SENSOR_DEVICE_ATTR(temp4_max, S_IRUGO | S_IWUSR, show_temp_max,
+       set_temp_max, 3);
+static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_temp_fault, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO, show_temp_min_alarm,
+       NULL, 3);
+static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_temp_max_alarm,
+       NULL, 3);
+
+static DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL);
+static DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR, show_fan_div, set_fan_div);
+static DEVICE_ATTR(fan1_target, S_IRUGO | S_IWUSR, show_fan_target,
+       set_fan_target);
+static DEVICE_ATTR(fan1_fault, S_IRUGO, show_fan_fault, NULL);
+
+static DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, show_pwm_enable,
+       set_pwm_enable);
+
+/* sensors present on all models */
+static struct attribute *emc2103_attributes[] = {
+       &sensor_dev_attr_temp1_input.dev_attr.attr,
+       &sensor_dev_attr_temp1_min.dev_attr.attr,
+       &sensor_dev_attr_temp1_max.dev_attr.attr,
+       &sensor_dev_attr_temp1_fault.dev_attr.attr,
+       &sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
+       &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
+       &sensor_dev_attr_temp2_input.dev_attr.attr,
+       &sensor_dev_attr_temp2_min.dev_attr.attr,
+       &sensor_dev_attr_temp2_max.dev_attr.attr,
+       &sensor_dev_attr_temp2_fault.dev_attr.attr,
+       &sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
+       &sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
+       &dev_attr_fan1_input.attr,
+       &dev_attr_fan1_div.attr,
+       &dev_attr_fan1_target.attr,
+       &dev_attr_fan1_fault.attr,
+       &dev_attr_pwm1_enable.attr,
+       NULL
+};
+
+/* extra temperature sensors only present on 2103-2 and 2103-4 */
+static struct attribute *emc2103_attributes_temp3[] = {
+       &sensor_dev_attr_temp3_input.dev_attr.attr,
+       &sensor_dev_attr_temp3_min.dev_attr.attr,
+       &sensor_dev_attr_temp3_max.dev_attr.attr,
+       &sensor_dev_attr_temp3_fault.dev_attr.attr,
+       &sensor_dev_attr_temp3_min_alarm.dev_attr.attr,
+       &sensor_dev_attr_temp3_max_alarm.dev_attr.attr,
+       NULL
+};
+
+/* extra temperature sensors only present on 2103-2 and 2103-4 in APD mode */
+static struct attribute *emc2103_attributes_temp4[] = {
+       &sensor_dev_attr_temp4_input.dev_attr.attr,
+       &sensor_dev_attr_temp4_min.dev_attr.attr,
+       &sensor_dev_attr_temp4_max.dev_attr.attr,
+       &sensor_dev_attr_temp4_fault.dev_attr.attr,
+       &sensor_dev_attr_temp4_min_alarm.dev_attr.attr,
+       &sensor_dev_attr_temp4_max_alarm.dev_attr.attr,
+       NULL
+};
+
+static const struct attribute_group emc2103_group = {
+       .attrs = emc2103_attributes,
+};
+
+static const struct attribute_group emc2103_temp3_group = {
+       .attrs = emc2103_attributes_temp3,
+};
+
+static const struct attribute_group emc2103_temp4_group = {
+       .attrs = emc2103_attributes_temp4,
+};
+
+static int
+emc2103_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+       struct emc2103_data *data;
+       int status;
+
+       if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+               return -EIO;
+
+       data = kzalloc(sizeof(struct emc2103_data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       i2c_set_clientdata(client, data);
+       mutex_init(&data->update_lock);
+
+       /* 2103-2 and 2103-4 have 3 external diodes, 2103-1 has 1 */
+       status = i2c_smbus_read_byte_data(client, REG_PRODUCT_ID);
+       if (status == 0x24) {
+               /* 2103-1 only has 1 external diode */
+               data->temp_count = 2;
+       } else {
+               /* 2103-2 and 2103-4 have 3 or 4 external diodes */
+               status = i2c_smbus_read_byte_data(client, REG_CONF1);
+               if (status < 0) {
+                       dev_dbg(&client->dev, "reg 0x%02x, err %d\n", REG_CONF1,
+                               status);
+                       goto exit_free;
+               }
+
+               /* detect current state of hardware */
+               data->temp_count = (status & 0x01) ? 4 : 3;
+
+               /* force APD state if module parameter is set */
+               if (apd == 0) {
+                       /* force APD mode off */
+                       data->temp_count = 3;
+                       status &= ~(0x01);
+                       i2c_smbus_write_byte_data(client, REG_CONF1, status);
+               } else if (apd == 1) {
+                       /* force APD mode on */
+                       data->temp_count = 4;
+                       status |= 0x01;
+                       i2c_smbus_write_byte_data(client, REG_CONF1, status);
+               }
+       }
+
+       /* Register sysfs hooks */
+       status = sysfs_create_group(&client->dev.kobj, &emc2103_group);
+       if (status)
+               goto exit_free;
+
+       if (data->temp_count >= 3) {
+               status = sysfs_create_group(&client->dev.kobj,
+                       &emc2103_temp3_group);
+               if (status)
+                       goto exit_remove;
+       }
+
+       if (data->temp_count == 4) {
+               status = sysfs_create_group(&client->dev.kobj,
+                       &emc2103_temp4_group);
+               if (status)
+                       goto exit_remove_temp3;
+       }
+
+       data->hwmon_dev = hwmon_device_register(&client->dev);
+       if (IS_ERR(data->hwmon_dev)) {
+               status = PTR_ERR(data->hwmon_dev);
+               goto exit_remove_temp4;
+       }
+
+       dev_info(&client->dev, "%s: sensor '%s'\n",
+                dev_name(data->hwmon_dev), client->name);
+
+       return 0;
+
+exit_remove_temp4:
+       if (data->temp_count == 4)
+               sysfs_remove_group(&client->dev.kobj, &emc2103_temp4_group);
+exit_remove_temp3:
+       if (data->temp_count >= 3)
+               sysfs_remove_group(&client->dev.kobj, &emc2103_temp3_group);
+exit_remove:
+       sysfs_remove_group(&client->dev.kobj, &emc2103_group);
+exit_free:
+       kfree(data);
+       return status;
+}
+
+static int emc2103_remove(struct i2c_client *client)
+{
+       struct emc2103_data *data = i2c_get_clientdata(client);
+
+       hwmon_device_unregister(data->hwmon_dev);
+
+       if (data->temp_count == 4)
+               sysfs_remove_group(&client->dev.kobj, &emc2103_temp4_group);
+
+       if (data->temp_count >= 3)
+               sysfs_remove_group(&client->dev.kobj, &emc2103_temp3_group);
+
+       sysfs_remove_group(&client->dev.kobj, &emc2103_group);
+
+       kfree(data);
+       return 0;
+}
+
+static const struct i2c_device_id emc2103_ids[] = {
+       { "emc2103", 0, },
+       { /* LIST END */ }
+};
+MODULE_DEVICE_TABLE(i2c, emc2103_ids);
+
+/* Return 0 if detection is successful, -ENODEV otherwise */
+static int
+emc2103_detect(struct i2c_client *new_client, struct i2c_board_info *info)
+{
+       struct i2c_adapter *adapter = new_client->adapter;
+       int manufacturer, product;
+
+       if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+               return -ENODEV;
+
+       manufacturer = i2c_smbus_read_byte_data(new_client, REG_MFG_ID);
+       if (manufacturer != 0x5D)
+               return -ENODEV;
+
+       product = i2c_smbus_read_byte_data(new_client, REG_PRODUCT_ID);
+       if ((product != 0x24) && (product != 0x26))
+               return -ENODEV;
+
+       strlcpy(info->type, "emc2103", I2C_NAME_SIZE);
+
+       return 0;
+}
+
+static struct i2c_driver emc2103_driver = {
+       .class          = I2C_CLASS_HWMON,
+       .driver = {
+               .name   = "emc2103",
+       },
+       .probe          = emc2103_probe,
+       .remove         = emc2103_remove,
+       .id_table       = emc2103_ids,
+       .detect         = emc2103_detect,
+       .address_list   = normal_i2c,
+};
+
+static int __init sensors_emc2103_init(void)
+{
+       return i2c_add_driver(&emc2103_driver);
+}
+
+static void __exit sensors_emc2103_exit(void)
+{
+       i2c_del_driver(&emc2103_driver);
+}
+
+MODULE_AUTHOR("Steve Glendinning <steve.glendinning@smsc.com>");
+MODULE_DESCRIPTION("SMSC EMC2103 hwmon driver");
+MODULE_LICENSE("GPL");
+
+module_init(sensors_emc2103_init);
+module_exit(sensors_emc2103_exit);
index 25763d2223b664aeb16af5e3d2c43592265cff68..f7701295937dda4fcd6537c58a9236f7b91fcd54 100644 (file)
@@ -259,6 +259,7 @@ struct it87_sio_data {
        u8 revision;
        u8 vid_value;
        u8 beep_pin;
+       u8 internal;    /* Internal sensors can be labeled */
        /* Features skipped based on config or DMI */
        u8 skip_vid;
        u8 skip_fan;
@@ -1194,6 +1195,22 @@ static ssize_t show_vid_reg(struct device *dev, struct device_attribute *attr,
 }
 static DEVICE_ATTR(cpu0_vid, S_IRUGO, show_vid_reg, NULL);
 
+static ssize_t show_label(struct device *dev, struct device_attribute *attr,
+               char *buf)
+{
+       static const char *labels[] = {
+               "+5V",
+               "5VSB",
+               "Vbat",
+       };
+       int nr = to_sensor_dev_attr(attr)->index;
+
+       return sprintf(buf, "%s\n", labels[nr]);
+}
+static SENSOR_DEVICE_ATTR(in3_label, S_IRUGO, show_label, NULL, 0);
+static SENSOR_DEVICE_ATTR(in7_label, S_IRUGO, show_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(in8_label, S_IRUGO, show_label, NULL, 2);
+
 static ssize_t show_name(struct device *dev, struct device_attribute
                         *devattr, char *buf)
 {
@@ -1434,6 +1451,17 @@ static const struct attribute_group it87_group_vid = {
        .attrs = it87_attributes_vid,
 };
 
+static struct attribute *it87_attributes_label[] = {
+       &sensor_dev_attr_in3_label.dev_attr.attr,
+       &sensor_dev_attr_in7_label.dev_attr.attr,
+       &sensor_dev_attr_in8_label.dev_attr.attr,
+       NULL
+};
+
+static const struct attribute_group it87_group_label = {
+       .attrs = it87_attributes_vid,
+};
+
 /* SuperIO detection - will change isa_address if a chip is found */
 static int __init it87_find(unsigned short *address,
        struct it87_sio_data *sio_data)
@@ -1487,6 +1515,9 @@ static int __init it87_find(unsigned short *address,
        pr_info("it87: Found IT%04xF chip at 0x%x, revision %d\n",
                chip_type, *address, sio_data->revision);
 
+       /* in8 (Vbat) is always internal */
+       sio_data->internal = (1 << 2);
+
        /* Read GPIO config and VID value from LDN 7 (GPIO) */
        if (sio_data->type == it87) {
                /* The IT8705F doesn't have VID pins at all */
@@ -1540,9 +1571,9 @@ static int __init it87_find(unsigned short *address,
                        pr_notice("it87: Routing internal VCCH to in7\n");
                }
                if (reg & (1 << 0))
-                       pr_info("it87: in3 is VCC (+5V)\n");
+                       sio_data->internal |= (1 << 0);
                if (reg & (1 << 1))
-                       pr_info("it87: in7 is VCCH (+5V Stand-By)\n");
+                       sio_data->internal |= (1 << 1);
 
                sio_data->beep_pin = superio_inb(IT87_SIO_BEEP_PIN_REG) & 0x3f;
        }
@@ -1600,6 +1631,7 @@ static void it87_remove_files(struct device *dev)
        }
        if (!sio_data->skip_vid)
                sysfs_remove_group(&dev->kobj, &it87_group_vid);
+       sysfs_remove_group(&dev->kobj, &it87_group_label);
 }
 
 static int __devinit it87_probe(struct platform_device *pdev)
@@ -1725,6 +1757,16 @@ static int __devinit it87_probe(struct platform_device *pdev)
                        goto ERROR4;
        }
 
+       /* Export labels for internal sensors */
+       for (i = 0; i < 3; i++) {
+               if (!(sio_data->internal & (1 << i)))
+                       continue;
+               err = sysfs_create_file(&dev->kobj,
+                                       it87_attributes_label[i]);
+               if (err)
+                       goto ERROR4;
+       }
+
        data->hwmon_dev = hwmon_device_register(dev);
        if (IS_ERR(data->hwmon_dev)) {
                err = PTR_ERR(data->hwmon_dev);
index 8bdf80d915985ff77ae31b197aab2f7fb08acb68..b9bb3e0ca53083bd152332be698c58dbd0ff66bd 100644 (file)
@@ -252,12 +252,13 @@ static int __devinit k8temp_probe(struct pci_dev *pdev,
                                   &sensor_dev_attr_temp3_input.dev_attr);
                if (err)
                        goto exit_remove;
-               if (data->sensorsp & SEL_PLACE)
+               if (data->sensorsp & SEL_PLACE) {
                        err = device_create_file(&pdev->dev,
                                           &sensor_dev_attr_temp4_input.
                                           dev_attr);
                        if (err)
                                goto exit_remove;
+               }
        }
 
        err = device_create_file(&pdev->dev, &dev_attr_name);
index 393f354f92a4c012c46ee96a8062f7b604697cdf..ab5b87a8167711303bc2aa8a3f107f7b86af798a 100644 (file)
@@ -280,10 +280,49 @@ static int lm75_detect(struct i2c_client *new_client,
        return 0;
 }
 
+#ifdef CONFIG_PM
+static int lm75_suspend(struct device *dev)
+{
+       int status;
+       struct i2c_client *client = to_i2c_client(dev);
+       status = lm75_read_value(client, LM75_REG_CONF);
+       if (status < 0) {
+               dev_dbg(&client->dev, "Can't read config? %d\n", status);
+               return status;
+       }
+       status = status | LM75_SHUTDOWN;
+       lm75_write_value(client, LM75_REG_CONF, status);
+       return 0;
+}
+
+static int lm75_resume(struct device *dev)
+{
+       int status;
+       struct i2c_client *client = to_i2c_client(dev);
+       status = lm75_read_value(client, LM75_REG_CONF);
+       if (status < 0) {
+               dev_dbg(&client->dev, "Can't read config? %d\n", status);
+               return status;
+       }
+       status = status & ~LM75_SHUTDOWN;
+       lm75_write_value(client, LM75_REG_CONF, status);
+       return 0;
+}
+
+static const struct dev_pm_ops lm75_dev_pm_ops = {
+       .suspend        = lm75_suspend,
+       .resume         = lm75_resume,
+};
+#define LM75_DEV_PM_OPS (&lm75_dev_pm_ops)
+#else
+#define LM75_DEV_PM_OPS NULL
+#endif /* CONFIG_PM */
+
 static struct i2c_driver lm75_driver = {
        .class          = I2C_CLASS_HWMON,
        .driver = {
                .name   = "lm75",
+               .pm     = LM75_DEV_PM_OPS,
        },
        .probe          = lm75_probe,
        .remove         = lm75_remove,
index 7c93454bb4e3eb4d01e482b2475335aa03aafc7e..e547a3eb4de31ae03d12b3c17f6320d05df2504a 100644 (file)
@@ -30,6 +30,7 @@
 /* straight from the datasheet */
 #define LM75_TEMP_MIN (-55000)
 #define LM75_TEMP_MAX 125000
+#define LM75_SHUTDOWN 0x01
 
 /* TEMP: 0.001C/bit (-55C to +125C)
    REG: (0.5C/bit, two's complement) << 7 */
index 21d201befc2cdb7aeebb11c495f5ba3c10e74215..659308329308c4b03e1407c57557fd962b70fd8b 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/i2c.h>
 #include <linux/hwmon.h>
 #include <linux/hwmon-sysfs.h>
+#include <linux/i2c/ltc4245.h>
 
 /* Here are names of the chip's registers (a.k.a. commands) */
 enum ltc4245_cmd {
@@ -60,8 +61,72 @@ struct ltc4245_data {
 
        /* Voltage registers */
        u8 vregs[0x0d];
+
+       /* GPIO ADC registers */
+       bool use_extra_gpios;
+       int gpios[3];
 };
 
+/*
+ * Update the readings from the GPIO pins. If the driver has been configured to
+ * sample all GPIO's as analog voltages, a round-robin sampling method is used.
+ * Otherwise, only the configured GPIO pin is sampled.
+ *
+ * LOCKING: must hold data->update_lock
+ */
+static void ltc4245_update_gpios(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct ltc4245_data *data = i2c_get_clientdata(client);
+       u8 gpio_curr, gpio_next, gpio_reg;
+       int i;
+
+       /* no extra gpio support, we're basically done */
+       if (!data->use_extra_gpios) {
+               data->gpios[0] = data->vregs[LTC4245_GPIOADC - 0x10];
+               return;
+       }
+
+       /*
+        * If the last reading was too long ago, then we mark all old GPIO
+        * readings as stale by setting them to -EAGAIN
+        */
+       if (time_after(jiffies, data->last_updated + 5 * HZ)) {
+               dev_dbg(&client->dev, "Marking GPIOs invalid\n");
+               for (i = 0; i < ARRAY_SIZE(data->gpios); i++)
+                       data->gpios[i] = -EAGAIN;
+       }
+
+       /*
+        * Get the current GPIO pin
+        *
+        * The datasheet calls these GPIO[1-3], but we'll calculate the zero
+        * based array index instead, and call them GPIO[0-2]. This is much
+        * easier to think about.
+        */
+       gpio_curr = (data->cregs[LTC4245_GPIO] & 0xc0) >> 6;
+       if (gpio_curr > 0)
+               gpio_curr -= 1;
+
+       /* Read the GPIO voltage from the GPIOADC register */
+       data->gpios[gpio_curr] = data->vregs[LTC4245_GPIOADC - 0x10];
+
+       /* Find the next GPIO pin to read */
+       gpio_next = (gpio_curr + 1) % ARRAY_SIZE(data->gpios);
+
+       /*
+        * Calculate the correct setting for the GPIO register so it will
+        * sample the next GPIO pin
+        */
+       gpio_reg = (data->cregs[LTC4245_GPIO] & 0x3f) | ((gpio_next + 1) << 6);
+
+       /* Update the GPIO register */
+       i2c_smbus_write_byte_data(client, LTC4245_GPIO, gpio_reg);
+
+       /* Update saved data */
+       data->cregs[LTC4245_GPIO] = gpio_reg;
+}
+
 static struct ltc4245_data *ltc4245_update_device(struct device *dev)
 {
        struct i2c_client *client = to_i2c_client(dev);
@@ -93,6 +158,9 @@ static struct ltc4245_data *ltc4245_update_device(struct device *dev)
                                data->vregs[i] = val;
                }
 
+               /* Update GPIO readings */
+               ltc4245_update_gpios(dev);
+
                data->last_updated = jiffies;
                data->valid = 1;
        }
@@ -233,6 +301,22 @@ static ssize_t ltc4245_show_alarm(struct device *dev,
        return snprintf(buf, PAGE_SIZE, "%u\n", (reg & mask) ? 1 : 0);
 }
 
+static ssize_t ltc4245_show_gpio(struct device *dev,
+                                struct device_attribute *da,
+                                char *buf)
+{
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+       struct ltc4245_data *data = ltc4245_update_device(dev);
+       int val = data->gpios[attr->index];
+
+       /* handle stale GPIO's */
+       if (val < 0)
+               return val;
+
+       /* Convert to millivolts and print */
+       return snprintf(buf, PAGE_SIZE, "%u\n", val * 10);
+}
+
 /* These macros are used below in constructing device attribute objects
  * for use with sysfs_create_group() to make a sysfs device file
  * for each register.
@@ -254,6 +338,10 @@ static ssize_t ltc4245_show_alarm(struct device *dev,
        static SENSOR_DEVICE_ATTR_2(name, S_IRUGO, \
        ltc4245_show_alarm, NULL, (mask), reg)
 
+#define LTC4245_GPIO_VOLTAGE(name, gpio_num) \
+       static SENSOR_DEVICE_ATTR(name, S_IRUGO, \
+       ltc4245_show_gpio, NULL, gpio_num)
+
 /* Construct a sensor_device_attribute structure for each register */
 
 /* Input voltages */
@@ -293,7 +381,9 @@ LTC4245_ALARM(in7_min_alarm,        (1 << 2),       LTC4245_FAULT2);
 LTC4245_ALARM(in8_min_alarm,   (1 << 3),       LTC4245_FAULT2);
 
 /* GPIO voltages */
-LTC4245_VOLTAGE(in9_input,                     LTC4245_GPIOADC);
+LTC4245_GPIO_VOLTAGE(in9_input,                        0);
+LTC4245_GPIO_VOLTAGE(in10_input,               1);
+LTC4245_GPIO_VOLTAGE(in11_input,               2);
 
 /* Power Consumption (virtual) */
 LTC4245_POWER(power1_input,                    LTC4245_12VSENSE);
@@ -304,7 +394,7 @@ LTC4245_POWER(power4_input,                 LTC4245_VEESENSE);
 /* Finally, construct an array of pointers to members of the above objects,
  * as required for sysfs_create_group()
  */
-static struct attribute *ltc4245_attributes[] = {
+static struct attribute *ltc4245_std_attributes[] = {
        &sensor_dev_attr_in1_input.dev_attr.attr,
        &sensor_dev_attr_in2_input.dev_attr.attr,
        &sensor_dev_attr_in3_input.dev_attr.attr,
@@ -345,10 +435,77 @@ static struct attribute *ltc4245_attributes[] = {
        NULL,
 };
 
-static const struct attribute_group ltc4245_group = {
-       .attrs = ltc4245_attributes,
+static struct attribute *ltc4245_gpio_attributes[] = {
+       &sensor_dev_attr_in10_input.dev_attr.attr,
+       &sensor_dev_attr_in11_input.dev_attr.attr,
+       NULL,
+};
+
+static const struct attribute_group ltc4245_std_group = {
+       .attrs = ltc4245_std_attributes,
+};
+
+static const struct attribute_group ltc4245_gpio_group = {
+       .attrs = ltc4245_gpio_attributes,
 };
 
+static int ltc4245_sysfs_create_groups(struct i2c_client *client)
+{
+       struct ltc4245_data *data = i2c_get_clientdata(client);
+       struct device *dev = &client->dev;
+       int ret;
+
+       /* register the standard sysfs attributes */
+       ret = sysfs_create_group(&dev->kobj, &ltc4245_std_group);
+       if (ret) {
+               dev_err(dev, "unable to register standard attributes\n");
+               return ret;
+       }
+
+       /* if we're using the extra gpio support, register it's attributes */
+       if (data->use_extra_gpios) {
+               ret = sysfs_create_group(&dev->kobj, &ltc4245_gpio_group);
+               if (ret) {
+                       dev_err(dev, "unable to register gpio attributes\n");
+                       sysfs_remove_group(&dev->kobj, &ltc4245_std_group);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+static void ltc4245_sysfs_remove_groups(struct i2c_client *client)
+{
+       struct ltc4245_data *data = i2c_get_clientdata(client);
+       struct device *dev = &client->dev;
+
+       if (data->use_extra_gpios)
+               sysfs_remove_group(&dev->kobj, &ltc4245_gpio_group);
+
+       sysfs_remove_group(&dev->kobj, &ltc4245_std_group);
+}
+
+static bool ltc4245_use_extra_gpios(struct i2c_client *client)
+{
+       struct ltc4245_platform_data *pdata = dev_get_platdata(&client->dev);
+#ifdef CONFIG_OF
+       struct device_node *np = client->dev.of_node;
+#endif
+
+       /* prefer platform data */
+       if (pdata)
+               return pdata->use_extra_gpios;
+
+#ifdef CONFIG_OF
+       /* fallback on OF */
+       if (of_find_property(np, "ltc4245,use-extra-gpios", NULL))
+               return true;
+#endif
+
+       return false;
+}
+
 static int ltc4245_probe(struct i2c_client *client,
                         const struct i2c_device_id *id)
 {
@@ -367,15 +524,16 @@ static int ltc4245_probe(struct i2c_client *client,
 
        i2c_set_clientdata(client, data);
        mutex_init(&data->update_lock);
+       data->use_extra_gpios = ltc4245_use_extra_gpios(client);
 
        /* Initialize the LTC4245 chip */
        i2c_smbus_write_byte_data(client, LTC4245_FAULT1, 0x00);
        i2c_smbus_write_byte_data(client, LTC4245_FAULT2, 0x00);
 
        /* Register sysfs hooks */
-       ret = sysfs_create_group(&client->dev.kobj, &ltc4245_group);
+       ret = ltc4245_sysfs_create_groups(client);
        if (ret)
-               goto out_sysfs_create_group;
+               goto out_sysfs_create_groups;
 
        data->hwmon_dev = hwmon_device_register(&client->dev);
        if (IS_ERR(data->hwmon_dev)) {
@@ -386,8 +544,8 @@ static int ltc4245_probe(struct i2c_client *client,
        return 0;
 
 out_hwmon_device_register:
-       sysfs_remove_group(&client->dev.kobj, &ltc4245_group);
-out_sysfs_create_group:
+       ltc4245_sysfs_remove_groups(client);
+out_sysfs_create_groups:
        kfree(data);
 out_kzalloc:
        return ret;
@@ -398,8 +556,7 @@ static int ltc4245_remove(struct i2c_client *client)
        struct ltc4245_data *data = i2c_get_clientdata(client);
 
        hwmon_device_unregister(data->hwmon_dev);
-       sysfs_remove_group(&client->dev.kobj, &ltc4245_group);
-
+       ltc4245_sysfs_remove_groups(client);
        kfree(data);
 
        return 0;
index 4a64b85d4ec955d383932fe25d5edb726981a650..68e69a49633cc2ac5cbbf9572b829c8bc6aadd76 100644 (file)
@@ -1610,11 +1610,8 @@ static struct pc87360_data *pc87360_update_device(struct device *dev)
 
 static int __init pc87360_device_add(unsigned short address)
 {
-       struct resource res = {
-               .name   = "pc87360",
-               .flags  = IORESOURCE_IO,
-       };
-       int err, i;
+       struct resource res[3];
+       int err, i, res_count;
 
        pdev = platform_device_alloc("pc87360", address);
        if (!pdev) {
@@ -1623,22 +1620,28 @@ static int __init pc87360_device_add(unsigned short address)
                goto exit;
        }
 
+       memset(res, 0, 3 * sizeof(struct resource));
+       res_count = 0;
        for (i = 0; i < 3; i++) {
                if (!extra_isa[i])
                        continue;
-               res.start = extra_isa[i];
-               res.end = extra_isa[i] + PC87360_EXTENT - 1;
+               res[res_count].start = extra_isa[i];
+               res[res_count].end = extra_isa[i] + PC87360_EXTENT - 1;
+               res[res_count].name = "pc87360",
+               res[res_count].flags = IORESOURCE_IO,
 
-               err = acpi_check_resource_conflict(&res);
+               err = acpi_check_resource_conflict(&res[res_count]);
                if (err)
                        goto exit_device_put;
 
-               err = platform_device_add_resources(pdev, &res, 1);
-               if (err) {
-                       printk(KERN_ERR "pc87360: Device resource[%d] "
-                              "addition failed (%d)\n", i, err);
-                       goto exit_device_put;
-               }
+               res_count++;
+       }
+
+       err = platform_device_add_resources(pdev, res, res_count);
+       if (err) {
+               printk(KERN_ERR "pc87360: Device resources addition failed "
+                      "(%d)\n", err);
+               goto exit_device_put;
        }
 
        err = platform_device_add(pdev);
index 3170b26d2443b76bfbd6b518153c7329d622280c..9ec4daaf6ca6313ee58744f7ffb9eaf5146cce78 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  pc87427.c - hardware monitoring driver for the
  *              National Semiconductor PC87427 Super-I/O chip
- *  Copyright (C) 2006 Jean Delvare <khali@linux-fr.org>
+ *  Copyright (C) 2006, 2008, 2010  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 version 2 as
  *  Supports the following chips:
  *
  *  Chip        #vin    #fan    #pwm    #temp   devid
- *  PC87427     -       8       -       -       0xF2
+ *  PC87427     -       8       4       6       0xF2
  *
  *  This driver assumes that no more than one chip is present.
- *  Only fan inputs are supported so far, although the chip can do much more.
+ *  Only fans are fully supported so far. Temperatures are in read-only
+ *  mode, and voltages aren't supported at all.
  */
 
 #include <linux/module.h>
@@ -57,6 +58,25 @@ struct pc87427_data {
        u16 fan[8];                     /* register values */
        u16 fan_min[8];                 /* register values */
        u8 fan_status[8];               /* register values */
+
+       u8 pwm_enabled;                 /* bit vector */
+       u8 pwm_auto_ok;                 /* bit vector */
+       u8 pwm_enable[4];               /* register values */
+       u8 pwm[4];                      /* register values */
+
+       u8 temp_enabled;                /* bit vector */
+       s16 temp[6];                    /* register values */
+       s8 temp_min[6];                 /* register values */
+       s8 temp_max[6];                 /* register values */
+       s8 temp_crit[6];                /* register values */
+       u8 temp_status[6];              /* register values */
+       u8 temp_type[6];                /* register values */
+};
+
+struct pc87427_sio_data {
+       unsigned short address[2];
+       u8 has_fanin;
+       u8 has_fanout;
 };
 
 /*
@@ -65,6 +85,13 @@ struct pc87427_data {
 
 #define SIOREG_LDSEL   0x07    /* Logical device select */
 #define SIOREG_DEVID   0x20    /* Device ID */
+#define SIOREG_CF2     0x22    /* Configuration 2 */
+#define SIOREG_CF3     0x23    /* Configuration 3 */
+#define SIOREG_CF4     0x24    /* Configuration 4 */
+#define SIOREG_CF5     0x25    /* Configuration 5 */
+#define SIOREG_CFB     0x2B    /* Configuration B */
+#define SIOREG_CFC     0x2C    /* Configuration C */
+#define SIOREG_CFD     0x2D    /* Configuration D */
 #define SIOREG_ACT     0x30    /* Device activation */
 #define SIOREG_MAP     0x50    /* I/O or memory mapping */
 #define SIOREG_IOBASE  0x60    /* I/O base address */
@@ -102,6 +129,8 @@ static inline void superio_exit(int sioaddr)
 #define BANK_FM(nr)            (nr)
 #define BANK_FT(nr)            (0x08 + (nr))
 #define BANK_FC(nr)            (0x10 + (nr) * 2)
+#define BANK_TM(nr)            (nr)
+#define BANK_VM(nr)            (0x08 + (nr))
 
 /*
  * I/O access functions
@@ -178,6 +207,127 @@ static inline u16 fan_to_reg(unsigned long val)
        return ((1350000UL + val / 2) / val) << 2;
 }
 
+/*
+ * PWM registers and conversions
+ */
+
+#define PC87427_REG_PWM_ENABLE         0x10
+#define PC87427_REG_PWM_DUTY           0x12
+
+#define PWM_ENABLE_MODE_MASK           (7 << 4)
+#define PWM_ENABLE_CTLEN               (1 << 0)
+
+#define PWM_MODE_MANUAL                        (0 << 4)
+#define PWM_MODE_AUTO                  (1 << 4)
+#define PWM_MODE_OFF                   (2 << 4)
+#define PWM_MODE_ON                    (7 << 4)
+
+/* Dedicated function to read all registers related to a given PWM output.
+   This saves us quite a few locks and bank selections.
+   Must be called with data->lock held.
+   nr is from 0 to 3 */
+static void pc87427_readall_pwm(struct pc87427_data *data, u8 nr)
+{
+       int iobase = data->address[LD_FAN];
+
+       outb(BANK_FC(nr), iobase + PC87427_REG_BANK);
+       data->pwm_enable[nr] = inb(iobase + PC87427_REG_PWM_ENABLE);
+       data->pwm[nr] = inb(iobase + PC87427_REG_PWM_DUTY);
+}
+
+static inline int pwm_enable_from_reg(u8 reg)
+{
+       switch (reg & PWM_ENABLE_MODE_MASK) {
+       case PWM_MODE_ON:
+               return 0;
+       case PWM_MODE_MANUAL:
+       case PWM_MODE_OFF:
+               return 1;
+       case PWM_MODE_AUTO:
+               return 2;
+       default:
+               return -EPROTO;
+       }
+}
+
+static inline u8 pwm_enable_to_reg(unsigned long val, u8 pwmval)
+{
+       switch (val) {
+       default:
+               return PWM_MODE_ON;
+       case 1:
+               return pwmval ? PWM_MODE_MANUAL : PWM_MODE_OFF;
+       case 2:
+               return PWM_MODE_AUTO;
+       }
+}
+
+/*
+ * Temperature registers and conversions
+ */
+
+#define PC87427_REG_TEMP_STATUS                0x10
+#define PC87427_REG_TEMP               0x14
+#define PC87427_REG_TEMP_MAX           0x18
+#define PC87427_REG_TEMP_MIN           0x19
+#define PC87427_REG_TEMP_CRIT          0x1a
+#define PC87427_REG_TEMP_TYPE          0x1d
+
+#define TEMP_STATUS_CHANEN             (1 << 0)
+#define TEMP_STATUS_LOWFLG             (1 << 1)
+#define TEMP_STATUS_HIGHFLG            (1 << 2)
+#define TEMP_STATUS_CRITFLG            (1 << 3)
+#define TEMP_STATUS_SENSERR            (1 << 5)
+#define TEMP_TYPE_MASK                 (3 << 5)
+
+#define TEMP_TYPE_THERMISTOR           (1 << 5)
+#define TEMP_TYPE_REMOTE_DIODE         (2 << 5)
+#define TEMP_TYPE_LOCAL_DIODE          (3 << 5)
+
+/* Dedicated function to read all registers related to a given temperature
+   input. This saves us quite a few locks and bank selections.
+   Must be called with data->lock held.
+   nr is from 0 to 5 */
+static void pc87427_readall_temp(struct pc87427_data *data, u8 nr)
+{
+       int iobase = data->address[LD_TEMP];
+
+       outb(BANK_TM(nr), iobase + PC87427_REG_BANK);
+       data->temp[nr] = le16_to_cpu(inw(iobase + PC87427_REG_TEMP));
+       data->temp_max[nr] = inb(iobase + PC87427_REG_TEMP_MAX);
+       data->temp_min[nr] = inb(iobase + PC87427_REG_TEMP_MIN);
+       data->temp_crit[nr] = inb(iobase + PC87427_REG_TEMP_CRIT);
+       data->temp_type[nr] = inb(iobase + PC87427_REG_TEMP_TYPE);
+       data->temp_status[nr] = inb(iobase + PC87427_REG_TEMP_STATUS);
+       /* Clear fan alarm bits */
+       outb(data->temp_status[nr], iobase + PC87427_REG_TEMP_STATUS);
+}
+
+static inline unsigned int temp_type_from_reg(u8 reg)
+{
+       switch (reg & TEMP_TYPE_MASK) {
+       case TEMP_TYPE_THERMISTOR:
+               return 4;
+       case TEMP_TYPE_REMOTE_DIODE:
+       case TEMP_TYPE_LOCAL_DIODE:
+               return 3;
+       default:
+               return 0;
+       }
+}
+
+/* We assume 8-bit thermal sensors; 9-bit thermal sensors are possible
+   too, but I have no idea how to figure out when they are used. */
+static inline long temp_from_reg(s16 reg)
+{
+       return reg * 1000 / 256;
+}
+
+static inline long temp_from_reg8(s8 reg)
+{
+       return reg * 1000;
+}
+
 /*
  * Data interface
  */
@@ -198,6 +348,21 @@ static struct pc87427_data *pc87427_update_device(struct device *dev)
                        continue;
                pc87427_readall_fan(data, i);
        }
+
+       /* PWM outputs */
+       for (i = 0; i < 4; i++) {
+               if (!(data->pwm_enabled & (1 << i)))
+                       continue;
+               pc87427_readall_pwm(data, i);
+       }
+
+       /* Temperature channels */
+       for (i = 0; i < 6; i++) {
+               if (!(data->temp_enabled & (1 << i)))
+                       continue;
+               pc87427_readall_temp(data, i);
+       }
+
        data->last_updated = jiffies;
 
 done:
@@ -208,9 +373,8 @@ done:
 static ssize_t show_fan_input(struct device *dev, struct device_attribute
                              *devattr, char *buf)
 {
-       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
        struct pc87427_data *data = pc87427_update_device(dev);
-       int nr = attr->index;
+       int nr = to_sensor_dev_attr(devattr)->index;
 
        return sprintf(buf, "%lu\n", fan_from_reg(data->fan[nr]));
 }
@@ -218,9 +382,8 @@ static ssize_t show_fan_input(struct device *dev, struct device_attribute
 static ssize_t show_fan_min(struct device *dev, struct device_attribute
                            *devattr, char *buf)
 {
-       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
        struct pc87427_data *data = pc87427_update_device(dev);
-       int nr = attr->index;
+       int nr = to_sensor_dev_attr(devattr)->index;
 
        return sprintf(buf, "%lu\n", fan_from_reg(data->fan_min[nr]));
 }
@@ -228,9 +391,8 @@ static ssize_t show_fan_min(struct device *dev, struct device_attribute
 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 pc87427_data *data = pc87427_update_device(dev);
-       int nr = attr->index;
+       int nr = to_sensor_dev_attr(devattr)->index;
 
        return sprintf(buf, "%d\n", !!(data->fan_status[nr]
                                       & FAN_STATUS_LOSPD));
@@ -239,9 +401,8 @@ static ssize_t show_fan_alarm(struct device *dev, struct device_attribute
 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 pc87427_data *data = pc87427_update_device(dev);
-       int nr = attr->index;
+       int nr = to_sensor_dev_attr(devattr)->index;
 
        return sprintf(buf, "%d\n", !!(data->fan_status[nr]
                                       & FAN_STATUS_STALL));
@@ -251,11 +412,13 @@ static ssize_t set_fan_min(struct device *dev, struct device_attribute
                           *devattr, const char *buf, size_t count)
 {
        struct pc87427_data *data = dev_get_drvdata(dev);
-       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-       int nr = attr->index;
-       unsigned long val = simple_strtoul(buf, NULL, 10);
+       int nr = to_sensor_dev_attr(devattr)->index;
+       unsigned long val;
        int iobase = data->address[LD_FAN];
 
+       if (strict_strtoul(buf, 10, &val) < 0)
+               return -EINVAL;
+
        mutex_lock(&data->lock);
        outb(BANK_FM(nr), iobase + PC87427_REG_BANK);
        /* The low speed limit registers are read-only while monitoring
@@ -377,6 +540,390 @@ static const struct attribute_group pc87427_group_fan[8] = {
        { .attrs = pc87427_attributes_fan[7] },
 };
 
+/* Must be called with data->lock held and pc87427_readall_pwm() freshly
+   called */
+static void update_pwm_enable(struct pc87427_data *data, int nr, u8 mode)
+{
+       int iobase = data->address[LD_FAN];
+       data->pwm_enable[nr] &= ~PWM_ENABLE_MODE_MASK;
+       data->pwm_enable[nr] |= mode;
+       outb(data->pwm_enable[nr], iobase + PC87427_REG_PWM_ENABLE);
+}
+
+static ssize_t show_pwm_enable(struct device *dev, struct device_attribute
+                              *devattr, char *buf)
+{
+       struct pc87427_data *data = pc87427_update_device(dev);
+       int nr = to_sensor_dev_attr(devattr)->index;
+       int pwm_enable;
+
+       pwm_enable = pwm_enable_from_reg(data->pwm_enable[nr]);
+       if (pwm_enable < 0)
+               return pwm_enable;
+       return sprintf(buf, "%d\n", pwm_enable);
+}
+
+static ssize_t set_pwm_enable(struct device *dev, struct device_attribute
+                             *devattr, const char *buf, size_t count)
+{
+       struct pc87427_data *data = dev_get_drvdata(dev);
+       int nr = to_sensor_dev_attr(devattr)->index;
+       unsigned long val;
+
+       if (strict_strtoul(buf, 10, &val) < 0 || val > 2)
+               return -EINVAL;
+       /* Can't go to automatic mode if it isn't configured */
+       if (val == 2 && !(data->pwm_auto_ok & (1 << nr)))
+               return -EINVAL;
+
+       mutex_lock(&data->lock);
+       pc87427_readall_pwm(data, nr);
+       update_pwm_enable(data, nr, pwm_enable_to_reg(val, data->pwm[nr]));
+       mutex_unlock(&data->lock);
+
+       return count;
+}
+
+static ssize_t show_pwm(struct device *dev, struct device_attribute
+                       *devattr, char *buf)
+{
+       struct pc87427_data *data = pc87427_update_device(dev);
+       int nr = to_sensor_dev_attr(devattr)->index;
+
+       return sprintf(buf, "%d\n", (int)data->pwm[nr]);
+}
+
+static ssize_t set_pwm(struct device *dev, struct device_attribute
+                      *devattr, const char *buf, size_t count)
+{
+       struct pc87427_data *data = dev_get_drvdata(dev);
+       int nr = to_sensor_dev_attr(devattr)->index;
+       unsigned long val;
+       int iobase = data->address[LD_FAN];
+       u8 mode;
+
+       if (strict_strtoul(buf, 10, &val) < 0 || val > 0xff)
+               return -EINVAL;
+
+       mutex_lock(&data->lock);
+       pc87427_readall_pwm(data, nr);
+       mode = data->pwm_enable[nr] & PWM_ENABLE_MODE_MASK;
+       if (mode != PWM_MODE_MANUAL && mode != PWM_MODE_OFF) {
+               dev_notice(dev, "Can't set PWM%d duty cycle while not in "
+                          "manual mode\n", nr + 1);
+               mutex_unlock(&data->lock);
+               return -EPERM;
+       }
+
+       /* We may have to change the mode */
+       if (mode == PWM_MODE_MANUAL && val == 0) {
+               /* Transition from Manual to Off */
+               update_pwm_enable(data, nr, PWM_MODE_OFF);
+               mode = PWM_MODE_OFF;
+               dev_dbg(dev, "Switching PWM%d from %s to %s\n", nr + 1,
+                       "manual", "off");
+       } else if (mode == PWM_MODE_OFF && val != 0) {
+               /* Transition from Off to Manual */
+               update_pwm_enable(data, nr, PWM_MODE_MANUAL);
+               mode = PWM_MODE_MANUAL;
+               dev_dbg(dev, "Switching PWM%d from %s to %s\n", nr + 1,
+                       "off", "manual");
+       }
+
+       data->pwm[nr] = val;
+       if (mode == PWM_MODE_MANUAL)
+               outb(val, iobase + PC87427_REG_PWM_DUTY);
+       mutex_unlock(&data->lock);
+
+       return count;
+}
+
+static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO,
+                         show_pwm_enable, set_pwm_enable, 0);
+static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO,
+                         show_pwm_enable, set_pwm_enable, 1);
+static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO,
+                         show_pwm_enable, set_pwm_enable, 2);
+static SENSOR_DEVICE_ATTR(pwm4_enable, S_IWUSR | S_IRUGO,
+                         show_pwm_enable, set_pwm_enable, 3);
+
+static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 0);
+static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 1);
+static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 2);
+static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 3);
+
+static struct attribute *pc87427_attributes_pwm[4][3] = {
+       {
+               &sensor_dev_attr_pwm1_enable.dev_attr.attr,
+               &sensor_dev_attr_pwm1.dev_attr.attr,
+               NULL
+       }, {
+               &sensor_dev_attr_pwm2_enable.dev_attr.attr,
+               &sensor_dev_attr_pwm2.dev_attr.attr,
+               NULL
+       }, {
+               &sensor_dev_attr_pwm3_enable.dev_attr.attr,
+               &sensor_dev_attr_pwm3.dev_attr.attr,
+               NULL
+       }, {
+               &sensor_dev_attr_pwm4_enable.dev_attr.attr,
+               &sensor_dev_attr_pwm4.dev_attr.attr,
+               NULL
+       }
+};
+
+static const struct attribute_group pc87427_group_pwm[4] = {
+       { .attrs = pc87427_attributes_pwm[0] },
+       { .attrs = pc87427_attributes_pwm[1] },
+       { .attrs = pc87427_attributes_pwm[2] },
+       { .attrs = pc87427_attributes_pwm[3] },
+};
+
+static ssize_t show_temp_input(struct device *dev, struct device_attribute
+                              *devattr, char *buf)
+{
+       struct pc87427_data *data = pc87427_update_device(dev);
+       int nr = to_sensor_dev_attr(devattr)->index;
+
+       return sprintf(buf, "%ld\n", temp_from_reg(data->temp[nr]));
+}
+
+static ssize_t show_temp_min(struct device *dev, struct device_attribute
+                            *devattr, char *buf)
+{
+       struct pc87427_data *data = pc87427_update_device(dev);
+       int nr = to_sensor_dev_attr(devattr)->index;
+
+       return sprintf(buf, "%ld\n", temp_from_reg8(data->temp_min[nr]));
+}
+
+static ssize_t show_temp_max(struct device *dev, struct device_attribute
+                            *devattr, char *buf)
+{
+       struct pc87427_data *data = pc87427_update_device(dev);
+       int nr = to_sensor_dev_attr(devattr)->index;
+
+       return sprintf(buf, "%ld\n", temp_from_reg8(data->temp_max[nr]));
+}
+
+static ssize_t show_temp_crit(struct device *dev, struct device_attribute
+                             *devattr, char *buf)
+{
+       struct pc87427_data *data = pc87427_update_device(dev);
+       int nr = to_sensor_dev_attr(devattr)->index;
+
+       return sprintf(buf, "%ld\n", temp_from_reg8(data->temp_crit[nr]));
+}
+
+static ssize_t show_temp_type(struct device *dev, struct device_attribute
+                             *devattr, char *buf)
+{
+       struct pc87427_data *data = pc87427_update_device(dev);
+       int nr = to_sensor_dev_attr(devattr)->index;
+
+       return sprintf(buf, "%u\n", temp_type_from_reg(data->temp_type[nr]));
+}
+
+static ssize_t show_temp_min_alarm(struct device *dev, struct device_attribute
+                                  *devattr, char *buf)
+{
+       struct pc87427_data *data = pc87427_update_device(dev);
+       int nr = to_sensor_dev_attr(devattr)->index;
+
+       return sprintf(buf, "%d\n", !!(data->temp_status[nr]
+                                      & TEMP_STATUS_LOWFLG));
+}
+
+static ssize_t show_temp_max_alarm(struct device *dev, struct device_attribute
+                                  *devattr, char *buf)
+{
+       struct pc87427_data *data = pc87427_update_device(dev);
+       int nr = to_sensor_dev_attr(devattr)->index;
+
+       return sprintf(buf, "%d\n", !!(data->temp_status[nr]
+                                      & TEMP_STATUS_HIGHFLG));
+}
+
+static ssize_t show_temp_crit_alarm(struct device *dev, struct device_attribute
+                                  *devattr, char *buf)
+{
+       struct pc87427_data *data = pc87427_update_device(dev);
+       int nr = to_sensor_dev_attr(devattr)->index;
+
+       return sprintf(buf, "%d\n", !!(data->temp_status[nr]
+                                      & TEMP_STATUS_CRITFLG));
+}
+
+static ssize_t show_temp_fault(struct device *dev, struct device_attribute
+                              *devattr, char *buf)
+{
+       struct pc87427_data *data = pc87427_update_device(dev);
+       int nr = to_sensor_dev_attr(devattr)->index;
+
+       return sprintf(buf, "%d\n", !!(data->temp_status[nr]
+                                      & TEMP_STATUS_SENSERR));
+}
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp_input, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp_input, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp_input, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp_input, NULL, 4);
+static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, show_temp_input, NULL, 5);
+
+static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO, show_temp_min, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO, show_temp_min, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp3_min, S_IRUGO, show_temp_min, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp4_min, S_IRUGO, show_temp_min, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp5_min, S_IRUGO, show_temp_min, NULL, 4);
+static SENSOR_DEVICE_ATTR(temp6_min, S_IRUGO, show_temp_min, NULL, 5);
+
+static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_temp_max, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO, show_temp_max, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp3_max, S_IRUGO, show_temp_max, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp4_max, S_IRUGO, show_temp_max, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp5_max, S_IRUGO, show_temp_max, NULL, 4);
+static SENSOR_DEVICE_ATTR(temp6_max, S_IRUGO, show_temp_max, NULL, 5);
+
+static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_temp_crit, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO, show_temp_crit, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp3_crit, S_IRUGO, show_temp_crit, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp4_crit, S_IRUGO, show_temp_crit, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp5_crit, S_IRUGO, show_temp_crit, NULL, 4);
+static SENSOR_DEVICE_ATTR(temp6_crit, S_IRUGO, show_temp_crit, NULL, 5);
+
+static SENSOR_DEVICE_ATTR(temp1_type, S_IRUGO, show_temp_type, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_type, S_IRUGO, show_temp_type, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp3_type, S_IRUGO, show_temp_type, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp4_type, S_IRUGO, show_temp_type, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp5_type, S_IRUGO, show_temp_type, NULL, 4);
+static SENSOR_DEVICE_ATTR(temp6_type, S_IRUGO, show_temp_type, NULL, 5);
+
+static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO,
+                         show_temp_min_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO,
+                         show_temp_min_alarm, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO,
+                         show_temp_min_alarm, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO,
+                         show_temp_min_alarm, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp5_min_alarm, S_IRUGO,
+                         show_temp_min_alarm, NULL, 4);
+static SENSOR_DEVICE_ATTR(temp6_min_alarm, S_IRUGO,
+                         show_temp_min_alarm, NULL, 5);
+
+static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO,
+                         show_temp_max_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO,
+                         show_temp_max_alarm, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO,
+                         show_temp_max_alarm, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO,
+                         show_temp_max_alarm, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp5_max_alarm, S_IRUGO,
+                         show_temp_max_alarm, NULL, 4);
+static SENSOR_DEVICE_ATTR(temp6_max_alarm, S_IRUGO,
+                         show_temp_max_alarm, NULL, 5);
+
+static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO,
+                         show_temp_crit_alarm, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO,
+                         show_temp_crit_alarm, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp3_crit_alarm, S_IRUGO,
+                         show_temp_crit_alarm, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp4_crit_alarm, S_IRUGO,
+                         show_temp_crit_alarm, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp5_crit_alarm, S_IRUGO,
+                         show_temp_crit_alarm, NULL, 4);
+static SENSOR_DEVICE_ATTR(temp6_crit_alarm, S_IRUGO,
+                         show_temp_crit_alarm, NULL, 5);
+
+static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_temp_fault, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp5_fault, S_IRUGO, show_temp_fault, NULL, 4);
+static SENSOR_DEVICE_ATTR(temp6_fault, S_IRUGO, show_temp_fault, NULL, 5);
+
+static struct attribute *pc87427_attributes_temp[6][10] = {
+       {
+               &sensor_dev_attr_temp1_input.dev_attr.attr,
+               &sensor_dev_attr_temp1_min.dev_attr.attr,
+               &sensor_dev_attr_temp1_max.dev_attr.attr,
+               &sensor_dev_attr_temp1_crit.dev_attr.attr,
+               &sensor_dev_attr_temp1_type.dev_attr.attr,
+               &sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
+               &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
+               &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
+               &sensor_dev_attr_temp1_fault.dev_attr.attr,
+               NULL
+       }, {
+               &sensor_dev_attr_temp2_input.dev_attr.attr,
+               &sensor_dev_attr_temp2_min.dev_attr.attr,
+               &sensor_dev_attr_temp2_max.dev_attr.attr,
+               &sensor_dev_attr_temp2_crit.dev_attr.attr,
+               &sensor_dev_attr_temp2_type.dev_attr.attr,
+               &sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
+               &sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
+               &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,
+               &sensor_dev_attr_temp2_fault.dev_attr.attr,
+               NULL
+       }, {
+               &sensor_dev_attr_temp3_input.dev_attr.attr,
+               &sensor_dev_attr_temp3_min.dev_attr.attr,
+               &sensor_dev_attr_temp3_max.dev_attr.attr,
+               &sensor_dev_attr_temp3_crit.dev_attr.attr,
+               &sensor_dev_attr_temp3_type.dev_attr.attr,
+               &sensor_dev_attr_temp3_min_alarm.dev_attr.attr,
+               &sensor_dev_attr_temp3_max_alarm.dev_attr.attr,
+               &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr,
+               &sensor_dev_attr_temp3_fault.dev_attr.attr,
+               NULL
+       }, {
+               &sensor_dev_attr_temp4_input.dev_attr.attr,
+               &sensor_dev_attr_temp4_min.dev_attr.attr,
+               &sensor_dev_attr_temp4_max.dev_attr.attr,
+               &sensor_dev_attr_temp4_crit.dev_attr.attr,
+               &sensor_dev_attr_temp4_type.dev_attr.attr,
+               &sensor_dev_attr_temp4_min_alarm.dev_attr.attr,
+               &sensor_dev_attr_temp4_max_alarm.dev_attr.attr,
+               &sensor_dev_attr_temp4_crit_alarm.dev_attr.attr,
+               &sensor_dev_attr_temp4_fault.dev_attr.attr,
+               NULL
+       }, {
+               &sensor_dev_attr_temp5_input.dev_attr.attr,
+               &sensor_dev_attr_temp5_min.dev_attr.attr,
+               &sensor_dev_attr_temp5_max.dev_attr.attr,
+               &sensor_dev_attr_temp5_crit.dev_attr.attr,
+               &sensor_dev_attr_temp5_type.dev_attr.attr,
+               &sensor_dev_attr_temp5_min_alarm.dev_attr.attr,
+               &sensor_dev_attr_temp5_max_alarm.dev_attr.attr,
+               &sensor_dev_attr_temp5_crit_alarm.dev_attr.attr,
+               &sensor_dev_attr_temp5_fault.dev_attr.attr,
+               NULL
+       }, {
+               &sensor_dev_attr_temp6_input.dev_attr.attr,
+               &sensor_dev_attr_temp6_min.dev_attr.attr,
+               &sensor_dev_attr_temp6_max.dev_attr.attr,
+               &sensor_dev_attr_temp6_crit.dev_attr.attr,
+               &sensor_dev_attr_temp6_type.dev_attr.attr,
+               &sensor_dev_attr_temp6_min_alarm.dev_attr.attr,
+               &sensor_dev_attr_temp6_max_alarm.dev_attr.attr,
+               &sensor_dev_attr_temp6_crit_alarm.dev_attr.attr,
+               &sensor_dev_attr_temp6_fault.dev_attr.attr,
+               NULL
+       }
+};
+
+static const struct attribute_group pc87427_group_temp[6] = {
+       { .attrs = pc87427_attributes_temp[0] },
+       { .attrs = pc87427_attributes_temp[1] },
+       { .attrs = pc87427_attributes_temp[2] },
+       { .attrs = pc87427_attributes_temp[3] },
+       { .attrs = pc87427_attributes_temp[4] },
+       { .attrs = pc87427_attributes_temp[5] },
+};
+
 static ssize_t show_name(struct device *dev, struct device_attribute
                         *devattr, char *buf)
 {
@@ -391,8 +938,49 @@ static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
  * Device detection, attach and detach
  */
 
+static void pc87427_release_regions(struct platform_device *pdev, int count)
+{
+       struct resource *res;
+       int i;
+
+       for (i = 0; i < count; i++) {
+               res = platform_get_resource(pdev, IORESOURCE_IO, i);
+               release_region(res->start, resource_size(res));
+       }
+}
+
+static int __devinit pc87427_request_regions(struct platform_device *pdev,
+                                            int count)
+{
+       struct resource *res;
+       int i, err = 0;
+
+       for (i = 0; i < count; i++) {
+               res = platform_get_resource(pdev, IORESOURCE_IO, i);
+               if (!res) {
+                       err = -ENOENT;
+                       dev_err(&pdev->dev, "Missing resource #%d\n", i);
+                       break;
+               }
+               if (!request_region(res->start, resource_size(res), DRVNAME)) {
+                       err = -EBUSY;
+                       dev_err(&pdev->dev,
+                               "Failed to request region 0x%lx-0x%lx\n",
+                               (unsigned long)res->start,
+                               (unsigned long)res->end);
+                       break;
+               }
+       }
+
+       if (err && i)
+               pc87427_release_regions(pdev, i);
+
+       return err;
+}
+
 static void __devinit pc87427_init_device(struct device *dev)
 {
+       struct pc87427_sio_data *sio_data = dev->platform_data;
        struct pc87427_data *data = dev_get_drvdata(dev);
        int i;
        u8 reg;
@@ -400,10 +988,12 @@ static void __devinit pc87427_init_device(struct device *dev)
        /* The FMC module should be ready */
        reg = pc87427_read8(data, LD_FAN, PC87427_REG_BANK);
        if (!(reg & 0x80))
-               dev_warn(dev, "FMC module not ready!\n");
+               dev_warn(dev, "%s module not ready!\n", "FMC");
 
        /* Check which fans are enabled */
        for (i = 0; i < 8; i++) {
+               if (!(sio_data->has_fanin & (1 << i)))  /* Not wired */
+                       continue;
                reg = pc87427_read8_bank(data, LD_FAN, BANK_FM(i),
                                         PC87427_REG_FAN_STATUS);
                if (reg & FAN_STATUS_MONEN)
@@ -411,37 +1001,93 @@ static void __devinit pc87427_init_device(struct device *dev)
        }
 
        if (!data->fan_enabled) {
-               dev_dbg(dev, "Enabling all fan inputs\n");
-               for (i = 0; i < 8; i++)
+               dev_dbg(dev, "Enabling monitoring of all fans\n");
+               for (i = 0; i < 8; i++) {
+                       if (!(sio_data->has_fanin & (1 << i)))  /* Not wired */
+                               continue;
                        pc87427_write8_bank(data, LD_FAN, BANK_FM(i),
                                            PC87427_REG_FAN_STATUS,
                                            FAN_STATUS_MONEN);
-               data->fan_enabled = 0xff;
+               }
+               data->fan_enabled = sio_data->has_fanin;
+       }
+
+       /* Check which PWM outputs are enabled */
+       for (i = 0; i < 4; i++) {
+               if (!(sio_data->has_fanout & (1 << i))) /* Not wired */
+                       continue;
+               reg = pc87427_read8_bank(data, LD_FAN, BANK_FC(i),
+                                        PC87427_REG_PWM_ENABLE);
+               if (reg & PWM_ENABLE_CTLEN)
+                       data->pwm_enabled |= (1 << i);
+
+               /* We don't expose an interface to reconfigure the automatic
+                  fan control mode, so only allow to return to this mode if
+                  it was originally set. */
+               if ((reg & PWM_ENABLE_MODE_MASK) == PWM_MODE_AUTO) {
+                       dev_dbg(dev, "PWM%d is in automatic control mode\n",
+                               i + 1);
+                       data->pwm_auto_ok |= (1 << i);
+               }
+       }
+
+       /* The HMC module should be ready */
+       reg = pc87427_read8(data, LD_TEMP, PC87427_REG_BANK);
+       if (!(reg & 0x80))
+               dev_warn(dev, "%s module not ready!\n", "HMC");
+
+       /* Check which temperature channels are enabled */
+       for (i = 0; i < 6; i++) {
+               reg = pc87427_read8_bank(data, LD_TEMP, BANK_TM(i),
+                                        PC87427_REG_TEMP_STATUS);
+               if (reg & TEMP_STATUS_CHANEN)
+                       data->temp_enabled |= (1 << i);
+       }
+}
+
+static void pc87427_remove_files(struct device *dev)
+{
+       struct pc87427_data *data = dev_get_drvdata(dev);
+       int i;
+
+       device_remove_file(dev, &dev_attr_name);
+       for (i = 0; i < 8; i++) {
+               if (!(data->fan_enabled & (1 << i)))
+                       continue;
+               sysfs_remove_group(&dev->kobj, &pc87427_group_fan[i]);
+       }
+       for (i = 0; i < 4; i++) {
+               if (!(data->pwm_enabled & (1 << i)))
+                       continue;
+               sysfs_remove_group(&dev->kobj, &pc87427_group_pwm[i]);
+       }
+       for (i = 0; i < 6; i++) {
+               if (!(data->temp_enabled & (1 << i)))
+                       continue;
+               sysfs_remove_group(&dev->kobj, &pc87427_group_temp[i]);
        }
 }
 
 static int __devinit pc87427_probe(struct platform_device *pdev)
 {
+       struct pc87427_sio_data *sio_data = pdev->dev.platform_data;
        struct pc87427_data *data;
-       struct resource *res;
-       int i, err;
+       int i, err, res_count;
 
-       if (!(data = kzalloc(sizeof(struct pc87427_data), GFP_KERNEL))) {
+       data = kzalloc(sizeof(struct pc87427_data), GFP_KERNEL);
+       if (!data) {
                err = -ENOMEM;
                printk(KERN_ERR DRVNAME ": Out of memory\n");
                goto exit;
        }
 
-       /* This will need to be revisited when we add support for
-          temperature and voltage monitoring. */
-       res = platform_get_resource(pdev, IORESOURCE_IO, 0);
-       if (!request_region(res->start, resource_size(res), DRVNAME)) {
-               err = -EBUSY;
-               dev_err(&pdev->dev, "Failed to request region 0x%lx-0x%lx\n",
-                       (unsigned long)res->start, (unsigned long)res->end);
+       data->address[0] = sio_data->address[0];
+       data->address[1] = sio_data->address[1];
+       res_count = (data->address[0] != 0) + (data->address[1] != 0);
+
+       err = pc87427_request_regions(pdev, res_count);
+       if (err)
                goto exit_kfree;
-       }
-       data->address[0] = res->start;
 
        mutex_init(&data->lock);
        data->name = "pc87427";
@@ -449,13 +1095,31 @@ static int __devinit pc87427_probe(struct platform_device *pdev)
        pc87427_init_device(&pdev->dev);
 
        /* Register sysfs hooks */
-       if ((err = device_create_file(&pdev->dev, &dev_attr_name)))
+       err = device_create_file(&pdev->dev, &dev_attr_name);
+       if (err)
                goto exit_release_region;
        for (i = 0; i < 8; i++) {
                if (!(data->fan_enabled & (1 << i)))
                        continue;
-               if ((err = sysfs_create_group(&pdev->dev.kobj,
-                                             &pc87427_group_fan[i])))
+               err = sysfs_create_group(&pdev->dev.kobj,
+                                        &pc87427_group_fan[i]);
+               if (err)
+                       goto exit_remove_files;
+       }
+       for (i = 0; i < 4; i++) {
+               if (!(data->pwm_enabled & (1 << i)))
+                       continue;
+               err = sysfs_create_group(&pdev->dev.kobj,
+                                        &pc87427_group_pwm[i]);
+               if (err)
+                       goto exit_remove_files;
+       }
+       for (i = 0; i < 6; i++) {
+               if (!(data->temp_enabled & (1 << i)))
+                       continue;
+               err = sysfs_create_group(&pdev->dev.kobj,
+                                        &pc87427_group_temp[i]);
+               if (err)
                        goto exit_remove_files;
        }
 
@@ -469,13 +1133,9 @@ static int __devinit pc87427_probe(struct platform_device *pdev)
        return 0;
 
 exit_remove_files:
-       for (i = 0; i < 8; i++) {
-               if (!(data->fan_enabled & (1 << i)))
-                       continue;
-               sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]);
-       }
+       pc87427_remove_files(&pdev->dev);
 exit_release_region:
-       release_region(res->start, resource_size(res));
+       pc87427_release_regions(pdev, res_count);
 exit_kfree:
        platform_set_drvdata(pdev, NULL);
        kfree(data);
@@ -486,21 +1146,16 @@ exit:
 static int __devexit pc87427_remove(struct platform_device *pdev)
 {
        struct pc87427_data *data = platform_get_drvdata(pdev);
-       struct resource *res;
-       int i;
+       int res_count;
+
+       res_count = (data->address[0] != 0) + (data->address[1] != 0);
 
        hwmon_device_unregister(data->hwmon_dev);
-       device_remove_file(&pdev->dev, &dev_attr_name);
-       for (i = 0; i < 8; i++) {
-               if (!(data->fan_enabled & (1 << i)))
-                       continue;
-               sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]);
-       }
+       pc87427_remove_files(&pdev->dev);
        platform_set_drvdata(pdev, NULL);
        kfree(data);
 
-       res = platform_get_resource(pdev, IORESOURCE_IO, 0);
-       release_region(res->start, resource_size(res));
+       pc87427_release_regions(pdev, res_count);
 
        return 0;
 }
@@ -515,34 +1170,50 @@ static struct platform_driver pc87427_driver = {
        .remove         = __devexit_p(pc87427_remove),
 };
 
-static int __init pc87427_device_add(unsigned short address)
+static int __init pc87427_device_add(const struct pc87427_sio_data *sio_data)
 {
-       struct resource res = {
-               .start  = address,
-               .end    = address + REGION_LENGTH - 1,
-               .name   = logdev_str[0],
-               .flags  = IORESOURCE_IO,
+       struct resource res[2] = {
+               { .flags        = IORESOURCE_IO },
+               { .flags        = IORESOURCE_IO },
        };
-       int err;
+       int err, i, res_count;
 
-       err = acpi_check_resource_conflict(&res);
-       if (err)
-               goto exit;
+       res_count = 0;
+       for (i = 0; i < 2; i++) {
+               if (!sio_data->address[i])
+                       continue;
+               res[res_count].start = sio_data->address[i];
+               res[res_count].end = sio_data->address[i] + REGION_LENGTH - 1;
+               res[res_count].name = logdev_str[i];
 
-       pdev = platform_device_alloc(DRVNAME, address);
+               err = acpi_check_resource_conflict(&res[res_count]);
+               if (err)
+                       goto exit;
+
+               res_count++;
+       }
+
+       pdev = platform_device_alloc(DRVNAME, res[0].start);
        if (!pdev) {
                err = -ENOMEM;
                printk(KERN_ERR DRVNAME ": Device allocation failed\n");
                goto exit;
        }
 
-       err = platform_device_add_resources(pdev, &res, 1);
+       err = platform_device_add_resources(pdev, res, res_count);
        if (err) {
                printk(KERN_ERR DRVNAME ": Device resource addition failed "
                       "(%d)\n", err);
                goto exit_device_put;
        }
 
+       err = platform_device_add_data(pdev, sio_data,
+                                      sizeof(struct pc87427_sio_data));
+       if (err) {
+               printk(KERN_ERR DRVNAME ": Platform data allocation failed\n");
+               goto exit_device_put;
+       }
+
        err = platform_device_add(pdev);
        if (err) {
                printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n",
@@ -558,9 +1229,10 @@ exit:
        return err;
 }
 
-static int __init pc87427_find(int sioaddr, unsigned short *address)
+static int __init pc87427_find(int sioaddr, struct pc87427_sio_data *sio_data)
 {
        u16 val;
+       u8 cfg, cfg_b;
        int i, err = 0;
 
        /* Identify device */
@@ -571,7 +1243,7 @@ static int __init pc87427_find(int sioaddr, unsigned short *address)
        }
 
        for (i = 0; i < 2; i++) {
-               address[i] = 0;
+               sio_data->address[i] = 0;
                /* Select logical device */
                superio_outb(sioaddr, SIOREG_LDSEL, logdev[i]);
 
@@ -596,9 +1268,58 @@ static int __init pc87427_find(int sioaddr, unsigned short *address)
                               "for logical device 0x%02x\n", logdev[i]);
                        continue;
                }
-               address[i] = val;
+               sio_data->address[i] = val;
        }
 
+       /* No point in loading the driver if everything is disabled */
+       if (!sio_data->address[0] && !sio_data->address[1]) {
+               err = -ENODEV;
+               goto exit;
+       }
+
+       /* Check which fan inputs are wired */
+       sio_data->has_fanin = (1 << 2) | (1 << 3);      /* FANIN2, FANIN3 */
+
+       cfg = superio_inb(sioaddr, SIOREG_CF2);
+       if (!(cfg & (1 << 3)))
+               sio_data->has_fanin |= (1 << 0);        /* FANIN0 */
+       if (!(cfg & (1 << 2)))
+               sio_data->has_fanin |= (1 << 4);        /* FANIN4 */
+
+       cfg = superio_inb(sioaddr, SIOREG_CFD);
+       if (!(cfg & (1 << 0)))
+               sio_data->has_fanin |= (1 << 1);        /* FANIN1 */
+
+       cfg = superio_inb(sioaddr, SIOREG_CF4);
+       if (!(cfg & (1 << 0)))
+               sio_data->has_fanin |= (1 << 7);        /* FANIN7 */
+       cfg_b = superio_inb(sioaddr, SIOREG_CFB);
+       if (!(cfg & (1 << 1)) && (cfg_b & (1 << 3)))
+               sio_data->has_fanin |= (1 << 5);        /* FANIN5 */
+       cfg = superio_inb(sioaddr, SIOREG_CF3);
+       if ((cfg & (1 << 3)) && !(cfg_b & (1 << 5)))
+               sio_data->has_fanin |= (1 << 6);        /* FANIN6 */
+
+       /* Check which fan outputs are wired */
+       sio_data->has_fanout = (1 << 0);                /* FANOUT0 */
+       if (cfg_b & (1 << 0))
+               sio_data->has_fanout |= (1 << 3);       /* FANOUT3 */
+
+       cfg = superio_inb(sioaddr, SIOREG_CFC);
+       if (!(cfg & (1 << 4))) {
+               if (cfg_b & (1 << 1))
+                       sio_data->has_fanout |= (1 << 1); /* FANOUT1 */
+               if (cfg_b & (1 << 2))
+                       sio_data->has_fanout |= (1 << 2); /* FANOUT2 */
+       }
+
+       /* FANOUT1 and FANOUT2 can each be routed to 2 different pins */
+       cfg = superio_inb(sioaddr, SIOREG_CF5);
+       if (cfg & (1 << 6))
+               sio_data->has_fanout |= (1 << 1);       /* FANOUT1 */
+       if (cfg & (1 << 5))
+               sio_data->has_fanout |= (1 << 2);       /* FANOUT2 */
+
 exit:
        superio_exit(sioaddr);
        return err;
@@ -607,15 +1328,10 @@ exit:
 static int __init pc87427_init(void)
 {
        int err;
-       unsigned short address[2];
-
-       if (pc87427_find(0x2e, address)
-        && pc87427_find(0x4e, address))
-               return -ENODEV;
+       struct pc87427_sio_data sio_data;
 
-       /* For now the driver only handles fans so we only care about the
-          first address. */
-       if (!address[0])
+       if (pc87427_find(0x2e, &sio_data)
+        && pc87427_find(0x4e, &sio_data))
                return -ENODEV;
 
        err = platform_driver_register(&pc87427_driver);
@@ -623,7 +1339,7 @@ static int __init pc87427_init(void)
                goto exit;
 
        /* Sets global pdev as a side effect */
-       err = pc87427_device_add(address[0]);
+       err = pc87427_device_add(&sio_data);
        if (err)
                goto exit_driver;
 
index 7442cf7548563841765631f588cdfc480ef7ff42..ffb793af680b7cf378e6e1cf63c5e9bfc7c378c7 100644 (file)
@@ -39,7 +39,7 @@
 
 #define DRVNAME        "via_cputemp"
 
-enum { SHOW_TEMP, SHOW_LABEL, SHOW_NAME } SHOW;
+enum { SHOW_TEMP, SHOW_LABEL, SHOW_NAME };
 
 /*
  * Functions declaration
index 0dcaba9b7189c26cd242a2acfb72804e083a7f70..e96e69dd36fb4b4faba43ffad13b53815d06bc65 100644 (file)
@@ -39,6 +39,7 @@
     w83627dhg    9      5       4       3      0xa020 0xc1    0x5ca3
     w83627dhg-p  9      5       4       3      0xb070 0xc1    0x5ca3
     w83667hg     9      5       3       3      0xa510 0xc1    0x5ca3
+    w83667hg-b   9      5       3       3      0xb350 0xc1    0x5ca3
 */
 
 #include <linux/module.h>
@@ -55,7 +56,7 @@
 #include <linux/io.h>
 #include "lm75.h"
 
-enum kinds { w83627ehf, w83627dhg, w83627dhg_p, w83667hg };
+enum kinds { w83627ehf, w83627dhg, w83627dhg_p, w83667hg, w83667hg_b };
 
 /* used to set data->name = w83627ehf_device_names[data->sio_kind] */
 static const char * w83627ehf_device_names[] = {
@@ -63,6 +64,7 @@ static const char * w83627ehf_device_names[] = {
        "w83627dhg",
        "w83627dhg",
        "w83667hg",
+       "w83667hg",
 };
 
 static unsigned short force_id;
@@ -91,6 +93,7 @@ MODULE_PARM_DESC(force_id, "Override the detected device ID");
 #define SIO_W83627DHG_ID       0xa020
 #define SIO_W83627DHG_P_ID     0xb070
 #define SIO_W83667HG_ID        0xa510
+#define SIO_W83667HG_B_ID      0xb350
 #define SIO_ID_MASK            0xFFF0
 
 static inline void
@@ -201,8 +204,14 @@ static const u8 W83627EHF_REG_TOLERANCE[] = { 0x07, 0x07, 0x14, 0x62 };
 static const u8 W83627EHF_REG_FAN_START_OUTPUT[] = { 0x0a, 0x0b, 0x16, 0x65 };
 static const u8 W83627EHF_REG_FAN_STOP_OUTPUT[] = { 0x08, 0x09, 0x15, 0x64 };
 static const u8 W83627EHF_REG_FAN_STOP_TIME[] = { 0x0c, 0x0d, 0x17, 0x66 };
-static const u8 W83627EHF_REG_FAN_MAX_OUTPUT[] = { 0xff, 0x67, 0xff, 0x69 };
-static const u8 W83627EHF_REG_FAN_STEP_OUTPUT[] = { 0xff, 0x68, 0xff, 0x6a };
+
+static const u8 W83627EHF_REG_FAN_MAX_OUTPUT_COMMON[]
+                                               = { 0xff, 0x67, 0xff, 0x69 };
+static const u8 W83627EHF_REG_FAN_STEP_OUTPUT_COMMON[]
+                                               = { 0xff, 0x68, 0xff, 0x6a };
+
+static const u8 W83627EHF_REG_FAN_MAX_OUTPUT_W83667_B[] = { 0x67, 0x69, 0x6b };
+static const u8 W83627EHF_REG_FAN_STEP_OUTPUT_W83667_B[] = { 0x68, 0x6a, 0x6c };
 
 /*
  * Conversions
@@ -277,6 +286,11 @@ struct w83627ehf_data {
        struct device *hwmon_dev;
        struct mutex lock;
 
+       const u8 *REG_FAN_START_OUTPUT;
+       const u8 *REG_FAN_STOP_OUTPUT;
+       const u8 *REG_FAN_MAX_OUTPUT;
+       const u8 *REG_FAN_STEP_OUTPUT;
+
        struct mutex update_lock;
        char valid;             /* !=0 if following fields are valid */
        unsigned long last_updated;     /* In jiffies */
@@ -524,7 +538,10 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev)
                        }
                }
 
-               for (i = 0; i < 4; i++) {
+               for (i = 0; i < data->pwm_num; i++) {
+                       if (!(data->has_fan & (1 << i)))
+                               continue;
+
                        /* pwmcfg, tolerance mapped for i=0, i=1 to same reg */
                        if (i != 1) {
                                pwmcfg = w83627ehf_read_value(data,
@@ -546,6 +563,17 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev)
                                                W83627EHF_REG_FAN_STOP_OUTPUT[i]);
                        data->fan_stop_time[i] = w83627ehf_read_value(data,
                                                W83627EHF_REG_FAN_STOP_TIME[i]);
+
+                       if (data->REG_FAN_MAX_OUTPUT[i] != 0xff)
+                               data->fan_max_output[i] =
+                                 w83627ehf_read_value(data,
+                                              data->REG_FAN_MAX_OUTPUT[i]);
+
+                       if (data->REG_FAN_STEP_OUTPUT[i] != 0xff)
+                               data->fan_step_output[i] =
+                                 w83627ehf_read_value(data,
+                                              data->REG_FAN_STEP_OUTPUT[i]);
+
                        data->target_temp[i] =
                                w83627ehf_read_value(data,
                                        W83627EHF_REG_TARGET[i]) &
@@ -1126,7 +1154,7 @@ store_##reg(struct device *dev, struct device_attribute *attr, \
        u32 val = SENSORS_LIMIT(simple_strtoul(buf, NULL, 10), 1, 255); \
        mutex_lock(&data->update_lock); \
        data->reg[nr] = val; \
-       w83627ehf_write_value(data, W83627EHF_REG_##REG[nr], val); \
+       w83627ehf_write_value(data, data->REG_##REG[nr], val); \
        mutex_unlock(&data->update_lock); \
        return count; \
 }
@@ -1206,12 +1234,26 @@ static struct sensor_device_attribute sda_sf3_arrays[] = {
                    store_fan_stop_output, 1),
        SENSOR_ATTR(pwm3_stop_output, S_IWUSR | S_IRUGO, show_fan_stop_output,
                    store_fan_stop_output, 2),
+};
 
-       /* pwm1 and pwm3 don't support max and step settings */
+
+/*
+ * pwm1 and pwm3 don't support max and step settings on all chips.
+ * Need to check support while generating/removing attribute files.
+ */
+static struct sensor_device_attribute sda_sf3_max_step_arrays[] = {
+       SENSOR_ATTR(pwm1_max_output, S_IWUSR | S_IRUGO, show_fan_max_output,
+                   store_fan_max_output, 0),
+       SENSOR_ATTR(pwm1_step_output, S_IWUSR | S_IRUGO, show_fan_step_output,
+                   store_fan_step_output, 0),
        SENSOR_ATTR(pwm2_max_output, S_IWUSR | S_IRUGO, show_fan_max_output,
                    store_fan_max_output, 1),
        SENSOR_ATTR(pwm2_step_output, S_IWUSR | S_IRUGO, show_fan_step_output,
                    store_fan_step_output, 1),
+       SENSOR_ATTR(pwm3_max_output, S_IWUSR | S_IRUGO, show_fan_max_output,
+                   store_fan_max_output, 2),
+       SENSOR_ATTR(pwm3_step_output, S_IWUSR | S_IRUGO, show_fan_step_output,
+                   store_fan_step_output, 2),
 };
 
 static ssize_t
@@ -1235,6 +1277,12 @@ static void w83627ehf_device_remove_files(struct device *dev)
 
        for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays); i++)
                device_remove_file(dev, &sda_sf3_arrays[i].dev_attr);
+       for (i = 0; i < ARRAY_SIZE(sda_sf3_max_step_arrays); i++) {
+               struct sensor_device_attribute *attr =
+                 &sda_sf3_max_step_arrays[i];
+               if (data->REG_FAN_STEP_OUTPUT[attr->index] != 0xff)
+                       device_remove_file(dev, &attr->dev_attr);
+       }
        for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays_fan4); i++)
                device_remove_file(dev, &sda_sf3_arrays_fan4[i].dev_attr);
        for (i = 0; i < data->in_num; i++) {
@@ -1343,22 +1391,37 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev)
        /* 627EHG and 627EHF have 10 voltage inputs; 627DHG and 667HG have 9 */
        data->in_num = (sio_data->kind == w83627ehf) ? 10 : 9;
        /* 667HG has 3 pwms */
-       data->pwm_num = (sio_data->kind == w83667hg) ? 3 : 4;
+       data->pwm_num = (sio_data->kind == w83667hg
+                        || sio_data->kind == w83667hg_b) ? 3 : 4;
 
        /* Check temp3 configuration bit for 667HG */
-       if (sio_data->kind == w83667hg) {
+       if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) {
                data->temp3_disable = w83627ehf_read_value(data,
                                        W83627EHF_REG_TEMP_CONFIG[1]) & 0x01;
                data->in6_skip = !data->temp3_disable;
        }
 
+       data->REG_FAN_START_OUTPUT = W83627EHF_REG_FAN_START_OUTPUT;
+       data->REG_FAN_STOP_OUTPUT = W83627EHF_REG_FAN_STOP_OUTPUT;
+       if (sio_data->kind == w83667hg_b) {
+               data->REG_FAN_MAX_OUTPUT =
+                 W83627EHF_REG_FAN_MAX_OUTPUT_W83667_B;
+               data->REG_FAN_STEP_OUTPUT =
+                 W83627EHF_REG_FAN_STEP_OUTPUT_W83667_B;
+       } else {
+               data->REG_FAN_MAX_OUTPUT =
+                 W83627EHF_REG_FAN_MAX_OUTPUT_COMMON;
+               data->REG_FAN_STEP_OUTPUT =
+                 W83627EHF_REG_FAN_STEP_OUTPUT_COMMON;
+       }
+
        /* Initialize the chip */
        w83627ehf_init_device(data);
 
        data->vrm = vid_which_vrm();
        superio_enter(sio_data->sioreg);
        /* Read VID value */
-       if (sio_data->kind == w83667hg) {
+       if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) {
                /* W83667HG has different pins for VID input and output, so
                we can get the VID input values directly at logical device D
                0xe3. */
@@ -1409,7 +1472,7 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev)
        }
 
        /* fan4 and fan5 share some pins with the GPIO and serial flash */
-       if (sio_data->kind == w83667hg) {
+       if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) {
                fan5pin = superio_inb(sio_data->sioreg, 0x27) & 0x20;
                fan4pin = superio_inb(sio_data->sioreg, 0x27) & 0x40;
        } else {
@@ -1440,6 +1503,15 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev)
                        &sda_sf3_arrays[i].dev_attr)))
                        goto exit_remove;
 
+       for (i = 0; i < ARRAY_SIZE(sda_sf3_max_step_arrays); i++) {
+               struct sensor_device_attribute *attr =
+                 &sda_sf3_max_step_arrays[i];
+               if (data->REG_FAN_STEP_OUTPUT[attr->index] != 0xff) {
+                       err = device_create_file(dev, &attr->dev_attr);
+                       if (err)
+                               goto exit_remove;
+               }
+       }
        /* if fan4 is enabled create the sf3 files for it */
        if ((data->has_fan & (1 << 3)) && data->pwm_num >= 4)
                for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays_fan4); i++) {
@@ -1556,6 +1628,7 @@ static int __init w83627ehf_find(int sioaddr, unsigned short *addr,
        static const char __initdata sio_name_W83627DHG[] = "W83627DHG";
        static const char __initdata sio_name_W83627DHG_P[] = "W83627DHG-P";
        static const char __initdata sio_name_W83667HG[] = "W83667HG";
+       static const char __initdata sio_name_W83667HG_B[] = "W83667HG-B";
 
        u16 val;
        const char *sio_name;
@@ -1588,6 +1661,10 @@ static int __init w83627ehf_find(int sioaddr, unsigned short *addr,
                sio_data->kind = w83667hg;
                sio_name = sio_name_W83667HG;
                break;
+       case SIO_W83667HG_B_ID:
+               sio_data->kind = w83667hg_b;
+               sio_name = sio_name_W83667HG_B;
+               break;
        default:
                if (val != 0xffff)
                        pr_debug(DRVNAME ": unsupported chip ID: 0x%04x\n",
diff --git a/include/linux/i2c/ltc4245.h b/include/linux/i2c/ltc4245.h
new file mode 100644 (file)
index 0000000..56bda4b
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * Platform Data for LTC4245 hardware monitor chip
+ *
+ * Copyright (c) 2010 Ira W. Snyder <iws@ovro.caltech.edu>
+ *
+ * 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.
+ */
+
+#ifndef LINUX_LTC4245_H
+#define LINUX_LTC4245_H
+
+#include <linux/types.h>
+
+struct ltc4245_platform_data {
+       bool use_extra_gpios;
+};
+
+#endif /* LINUX_LTC4245_H */