hwmon: (f71882fg) Add PWM support
Mark van Doesburg [Wed, 7 Jan 2009 15:37:27 +0000 (16:37 +0100)]
Add PWM (fan speed control) support to the f71882fg driver. Both
manual control and automatic (temperature-based) modes are supported.
Additionally, each mode has a PWM-based and an RPM-based variant. By
default we use the mode set by the BIOS.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Cc: Mark van Doesburg <mark.vandoesburg@hetnet.nl>
Signed-off-by: Jean Delvare <khali@linux-fr.org>

drivers/hwmon/f71882fg.c

index c6fa857..c041087 100644 (file)
@@ -57,6 +57,8 @@
 #define F71882FG_REG_IN1_HIGH          0x32
 
 #define F71882FG_REG_FAN(nr)           (0xA0 + (16 * (nr)))
+#define F71882FG_REG_FAN_TARGET(nr)    (0xA2 + (16 * (nr)))
+#define F71882FG_REG_FAN_FULL_SPEED(nr)        (0xA4 + (16 * (nr)))
 #define F71882FG_REG_FAN_STATUS                0x92
 #define F71882FG_REG_FAN_BEEP          0x93
 
 #define F71882FG_REG_TEMP_TYPE         0x6B
 #define F71882FG_REG_TEMP_DIODE_OPEN   0x6F
 
+#define F71882FG_REG_PWM(nr)           (0xA3 + (16 * (nr)))
+#define F71882FG_REG_PWM_TYPE          0x94
+#define F71882FG_REG_PWM_ENABLE                0x96
+
+#define F71882FG_REG_FAN_HYST0         0x98
+#define F71882FG_REG_FAN_HYST1         0x99
+
+#define F71882FG_REG_POINT_PWM(pwm, point)     (0xAA + (point) + (16 * (pwm)))
+#define F71882FG_REG_POINT_TEMP(pwm, point)    (0xA6 + (point) + (16 * (pwm)))
+#define F71882FG_REG_POINT_MAPPING(nr)         (0xAF + 16 * (nr))
+
 #define        F71882FG_REG_START              0x01
 
 #define FAN_MIN_DETECT                 366 /* Lowest detectable fanspeed */
@@ -78,6 +91,12 @@ static unsigned short force_id;
 module_param(force_id, ushort, 0);
 MODULE_PARM_DESC(force_id, "Override the detected device ID");
 
+static int fan_mode[4] = { 0, 0, 0, 0 };
+module_param_array(fan_mode, int, NULL, 0644);
+MODULE_PARM_DESC(fan_mode, "List of fan control modes (f71882fg only) "
+                "(0=don't change, 1=pwm, 2=rpm)\n"
+                "Note: this needs a write to pwm#_enable to take effect");
+
 static struct platform_device *f71882fg_pdev;
 
 /* Super-I/O Function prototypes */
@@ -102,6 +121,8 @@ struct f71882fg_data {
        u8      in_status;
        u8      in_beep;
        u16     fan[4];
+       u16     fan_target[4];
+       u16     fan_full_speed[4];
        u8      fan_status;
        u8      fan_beep;
        u8      temp[3];
@@ -112,6 +133,12 @@ struct f71882fg_data {
        u8      temp_status;
        u8      temp_beep;
        u8      temp_diode_open;
+       u8      pwm[4];
+       u8      pwm_enable;
+       u8      pwm_auto_point_hyst[2];
+       u8      pwm_auto_point_mapping[4];
+       u8      pwm_auto_point_pwm[4][5];
+       u8      pwm_auto_point_temp[4][4];
 };
 
 /* Sysfs in */
@@ -130,6 +157,10 @@ static ssize_t show_in_alarm(struct device *dev, struct device_attribute
 /* Sysfs Fan */
 static ssize_t show_fan(struct device *dev, struct device_attribute *devattr,
        char *buf);
+static ssize_t show_fan_full_speed(struct device *dev,
+       struct device_attribute *devattr, char *buf);
+static ssize_t store_fan_full_speed(struct device *dev,
+       struct device_attribute *devattr, const char *buf, size_t count);
 static ssize_t show_fan_beep(struct device *dev, struct device_attribute
        *devattr, char *buf);
 static ssize_t store_fan_beep(struct device *dev, struct device_attribute
@@ -163,6 +194,35 @@ static ssize_t show_temp_alarm(struct device *dev, struct device_attribute
        *devattr, char *buf);
 static ssize_t show_temp_fault(struct device *dev, struct device_attribute
        *devattr, char *buf);
+/* PWM and Auto point control */
+static ssize_t show_pwm(struct device *dev, struct device_attribute *devattr,
+       char *buf);
+static ssize_t store_pwm(struct device *dev, struct device_attribute *devattr,
+       const char *buf, size_t count);
+static ssize_t show_pwm_enable(struct device *dev,
+       struct device_attribute *devattr, char *buf);
+static ssize_t store_pwm_enable(struct device *dev,
+       struct device_attribute *devattr, const char *buf, size_t count);
+static ssize_t show_pwm_interpolate(struct device *dev,
+       struct device_attribute *devattr, char *buf);
+static ssize_t store_pwm_interpolate(struct device *dev,
+       struct device_attribute *devattr, const char *buf, size_t count);
+static ssize_t show_pwm_auto_point_channel(struct device *dev,
+       struct device_attribute *devattr, char *buf);
+static ssize_t store_pwm_auto_point_channel(struct device *dev,
+       struct device_attribute *devattr, const char *buf, size_t count);
+static ssize_t show_pwm_auto_point_temp_hyst(struct device *dev,
+       struct device_attribute *devattr, char *buf);
+static ssize_t store_pwm_auto_point_temp_hyst(struct device *dev,
+       struct device_attribute *devattr, const char *buf, size_t count);
+static ssize_t show_pwm_auto_point_pwm(struct device *dev,
+       struct device_attribute *devattr, char *buf);
+static ssize_t store_pwm_auto_point_pwm(struct device *dev,
+       struct device_attribute *devattr, const char *buf, size_t count);
+static ssize_t show_pwm_auto_point_temp(struct device *dev,
+       struct device_attribute *devattr, char *buf);
+static ssize_t store_pwm_auto_point_temp(struct device *dev,
+       struct device_attribute *devattr, const char *buf, size_t count);
 /* Sysfs misc */
 static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
        char *buf);
@@ -249,21 +309,217 @@ static struct sensor_device_attribute_2 f71882fg_in_temp_attr[] = {
 
 static struct sensor_device_attribute_2 f71882fg_fan_attr[] = {
        SENSOR_ATTR_2(fan1_input, S_IRUGO, show_fan, NULL, 0, 0),
+       SENSOR_ATTR_2(fan1_full_speed, S_IRUGO|S_IWUSR,
+                     show_fan_full_speed,
+                     store_fan_full_speed, 0, 0),
        SENSOR_ATTR_2(fan1_beep, S_IRUGO|S_IWUSR, show_fan_beep,
                store_fan_beep, 0, 0),
        SENSOR_ATTR_2(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0, 0),
        SENSOR_ATTR_2(fan2_input, S_IRUGO, show_fan, NULL, 0, 1),
+       SENSOR_ATTR_2(fan2_full_speed, S_IRUGO|S_IWUSR,
+                     show_fan_full_speed,
+                     store_fan_full_speed, 0, 1),
        SENSOR_ATTR_2(fan2_beep, S_IRUGO|S_IWUSR, show_fan_beep,
                store_fan_beep, 0, 1),
        SENSOR_ATTR_2(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 0, 1),
        SENSOR_ATTR_2(fan3_input, S_IRUGO, show_fan, NULL, 0, 2),
+       SENSOR_ATTR_2(fan3_full_speed, S_IRUGO|S_IWUSR,
+                     show_fan_full_speed,
+                     store_fan_full_speed, 0, 2),
        SENSOR_ATTR_2(fan3_beep, S_IRUGO|S_IWUSR, show_fan_beep,
                store_fan_beep, 0, 2),
        SENSOR_ATTR_2(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 0, 2),
        SENSOR_ATTR_2(fan4_input, S_IRUGO, show_fan, NULL, 0, 3),
+       SENSOR_ATTR_2(fan4_full_speed, S_IRUGO|S_IWUSR,
+                     show_fan_full_speed,
+                     store_fan_full_speed, 0, 3),
        SENSOR_ATTR_2(fan4_beep, S_IRUGO|S_IWUSR, show_fan_beep,
                store_fan_beep, 0, 3),
        SENSOR_ATTR_2(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 0, 3),
+
+       SENSOR_ATTR_2(pwm1, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0, 0),
+       SENSOR_ATTR_2(pwm1_enable, S_IRUGO|S_IWUSR, show_pwm_enable,
+                     store_pwm_enable, 0, 0),
+       SENSOR_ATTR_2(pwm1_interpolate, S_IRUGO|S_IWUSR,
+                     show_pwm_interpolate, store_pwm_interpolate, 0, 0),
+       SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_channel,
+                     store_pwm_auto_point_channel, 0, 0),
+       SENSOR_ATTR_2(pwm1_auto_point1_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     0, 0),
+       SENSOR_ATTR_2(pwm1_auto_point2_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     1, 0),
+       SENSOR_ATTR_2(pwm1_auto_point3_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     2, 0),
+       SENSOR_ATTR_2(pwm1_auto_point4_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     3, 0),
+       SENSOR_ATTR_2(pwm1_auto_point5_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     4, 0),
+       SENSOR_ATTR_2(pwm1_auto_point1_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp, store_pwm_auto_point_temp,
+                     0, 0),
+       SENSOR_ATTR_2(pwm1_auto_point2_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp, store_pwm_auto_point_temp,
+                     1, 0),
+       SENSOR_ATTR_2(pwm1_auto_point3_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp, store_pwm_auto_point_temp,
+                     2, 0),
+       SENSOR_ATTR_2(pwm1_auto_point4_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp, store_pwm_auto_point_temp,
+                     3, 0),
+       SENSOR_ATTR_2(pwm1_auto_point1_temp_hyst, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp_hyst,
+                     store_pwm_auto_point_temp_hyst,
+                     0, 0),
+       SENSOR_ATTR_2(pwm1_auto_point2_temp_hyst, S_IRUGO,
+                     show_pwm_auto_point_temp_hyst, NULL, 1, 0),
+       SENSOR_ATTR_2(pwm1_auto_point3_temp_hyst, S_IRUGO,
+                     show_pwm_auto_point_temp_hyst, NULL, 2, 0),
+       SENSOR_ATTR_2(pwm1_auto_point4_temp_hyst, S_IRUGO,
+                     show_pwm_auto_point_temp_hyst, NULL, 3, 0),
+
+       SENSOR_ATTR_2(pwm2, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0, 1),
+       SENSOR_ATTR_2(pwm2_enable, S_IRUGO|S_IWUSR, show_pwm_enable,
+                     store_pwm_enable, 0, 1),
+       SENSOR_ATTR_2(pwm2_interpolate, S_IRUGO|S_IWUSR,
+                     show_pwm_interpolate, store_pwm_interpolate, 0, 1),
+       SENSOR_ATTR_2(pwm2_auto_channels_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_channel,
+                     store_pwm_auto_point_channel, 0, 1),
+       SENSOR_ATTR_2(pwm2_auto_point1_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     0, 1),
+       SENSOR_ATTR_2(pwm2_auto_point2_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     1, 1),
+       SENSOR_ATTR_2(pwm2_auto_point3_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     2, 1),
+       SENSOR_ATTR_2(pwm2_auto_point4_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     3, 1),
+       SENSOR_ATTR_2(pwm2_auto_point5_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     4, 1),
+       SENSOR_ATTR_2(pwm2_auto_point1_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp, store_pwm_auto_point_temp,
+                     0, 1),
+       SENSOR_ATTR_2(pwm2_auto_point2_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp, store_pwm_auto_point_temp,
+                     1, 1),
+       SENSOR_ATTR_2(pwm2_auto_point3_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp, store_pwm_auto_point_temp,
+                     2, 1),
+       SENSOR_ATTR_2(pwm2_auto_point4_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp, store_pwm_auto_point_temp,
+                     3, 1),
+       SENSOR_ATTR_2(pwm2_auto_point1_temp_hyst, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp_hyst,
+                     store_pwm_auto_point_temp_hyst,
+                     0, 1),
+       SENSOR_ATTR_2(pwm2_auto_point2_temp_hyst, S_IRUGO,
+                     show_pwm_auto_point_temp_hyst, NULL, 1, 1),
+       SENSOR_ATTR_2(pwm2_auto_point3_temp_hyst, S_IRUGO,
+                     show_pwm_auto_point_temp_hyst, NULL, 2, 1),
+       SENSOR_ATTR_2(pwm2_auto_point4_temp_hyst, S_IRUGO,
+                     show_pwm_auto_point_temp_hyst, NULL, 3, 1),
+
+       SENSOR_ATTR_2(pwm3, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0, 2),
+       SENSOR_ATTR_2(pwm3_enable, S_IRUGO|S_IWUSR, show_pwm_enable,
+                     store_pwm_enable, 0, 2),
+       SENSOR_ATTR_2(pwm3_interpolate, S_IRUGO|S_IWUSR,
+                     show_pwm_interpolate, store_pwm_interpolate, 0, 2),
+       SENSOR_ATTR_2(pwm3_auto_channels_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_channel,
+                     store_pwm_auto_point_channel, 0, 2),
+       SENSOR_ATTR_2(pwm3_auto_point1_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     0, 2),
+       SENSOR_ATTR_2(pwm3_auto_point2_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     1, 2),
+       SENSOR_ATTR_2(pwm3_auto_point3_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     2, 2),
+       SENSOR_ATTR_2(pwm3_auto_point4_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     3, 2),
+       SENSOR_ATTR_2(pwm3_auto_point5_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     4, 2),
+       SENSOR_ATTR_2(pwm3_auto_point1_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp, store_pwm_auto_point_temp,
+                     0, 2),
+       SENSOR_ATTR_2(pwm3_auto_point2_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp, store_pwm_auto_point_temp,
+                     1, 2),
+       SENSOR_ATTR_2(pwm3_auto_point3_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp, store_pwm_auto_point_temp,
+                     2, 2),
+       SENSOR_ATTR_2(pwm3_auto_point4_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp, store_pwm_auto_point_temp,
+                     3, 2),
+       SENSOR_ATTR_2(pwm3_auto_point1_temp_hyst, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp_hyst,
+                     store_pwm_auto_point_temp_hyst,
+                     0, 2),
+       SENSOR_ATTR_2(pwm3_auto_point2_temp_hyst, S_IRUGO,
+                     show_pwm_auto_point_temp_hyst, NULL, 1, 2),
+       SENSOR_ATTR_2(pwm3_auto_point3_temp_hyst, S_IRUGO,
+                     show_pwm_auto_point_temp_hyst, NULL, 2, 2),
+       SENSOR_ATTR_2(pwm3_auto_point4_temp_hyst, S_IRUGO,
+                     show_pwm_auto_point_temp_hyst, NULL, 3, 2),
+
+       SENSOR_ATTR_2(pwm4, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0, 3),
+       SENSOR_ATTR_2(pwm4_enable, S_IRUGO|S_IWUSR, show_pwm_enable,
+                     store_pwm_enable, 0, 3),
+       SENSOR_ATTR_2(pwm4_interpolate, S_IRUGO|S_IWUSR,
+                     show_pwm_interpolate, store_pwm_interpolate, 0, 3),
+       SENSOR_ATTR_2(pwm4_auto_channels_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_channel,
+                     store_pwm_auto_point_channel, 0, 3),
+       SENSOR_ATTR_2(pwm4_auto_point1_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     0, 3),
+       SENSOR_ATTR_2(pwm4_auto_point2_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     1, 3),
+       SENSOR_ATTR_2(pwm4_auto_point3_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     2, 3),
+       SENSOR_ATTR_2(pwm4_auto_point4_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     3, 3),
+       SENSOR_ATTR_2(pwm4_auto_point5_pwm, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
+                     4, 3),
+       SENSOR_ATTR_2(pwm4_auto_point1_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp, store_pwm_auto_point_temp,
+                     0, 3),
+       SENSOR_ATTR_2(pwm4_auto_point2_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp, store_pwm_auto_point_temp,
+                     1, 3),
+       SENSOR_ATTR_2(pwm4_auto_point3_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp, store_pwm_auto_point_temp,
+                     2, 3),
+       SENSOR_ATTR_2(pwm4_auto_point4_temp, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp, store_pwm_auto_point_temp,
+                     3, 3),
+       SENSOR_ATTR_2(pwm4_auto_point1_temp_hyst, S_IRUGO|S_IWUSR,
+                     show_pwm_auto_point_temp_hyst,
+                     store_pwm_auto_point_temp_hyst,
+                     0, 3),
+       SENSOR_ATTR_2(pwm4_auto_point2_temp_hyst, S_IRUGO,
+                     show_pwm_auto_point_temp_hyst, NULL, 1, 3),
+       SENSOR_ATTR_2(pwm4_auto_point3_temp_hyst, S_IRUGO,
+                     show_pwm_auto_point_temp_hyst, NULL, 2, 3),
+       SENSOR_ATTR_2(pwm4_auto_point4_temp_hyst, S_IRUGO,
+                     show_pwm_auto_point_temp_hyst, NULL, 3, 3),
 };
 
 
@@ -307,6 +563,11 @@ static inline u16 fan_from_reg(u16 reg)
        return reg ? (1500000 / reg) : 0;
 }
 
+static inline u16 fan_to_reg(u16 fan)
+{
+       return fan ? (1500000 / fan) : 0;
+}
+
 static u8 f71882fg_read8(struct f71882fg_data *data, u8 reg)
 {
        u8 val;
@@ -335,6 +596,14 @@ static void f71882fg_write8(struct f71882fg_data *data, u8 reg, u8 val)
        outb(val, data->addr + DATA_REG_OFFSET);
 }
 
+static void f71882fg_write16(struct f71882fg_data *data, u8 reg, u16 val)
+{
+       outb(reg++, data->addr + ADDR_REG_OFFSET);
+       outb(val >> 8, data->addr + DATA_REG_OFFSET);
+       outb(reg, data->addr + ADDR_REG_OFFSET);
+       outb(val & 255, data->addr + DATA_REG_OFFSET);
+}
+
 static struct f71882fg_data *f71882fg_update_device(struct device *dev)
 {
        struct f71882fg_data *data = dev_get_drvdata(dev);
@@ -381,6 +650,32 @@ static struct f71882fg_data *f71882fg_update_device(struct device *dev)
 
                data->fan_beep = f71882fg_read8(data, F71882FG_REG_FAN_BEEP);
 
+               data->pwm_enable = f71882fg_read8(data,
+                                                 F71882FG_REG_PWM_ENABLE);
+               data->pwm_auto_point_hyst[0] = f71882fg_read8(data,
+                                                     F71882FG_REG_FAN_HYST0);
+               data->pwm_auto_point_hyst[1] = f71882fg_read8(data,
+                                                     F71882FG_REG_FAN_HYST1);
+               for (nr = 0; nr < 4; nr++) {
+                       int point;
+
+                       data->pwm_auto_point_mapping[nr] =
+                           f71882fg_read8(data,
+                                          F71882FG_REG_POINT_MAPPING(nr));
+
+                       for (point = 0; point < 5; point++) {
+                               data->pwm_auto_point_pwm[nr][point] =
+                                   f71882fg_read8(data,
+                                                  F71882FG_REG_POINT_PWM
+                                                  (nr, point));
+                       }
+                       for (point = 0; point < 4; point++) {
+                               data->pwm_auto_point_temp[nr][point] =
+                                   f71882fg_read8(data,
+                                                  F71882FG_REG_POINT_TEMP
+                                                  (nr, point));
+                       }
+               }
                data->last_limits = jiffies;
        }
 
@@ -396,9 +691,17 @@ static struct f71882fg_data *f71882fg_update_device(struct device *dev)
 
                data->fan_status = f71882fg_read8(data,
                                                F71882FG_REG_FAN_STATUS);
-               for (nr = 0; nr < 4; nr++)
+               for (nr = 0; nr < 4; nr++) {
                        data->fan[nr] = f71882fg_read16(data,
                                                F71882FG_REG_FAN(nr));
+                       data->fan_target[nr] =
+                           f71882fg_read16(data, F71882FG_REG_FAN_TARGET(nr));
+                       data->fan_full_speed[nr] =
+                           f71882fg_read16(data,
+                                           F71882FG_REG_FAN_FULL_SPEED(nr));
+                       data->pwm[nr] =
+                           f71882fg_read8(data, F71882FG_REG_PWM(nr));
+               }
 
                data->in_status = f71882fg_read8(data,
                                                F71882FG_REG_IN_STATUS);
@@ -429,6 +732,40 @@ static ssize_t show_fan(struct device *dev, struct device_attribute *devattr,
        return sprintf(buf, "%d\n", speed);
 }
 
+static ssize_t show_fan_full_speed(struct device *dev,
+                                  struct device_attribute *devattr, char *buf)
+{
+       struct f71882fg_data *data = f71882fg_update_device(dev);
+       int nr = to_sensor_dev_attr_2(devattr)->index;
+       int speed = fan_from_reg(data->fan_full_speed[nr]);
+       return sprintf(buf, "%d\n", speed);
+}
+
+static ssize_t store_fan_full_speed(struct device *dev,
+                                   struct device_attribute *devattr,
+                                   const char *buf, size_t count)
+{
+       struct f71882fg_data *data = dev_get_drvdata(dev);
+       int nr = to_sensor_dev_attr_2(devattr)->index;
+       long val = simple_strtol(buf, NULL, 10);
+
+       val = SENSORS_LIMIT(val, 23, 1500000);
+       val = fan_to_reg(val);
+
+       mutex_lock(&data->update_lock);
+       if (data->pwm_enable & (1 << (2 * nr)))
+               /* PWM mode */
+               count = -EINVAL;
+       else {
+               /* RPM mode */
+               f71882fg_write16(data, F71882FG_REG_FAN_FULL_SPEED(nr), val);
+               data->fan_full_speed[nr] = val;
+       }
+       mutex_unlock(&data->update_lock);
+
+       return count;
+}
+
 static ssize_t show_fan_beep(struct device *dev, struct device_attribute
        *devattr, char *buf)
 {
@@ -736,6 +1073,323 @@ static ssize_t show_temp_fault(struct device *dev, struct device_attribute
                return sprintf(buf, "0\n");
 }
 
+static ssize_t show_pwm(struct device *dev,
+                       struct device_attribute *devattr, char *buf)
+{
+       struct f71882fg_data *data = f71882fg_update_device(dev);
+       int val, nr = to_sensor_dev_attr_2(devattr)->index;
+       if (data->pwm_enable & (1 << (2 * nr)))
+               /* PWM mode */
+               val = data->pwm[nr];
+       else {
+               /* RPM mode */
+               mutex_lock(&data->update_lock);
+               val = 255 * fan_from_reg(data->fan_target[nr])
+                       / fan_from_reg(data->fan_full_speed[nr]);
+               mutex_unlock(&data->update_lock);
+       }
+       return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t store_pwm(struct device *dev,
+                        struct device_attribute *devattr, const char *buf,
+                        size_t count)
+{
+       /* struct f71882fg_data *data = dev_get_drvdata(dev); */
+       struct f71882fg_data *data = f71882fg_update_device(dev);
+       int nr = to_sensor_dev_attr_2(devattr)->index;
+       long val = simple_strtol(buf, NULL, 10);
+       val = SENSORS_LIMIT(val, 0, 255);
+
+       mutex_lock(&data->update_lock);
+       if (data->pwm_enable & (1 << (2 * nr))) {
+               /* PWM mode */
+               f71882fg_write8(data, F71882FG_REG_PWM(nr), val);
+               data->pwm[nr] = val;
+       } else {
+               /* RPM mode */
+               int target = val * fan_from_reg(data->fan_full_speed[nr]) / 255;
+               f71882fg_write16(data, F71882FG_REG_FAN_TARGET(nr),
+                                fan_to_reg(target));
+               data->fan_target[nr] = fan_to_reg(target);
+       }
+       mutex_unlock(&data->update_lock);
+
+       return count;
+}
+
+static ssize_t show_pwm_enable(struct device *dev,
+                              struct device_attribute *devattr, char *buf)
+{
+       int result;
+       struct f71882fg_data *data = f71882fg_update_device(dev);
+       int nr = to_sensor_dev_attr_2(devattr)->index;
+
+       if (data->pwm_enable & (2 << (2 * nr)))
+               result = 1;
+       else
+               result = 2;
+
+       return sprintf(buf, "%d\n", result);
+}
+
+static ssize_t store_pwm_enable(struct device *dev, struct device_attribute
+                               *devattr, const char *buf, size_t count)
+{
+       struct f71882fg_data *data = dev_get_drvdata(dev);
+       int nr = to_sensor_dev_attr_2(devattr)->index;
+       long val = simple_strtol(buf, NULL, 10);
+       if (val < 1 || val > 2)
+               return -EINVAL;
+
+       mutex_lock(&data->update_lock);
+       switch (val) {
+       case 1:
+               data->pwm_enable |= 2 << (2 * nr);
+               break;          /* Manual */
+       case 2:
+               data->pwm_enable &= ~(2 << (2 * nr));
+               break;          /* Temperature ctrl */
+       }
+       switch (fan_mode[nr]) {
+       case 1:
+               data->pwm_enable |= 1 << (2 * nr);
+               break;          /* Duty cycle mode */
+       case 2:
+               data->pwm_enable &= ~(1 << (2 * nr));
+               break;          /* RPM mode */
+       }
+       f71882fg_write8(data, F71882FG_REG_PWM_ENABLE, data->pwm_enable);
+       mutex_unlock(&data->update_lock);
+
+       return count;
+}
+
+static ssize_t show_pwm_auto_point_pwm(struct device *dev,
+                                      struct device_attribute *devattr,
+                                      char *buf)
+{
+       int result;
+       struct f71882fg_data *data = f71882fg_update_device(dev);
+       int pwm = to_sensor_dev_attr_2(devattr)->index;
+       int point = to_sensor_dev_attr_2(devattr)->nr;
+
+       if (data->pwm_enable & (1 << (2 * pwm))) {
+               /* PWM mode */
+               result = data->pwm_auto_point_pwm[pwm][point];
+       } else {
+               /* RPM mode */
+               result = 32 * 255 / (32 + data->pwm_auto_point_pwm[pwm][point]);
+       }
+
+       return sprintf(buf, "%d\n", result);
+}
+
+static ssize_t store_pwm_auto_point_pwm(struct device *dev,
+                                       struct device_attribute *devattr,
+                                       const char *buf, size_t count)
+{
+       /* struct f71882fg_data *data = dev_get_drvdata(dev); */
+       struct f71882fg_data *data = f71882fg_update_device(dev);
+       int pwm = to_sensor_dev_attr_2(devattr)->index;
+       int point = to_sensor_dev_attr_2(devattr)->nr;
+       int val = simple_strtoul(buf, NULL, 10);
+       val = SENSORS_LIMIT(val, 0, 255);
+
+       mutex_lock(&data->update_lock);
+       if (data->pwm_enable & (1 << (2 * pwm))) {
+               /* PWM mode */
+       } else {
+               /* RPM mode */
+               if (val < 29)   /* Prevent negative numbers */
+                       val = 255;
+               else
+                       val = (255 - val) * 32 / val;
+       }
+       f71882fg_write8(data, F71882FG_REG_POINT_PWM(pwm, point), val);
+       data->pwm_auto_point_pwm[pwm][point] = val;
+       mutex_unlock(&data->update_lock);
+
+       return count;
+}
+
+static ssize_t show_pwm_auto_point_temp_hyst(struct device *dev,
+                                            struct device_attribute *devattr,
+                                            char *buf)
+{
+       int result = 0;
+       struct f71882fg_data *data = f71882fg_update_device(dev);
+       int nr = to_sensor_dev_attr_2(devattr)->index;
+       int point = to_sensor_dev_attr_2(devattr)->nr;
+
+       mutex_lock(&data->update_lock);
+       switch (nr) {
+       case 0:
+               result = data->pwm_auto_point_hyst[0] & 0x0f;
+               break;
+       case 1:
+               result = data->pwm_auto_point_hyst[0] >> 4;
+               break;
+       case 2:
+               result = data->pwm_auto_point_hyst[1] & 0x0f;
+               break;
+       case 3:
+               result = data->pwm_auto_point_hyst[1] >> 4;
+               break;
+       }
+       result = 1000 * (data->pwm_auto_point_temp[nr][point] - result);
+       mutex_unlock(&data->update_lock);
+
+       return sprintf(buf, "%d\n", result);
+}
+
+static ssize_t store_pwm_auto_point_temp_hyst(struct device *dev,
+                                             struct device_attribute *devattr,
+                                             const char *buf, size_t count)
+{
+       struct f71882fg_data *data = f71882fg_update_device(dev);
+       int nr = to_sensor_dev_attr_2(devattr)->index;
+       int point = to_sensor_dev_attr_2(devattr)->nr;
+       long val = simple_strtol(buf, NULL, 10) / 1000;
+
+       mutex_lock(&data->update_lock);
+       val = SENSORS_LIMIT(val, data->pwm_auto_point_temp[nr][point] - 15,
+                               data->pwm_auto_point_temp[nr][point]);
+       val = data->pwm_auto_point_temp[nr][point] - val;
+
+       switch (nr) {
+       case 0:
+               val = (data->pwm_auto_point_hyst[0] & 0xf0) | val;
+               break;
+       case 1:
+               val = (data->pwm_auto_point_hyst[0] & 0x0f) | (val << 4);
+               break;
+       case 2:
+               val = (data->pwm_auto_point_hyst[1] & 0xf0) | val;
+               break;
+       case 3:
+               val = (data->pwm_auto_point_hyst[1] & 0x0f) | (val << 4);
+               break;
+       }
+       if (nr == 0 || nr == 1) {
+               f71882fg_write8(data, F71882FG_REG_FAN_HYST0, val);
+               data->pwm_auto_point_hyst[0] = val;
+       } else {
+               f71882fg_write8(data, F71882FG_REG_FAN_HYST1, val);
+               data->pwm_auto_point_hyst[1] = val;
+       }
+       mutex_unlock(&data->update_lock);
+
+       return count;
+}
+
+static ssize_t show_pwm_interpolate(struct device *dev,
+                                   struct device_attribute *devattr, char *buf)
+{
+       int result;
+       struct f71882fg_data *data = f71882fg_update_device(dev);
+       int nr = to_sensor_dev_attr_2(devattr)->index;
+
+       result = (data->pwm_auto_point_mapping[nr] >> 4) & 1;
+
+       return sprintf(buf, "%d\n", result);
+}
+
+static ssize_t store_pwm_interpolate(struct device *dev,
+                                    struct device_attribute *devattr,
+                                    const char *buf, size_t count)
+{
+       /* struct f71882fg_data *data = dev_get_drvdata(dev); */
+       struct f71882fg_data *data = f71882fg_update_device(dev);
+       int nr = to_sensor_dev_attr_2(devattr)->index;
+       int val = simple_strtoul(buf, NULL, 10);
+       mutex_lock(&data->update_lock);
+       if (val)
+               val = data->pwm_auto_point_mapping[nr] | (1 << 4);
+       else
+               val = data->pwm_auto_point_mapping[nr] & (~(1 << 4));
+       f71882fg_write8(data, F71882FG_REG_POINT_MAPPING(nr), val);
+       data->pwm_auto_point_mapping[nr] = val;
+       mutex_unlock(&data->update_lock);
+
+       return count;
+}
+
+static ssize_t show_pwm_auto_point_channel(struct device *dev,
+                                          struct device_attribute *devattr,
+                                          char *buf)
+{
+       int result;
+       struct f71882fg_data *data = f71882fg_update_device(dev);
+       int nr = to_sensor_dev_attr_2(devattr)->index;
+
+       result = 1 << ((data->pwm_auto_point_mapping[nr] & 3) - 1);
+
+       return sprintf(buf, "%d\n", result);
+}
+
+static ssize_t store_pwm_auto_point_channel(struct device *dev,
+                                           struct device_attribute *devattr,
+                                           const char *buf, size_t count)
+{
+       /* struct f71882fg_data *data = dev_get_drvdata(dev); */
+       struct f71882fg_data *data = f71882fg_update_device(dev);
+       int nr = to_sensor_dev_attr_2(devattr)->index;
+       long val = simple_strtol(buf, NULL, 10);
+       switch (val) {
+       case 1:
+               val = 1;
+               break;
+       case 2:
+               val = 2;
+               break;
+       case 4:
+               val = 3;
+               break;
+       default:
+               return -EINVAL;
+       }
+       mutex_lock(&data->update_lock);
+       val = (data->pwm_auto_point_mapping[nr] & 0xfc) | val;
+       f71882fg_write8(data, F71882FG_REG_POINT_MAPPING(nr), val);
+       data->pwm_auto_point_mapping[nr] = val;
+       mutex_unlock(&data->update_lock);
+
+       return count;
+}
+
+static ssize_t show_pwm_auto_point_temp(struct device *dev,
+                                       struct device_attribute *devattr,
+                                       char *buf)
+{
+       int result;
+       struct f71882fg_data *data = f71882fg_update_device(dev);
+       int pwm = to_sensor_dev_attr_2(devattr)->index;
+       int point = to_sensor_dev_attr_2(devattr)->nr;
+
+       result = data->pwm_auto_point_temp[pwm][point];
+       return sprintf(buf, "%d\n", 1000 * result);
+}
+
+static ssize_t store_pwm_auto_point_temp(struct device *dev,
+                                        struct device_attribute *devattr,
+                                        const char *buf, size_t count)
+{
+       /* struct f71882fg_data *data = dev_get_drvdata(dev); */
+       struct f71882fg_data *data = f71882fg_update_device(dev);
+       int pwm = to_sensor_dev_attr_2(devattr)->index;
+       int point = to_sensor_dev_attr_2(devattr)->nr;
+       long val = simple_strtol(buf, NULL, 10) / 1000;
+       val = SENSORS_LIMIT(val, 0, 255);
+
+       mutex_lock(&data->update_lock);
+       f71882fg_write8(data, F71882FG_REG_POINT_TEMP(pwm, point), val);
+       data->pwm_auto_point_temp[pwm][point] = val;
+       mutex_unlock(&data->update_lock);
+
+       return count;
+}
+
 static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
        char *buf)
 {