All of lore.kernel.org
 help / color / mirror / Atom feed
* [lm-sensors] [PATCH 2.6.25.4] f71882.c driver,
@ 2008-06-11  5:20 Mark van Doesburg
  2008-06-11  8:18 ` Goede, J.W.R. de
                   ` (9 more replies)
  0 siblings, 10 replies; 11+ messages in thread
From: Mark van Doesburg @ 2008-06-11  5:20 UTC (permalink / raw)
  To: lm-sensors

Hi,

I've added fan/pwm control to the f71882fg driver in
linux-2.6.25.4. There's no hardware speed control yet, I intend
to add that later. Since this chip is able to work in RPM and
PWM controlled mode I needed to extend the possible input for
/sys/class/hwmon/hwmon*/pwm*_enable, where the extra value control PWM
vs. RPM mode.

This also raises a question. RPM control seems to be the most appropriate
version of fan control, but I need to add a maximum expected RPM value
to the driver. I was thinking about /sys/class/hwmon/hwmon./fan*_max,
but I would like like some feedback on that.

Please CC me when reacting to this post, since I'm not on the list.

Kind regards,

Mark van Doesburg.


--- /tmp/linux-2.6.25.4/drivers/hwmon/f71882fg.c	2008-05-15 17:00:12.000000000 +0200
+++ drivers/hwmon/f71882fg.c	2008-06-11 06:59:42.000000000 +0200
@@ -57,6 +57,7 @@
 #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_STATUS		0x92
 #define F71882FG_REG_FAN_BEEP		0x93
 
@@ -70,6 +71,11 @@
 #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_START		0x01
 
 #define FAN_MIN_DETECT			366 /* Lowest detectable fanspeed */
@@ -88,6 +94,7 @@
 static inline void superio_exit(int base);
 
 static inline u16 fan_from_reg ( u16 reg );
+static inline u16 fan_to_reg ( u16 reg );
 
 struct f71882fg_data {
 	unsigned short addr;
@@ -104,6 +111,7 @@
 	u8	in_status;
 	u8	in_beep;
 	u16	fan[4];
+	u16	fan_target[4];
 	u8	fan_status;
 	u8	fan_beep;
 	u8	temp[3];
@@ -114,11 +122,15 @@
 	u8	temp_status;
 	u8	temp_beep;
 	u8	temp_diode_open;
+	u8	pwm[4];
+	u8	pwm_enable;
+	u8	pwm_type;
 };
 
 static u8 f71882fg_read8(struct f71882fg_data *data, u8 reg);
 static u16 f71882fg_read16(struct f71882fg_data *data, u8 reg);
 static void f71882fg_write8(struct f71882fg_data *data, u8 reg, u8 val);
+static void f71882fg_write16(struct f71882fg_data *data, u8 reg, u16 val);
 
 /* Sysfs in*/
 static ssize_t show_in(struct device *dev, struct device_attribute *devattr,
@@ -136,6 +148,10 @@
 /* Sysfs Fan */
 static ssize_t show_fan(struct device *dev, struct device_attribute *devattr,
 	char *buf);
+static ssize_t show_fan_target(struct device *dev, struct device_attribute
+	*devattr, char *buf);
+static ssize_t store_fan_target(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
@@ -173,6 +189,15 @@
 static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
 	char *buf);
 
+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 int __devinit f71882fg_probe(struct platform_device * pdev);
 static int __devexit f71882fg_remove(struct platform_device *pdev);
 static int __init f71882fg_init(void);
@@ -252,21 +277,41 @@
 static struct sensor_device_attribute f71882fg_fan_attr[]  {
 	SENSOR_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0),
+	SENSOR_ATTR(fan1_target, S_IRUGO|S_IWUSR, show_fan_target,
+		store_fan_target, 0),
 	SENSOR_ATTR(fan1_beep, S_IRUGO|S_IWUSR, show_fan_beep,
 		store_fan_beep, 0),
 	SENSOR_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0),
 	SENSOR_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1),
+	SENSOR_ATTR(fan2_target, S_IRUGO|S_IWUSR, show_fan_target,
+		store_fan_target, 1),
 	SENSOR_ATTR(fan2_beep, S_IRUGO|S_IWUSR, show_fan_beep,
 		store_fan_beep, 1),
 	SENSOR_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1),
 	SENSOR_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2),
+	SENSOR_ATTR(fan3_target, S_IRUGO|S_IWUSR, show_fan_target,
+		store_fan_target, 2),
 	SENSOR_ATTR(fan3_beep, S_IRUGO|S_IWUSR, show_fan_beep,
 		store_fan_beep, 2),
 	SENSOR_ATTR(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 2),
 	SENSOR_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3),
+	SENSOR_ATTR(fan4_target, S_IRUGO|S_IWUSR, show_fan_target,
+		store_fan_target, 3),
 	SENSOR_ATTR(fan4_beep, S_IRUGO|S_IWUSR, show_fan_beep,
 		store_fan_beep, 3),
-	SENSOR_ATTR(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 3)
+	SENSOR_ATTR(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 3),
+	SENSOR_ATTR(pwm1, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0),
+	SENSOR_ATTR(pwm2, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 1),
+	SENSOR_ATTR(pwm3, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 2),
+	SENSOR_ATTR(pwm4, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 3),
+	SENSOR_ATTR(pwm1_enable, S_IRUGO|S_IWUSR, show_pwm_enable, 
+		store_pwm_enable, 0),
+	SENSOR_ATTR(pwm2_enable, S_IRUGO|S_IWUSR, show_pwm_enable, 
+		store_pwm_enable, 1), 
+	SENSOR_ATTR(pwm3_enable, S_IRUGO|S_IWUSR, show_pwm_enable, 
+		store_pwm_enable, 2),
+	SENSOR_ATTR(pwm4_enable, S_IRUGO|S_IWUSR, show_pwm_enable, 
+		store_pwm_enable, 3)
 };
 
 
@@ -310,6 +355,11 @@
 	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;
@@ -338,6 +388,15 @@
 	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);
@@ -399,9 +458,18 @@
 
 		data->fan_status = f71882fg_read8(data,
 						F71882FG_REG_FAN_STATUS);
-		for (nr = 0; nr < 4; nr++)
+		data->pwm_type = f71882fg_read8(data,
+						F71882FG_REG_PWM_TYPE);
+		data->pwm_enable = f71882fg_read8(data,
+						F71882FG_REG_PWM_ENABLE);
+		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->pwm[nr] = f71882fg_read8(data,
+						F71882FG_REG_PWM(nr));
+                }
 
 		data->in_status = f71882fg_read8(data,
 						F71882FG_REG_IN_STATUS);
@@ -432,6 +500,30 @@
 	return sprintf(buf, "%d\n", speed);
 }
 
+static ssize_t show_fan_target(struct device *dev, 
+	struct device_attribute *devattr, char *buf)
+{
+	struct f71882fg_data *data = f71882fg_update_device(dev);
+	int nr = to_sensor_dev_attr(devattr)->index;
+	int speed = fan_from_reg(data->fan_target[nr]);
+	return sprintf(buf, "%d\n", speed);
+}
+
+static ssize_t store_fan_target(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(devattr)->index;
+	int val = simple_strtoul(buf, NULL, 10);
+	valún_to_reg(val);
+
+	mutex_lock(&data->update_lock);
+	f71882fg_write16(data, F71882FG_REG_FAN_TARGET(nr), val);
+	mutex_unlock(&data->update_lock);
+
+	return count;
+}
+
 static ssize_t show_fan_beep(struct device *dev, struct device_attribute
 	*devattr, char *buf)
 {
@@ -739,6 +831,73 @@
 		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 nr = to_sensor_dev_attr(devattr)->index;
+	return sprintf(buf, "%d\n", data->pwm[nr]);
+}
+
+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);
+	int nr = to_sensor_dev_attr(devattr)->index;
+	int val = simple_strtoul(buf, NULL, 10);
+
+	mutex_lock(&data->update_lock);
+	f71882fg_write8(data, F71882FG_REG_PWM(nr), val);
+	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(devattr)->index;
+
+	switch(3&(data->pwm_enable>>(2*nr))) {
+	case 0: result=2; break; // Temperature controlled (RPM mode)
+	case 1: result=3; break; // Temperature controlled (DUTY CYCLE mode)
+	case 2: result=4; break; // Manual mode (RPM control)
+	default: result=1; break; // Manual mode (DUTY CYCLE)
+	}
+
+	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 reg;
+	int nr = to_sensor_dev_attr(devattr)->index;
+	int val = simple_strtoul(buf, NULL, 10);
+
+	switch(val) {
+	case 1: reg=3; break; // Manual mode (DUTY CYCLE)
+	case 2: reg=0; break; // Temperature controlled (RPM mode)
+	case 3: reg=1; break; // Temperature controlled (DUTY CYCLE mode)
+	case 4: reg=2; break; // Manual mode (RPM control)
+	default: val=0; reg=3; break; // Manual mode 100% duty cycle
+	}
+
+	mutex_lock(&data->update_lock);
+	data->pwm_enable&=~(3<<(2*nr));
+	data->pwm_enable|=reg<<(2*nr);
+	f71882fg_write8(data, F71882FG_REG_PWM_ENABLE, data->pwm_enable);
+	if(!val)
+		f71882fg_write8(data, F71882FG_REG_PWM(nr), 255);
+	mutex_unlock(&data->update_lock);
+
+	return count;
+}
+
 static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
 	char *buf)
 {


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [lm-sensors] [PATCH 2.6.25.4] f71882.c driver,
  2008-06-11  5:20 [lm-sensors] [PATCH 2.6.25.4] f71882.c driver, Mark van Doesburg
@ 2008-06-11  8:18 ` Goede, J.W.R. de
  2008-06-11 19:41 ` Mark van Doesburg
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Goede, J.W.R. de @ 2008-06-11  8:18 UTC (permalink / raw)
  To: lm-sensors

Hi Mark,

Thanks for looking into this, I'm the author of the current

f71882fg driver and I have adding pwm support on my todo
list for quite a while now, but unfortunately sofar I
haven't found the time for it.

I have however already a clear idea how I would like to see
the pwm implement (but I'm open for discussion).

You've currently added both pwmX and fanX_target
attributes, but those both show (and store!!) the same
register in a different "notation", and at any given time
only one of the 2 notations is valid, depending on the
pwmX_enable attribute.

Allowing writes to pwmX when the fintek is pwm set in rpm
modes not pwm modes or the other way round is IMHO
unacceptable, as this will lead to bogues settings.
I have been thinking about this for some time now and I see
4 solutions for this:

1) Only support pwm modes as this is what all other hwmon
   IC's have, but this would require reprogramming the 
   auto_points when in rpm auto mode so that we get sane
   settings when reprogramming the f71882fg in auto pwm 
   mode. I don't like this as it causes the chip to be 
   reprogrammed from its bios defaults without the user
   asking for it.

2) make pwmX / fanX_target come and go depending on 
   pwmX_enable, so yes that would mean removing and adding
   sysfs atributes on the fly as pwmX_enable changes.
   Don't like it either, ugly, and racy what happens if an
   app keeps an attribute open?

3) mark the pwmX / fanX_target readonly, that is chmod
   the attribute on the fly as pwmX_enable changes, and 
   check in show/store_pwm if we are in pwm mode, if not
   return EPERM for store (fixes attr kept open race) and
   return -1 as value when showing. Still quite complex

4) See rpm mode as just a different pwm mode, pwm is
   0-100% dutycylce mapped to 0-255, see rpm mode as
   0-100% of the fan full speed count register, note this
   is how the target speed per section is set in the
   registers when in rpm auto mode, and such for auto mode
   has very much an 1-1 mapping for all the auto regs,
   which can fully reuse our existing defined sysfs attr
   for auto pwm control (pwmX_auto_point_*****)

   The only place where this mapping isn't 1 on 1 is for
   manual rpm mode, where the fintek allows you to
   specify hard go X rpm, but mapping this to a % of
   full speed should be easy.

I greatly prefer solution 4 for its simplicity and matching
our currently defined sysfs attr standard for pwm and auto
pwm I want to combine this with simply using either pwm or
rpm mode (depending on what the BIOS gives us) and not
allow changing this on the fly with pwm_enable, so reduce
pwm_enable to simply choosing between auto and manual mode.

I don't want to allow on the fly changing, because then the
meaning of a lot of registers change and they would all
need to be reinitialized to something sane in their new
meaning. We could add a module option to force either mode
at module load time (overriding bios settings for both the
mode and the zone settings).

Last is what todo with the full speed reg, I wonder have
you tested if this gets set to anything sane by the BIOS or
the chip itself? I know some hwmon IC's how have a register
like this start by running the fan at full speed for 2
seconds and then measure that speed and store that in full
speed, I wonder if that happens here. Anyways we should
create an atrribute for it and it shoud _not_ be called
fanX_max, max is already used in other cases and it means
give an alarm if the reading goes above this value. Instead
I would like to suggest to use what the datasheet uses and
call it fanX_full_speed

So what do you think of my proposal 4) for sysfs interface
for the pwm part of the f71882fg ? And would you be willing
to implement this?

Thanks & Regards,

Hans

p.s.

1) If you want we can continue this discussion in private
in Dutch, but I would greatly prefer to keep this on the
list in English

2) Being bold and assusming you're Dutch, where in the
Netherlands do you live? I'm always interested in meeting
fellow Dutch Linux hackers :)


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [lm-sensors] [PATCH 2.6.25.4] f71882.c driver,
  2008-06-11  5:20 [lm-sensors] [PATCH 2.6.25.4] f71882.c driver, Mark van Doesburg
  2008-06-11  8:18 ` Goede, J.W.R. de
@ 2008-06-11 19:41 ` Mark van Doesburg
  2008-06-12  8:05 ` Hans de Goede
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Mark van Doesburg @ 2008-06-11 19:41 UTC (permalink / raw)
  To: lm-sensors

Hello Hans,

Your proposal sounds fine to me. I wasn't too happy with the fan?_target
pwm? conflict either.  So I will implement the following:

	1. Remove fan?_target.
	2. Add fan?_full_speed.
	3. Interpret pwm? as the duty cycle when in duty cycle mode.
	4. Scale pwm? from 0..255 to 0..fan?_full_speed when in rpm mode.
	5. Change the behavior of pwm?_enable not to change pwm vs. duty
	   cycle mode.
	5. Add module option to select rpm/duty cycle mode.  
	6. Add pwm?_auto*

This will take some time of course. (Although being able to read the
temperature of my "Asus EAH3650 silent" would speed things up ;-)

My BIOS doesn't put a usefull value in the full speed register. There is
also no way to set the case fan to anything but a fixed PWM value. Then
again I build the computer from seperate components, I can imagine a
fully assembled system could do more.

regards,

Mark van Doesburg.


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [lm-sensors] [PATCH 2.6.25.4] f71882.c driver,
  2008-06-11  5:20 [lm-sensors] [PATCH 2.6.25.4] f71882.c driver, Mark van Doesburg
  2008-06-11  8:18 ` Goede, J.W.R. de
  2008-06-11 19:41 ` Mark van Doesburg
@ 2008-06-12  8:05 ` Hans de Goede
  2008-06-25  6:14 ` Mark van Doesburg
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Hans de Goede @ 2008-06-12  8:05 UTC (permalink / raw)
  To: lm-sensors

Mark van Doesburg wrote:
> Hello Hans,
> 
> Your proposal sounds fine to me. I wasn't too happy with the fan?_target
> pwm? conflict either.  So I will implement the following:
> 
> 	1. Remove fan?_target.
> 	2. Add fan?_full_speed.
> 	3. Interpret pwm? as the duty cycle when in duty cycle mode.
> 	4. Scale pwm? from 0..255 to 0..fan?_full_speed when in rpm mode.
> 	5. Change the behavior of pwm?_enable not to change pwm vs. duty
> 	   cycle mode.
> 	5. Add module option to select rpm/duty cycle mode.  
> 	6. Add pwm?_auto*

Excellent! (and thanks!)

> My BIOS doesn't put a usefull value in the full speed register. There is
> also no way to set the case fan to anything but a fixed PWM value.

To bad, I guess that it doesn't use rpm mode then, if the chip doesn't put 
anything usefull in the fullspeed reg itself either I guess most BIOS'es will 
not use rpm mode, but will stick with pwm mode.

Regards,

hans


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [lm-sensors]  [PATCH 2.6.25.4] f71882.c driver,
  2008-06-11  5:20 [lm-sensors] [PATCH 2.6.25.4] f71882.c driver, Mark van Doesburg
                   ` (2 preceding siblings ...)
  2008-06-12  8:05 ` Hans de Goede
@ 2008-06-25  6:14 ` Mark van Doesburg
  2008-06-25  7:22 ` Hans de Goede
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Mark van Doesburg @ 2008-06-25  6:14 UTC (permalink / raw)
  To: lm-sensors

Hi,

I've finally added some missing features to the driver. There are still
some things unimplemented, such as:

	1. Changing the fan target reached timeout.
	2. Something to set FAN?_RATE_SEL.  
	3. FAN?_STOP_DUTY
	4. FAN?_JUMP_HIGH_EN and FAN?_JUMP_LOW_EN 

Mostly because I don't need them, and there is no default name for
these features.

I did add one entry to set/reset the interpolation mode of the auto_point
follower, to make the pwm control act as a P-regulator. Simply because
I like it, it prevents rapid speedups and slowdowns of the fan.

Since the auto_point follower is able to track only one temperature at
a time, I gave a preference to the lowest numbered one. Maybe returning
an error would be better if the user tries to set an impossible value ?

regards,

Mark van Doesburg


--- /tmp/linux-2.6.25.4/drivers/hwmon/f71882fg.c	2008-05-15 17:00:12.000000000 +0200
+++ ./f71882fg.c	2008-06-25 07:43:52.000000000 +0200
@@ -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
 
@@ -70,14 +72,36 @@
 #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 */
 
+#define AUTO_POINT(pwm,point) ((pwm<<4)|(point))
+#define AUTO_POINT_PWM(val) ((val)>>4)
+#define AUTO_POINT_POINT(val) ((val)&15)
+
 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, 0);
+MODULE_PARM_DESC(fan_mode, 
+	"List of fan modes (for four fans) (0=don't change, 1=pwm, 2=rpm");
+
+
 static struct platform_device *f71882fg_pdev = NULL;
 
 /* Super-I/O Function prototypes */
@@ -88,6 +112,7 @@
 static inline void superio_exit(int base);
 
 static inline u16 fan_from_reg ( u16 reg );
+static inline u16 fan_to_reg ( u16 reg );
 
 struct f71882fg_data {
 	unsigned short addr;
@@ -104,6 +129,8 @@
 	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];
@@ -114,11 +141,19 @@
 	u8	temp_status;
 	u8	temp_beep;
 	u8	temp_diode_open;
+	u8	pwm[4];
+	u8	pwm_enable;
+	u8	pwm_type;
+	u8	pwm_auto_point_hyst[2];
+	u8	pwm_auto_point_mapping[4];
+	u8	pwm_auto_point_pwm[4][4];
+	u8	pwm_auto_point_temp[4][4];
 };
 
 static u8 f71882fg_read8(struct f71882fg_data *data, u8 reg);
 static u16 f71882fg_read16(struct f71882fg_data *data, u8 reg);
 static void f71882fg_write8(struct f71882fg_data *data, u8 reg, u8 val);
+static void f71882fg_write16(struct f71882fg_data *data, u8 reg, u16 val);
 
 /* Sysfs in*/
 static ssize_t show_in(struct device *dev, struct device_attribute *devattr,
@@ -136,6 +171,10 @@
 /* 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
@@ -173,6 +212,36 @@
 static ssize_t show_name(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);
+
 static int __devinit f71882fg_probe(struct platform_device * pdev);
 static int __devexit f71882fg_remove(struct platform_device *pdev);
 static int __init f71882fg_init(void);
@@ -252,21 +321,181 @@
 static struct sensor_device_attribute f71882fg_fan_attr[]  {
 	SENSOR_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0),
+	SENSOR_ATTR(fan1_full_speed, S_IRUGO|S_IWUSR, show_fan_full_speed, 
+		store_fan_full_speed, 0),
 	SENSOR_ATTR(fan1_beep, S_IRUGO|S_IWUSR, show_fan_beep,
 		store_fan_beep, 0),
 	SENSOR_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0),
 	SENSOR_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1),
+	SENSOR_ATTR(fan2_full_speed, S_IRUGO|S_IWUSR, show_fan_full_speed, 
+		store_fan_full_speed, 1),
 	SENSOR_ATTR(fan2_beep, S_IRUGO|S_IWUSR, show_fan_beep,
 		store_fan_beep, 1),
 	SENSOR_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1),
 	SENSOR_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2),
+	SENSOR_ATTR(fan3_full_speed, S_IRUGO|S_IWUSR, show_fan_full_speed, 
+		store_fan_full_speed, 2),
 	SENSOR_ATTR(fan3_beep, S_IRUGO|S_IWUSR, show_fan_beep,
 		store_fan_beep, 2),
 	SENSOR_ATTR(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 2),
 	SENSOR_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3),
+	SENSOR_ATTR(fan4_full_speed, S_IRUGO|S_IWUSR, show_fan_full_speed, 
+		store_fan_full_speed, 3),
 	SENSOR_ATTR(fan4_beep, S_IRUGO|S_IWUSR, show_fan_beep,
 		store_fan_beep, 3),
-	SENSOR_ATTR(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 3)
+	SENSOR_ATTR(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 3),
+
+	SENSOR_ATTR(pwm1, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0),
+	SENSOR_ATTR(pwm1_enable, S_IRUGO|S_IWUSR, show_pwm_enable, 
+		store_pwm_enable, 0),
+	SENSOR_ATTR(pwm1_interpolate, S_IRUGO|S_IWUSR, 
+		show_pwm_interpolate, store_pwm_interpolate, 0),
+	SENSOR_ATTR(pwm1_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp_hyst, store_pwm_auto_point_temp_hyst, 
+		0),
+	SENSOR_ATTR(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_channel, store_pwm_auto_point_channel, 0),
+	SENSOR_ATTR(pwm1_auto_point1_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(0,0)),
+	SENSOR_ATTR(pwm1_auto_point2_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(0,1)),
+	SENSOR_ATTR(pwm1_auto_point3_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(0,2)),
+	SENSOR_ATTR(pwm1_auto_point4_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(0,3)),
+	SENSOR_ATTR(pwm1_auto_point5_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(0,4)),
+	SENSOR_ATTR(pwm1_auto_point1_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		AUTO_POINT(0,0)),
+	SENSOR_ATTR(pwm1_auto_point2_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		AUTO_POINT(0,1)),
+	SENSOR_ATTR(pwm1_auto_point3_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		AUTO_POINT(0,2)),
+	SENSOR_ATTR(pwm1_auto_point4_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		AUTO_POINT(0,3)),
+
+	SENSOR_ATTR(pwm2, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 1),
+	SENSOR_ATTR(pwm2_enable, S_IRUGO|S_IWUSR, show_pwm_enable, 
+		store_pwm_enable, 1),
+	SENSOR_ATTR(pwm2_interpolate, S_IRUGO|S_IWUSR, 
+		show_pwm_interpolate, store_pwm_interpolate, 1),
+	SENSOR_ATTR(pwm2_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp_hyst, store_pwm_auto_point_temp_hyst, 
+		1),
+	SENSOR_ATTR(pwm2_auto_channels_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_channel, store_pwm_auto_point_channel, 1),
+	SENSOR_ATTR(pwm2_auto_point1_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(1,0)),
+	SENSOR_ATTR(pwm2_auto_point2_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(1,1)),
+	SENSOR_ATTR(pwm2_auto_point3_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(1,2)),
+	SENSOR_ATTR(pwm2_auto_point4_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(1,3)),
+	SENSOR_ATTR(pwm2_auto_point5_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(1,4)),
+	SENSOR_ATTR(pwm2_auto_point1_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		AUTO_POINT(1,0)),
+	SENSOR_ATTR(pwm2_auto_point2_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		AUTO_POINT(1,1)),
+	SENSOR_ATTR(pwm2_auto_point3_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		AUTO_POINT(1,2)),
+	SENSOR_ATTR(pwm2_auto_point4_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		AUTO_POINT(1,3)),
+
+	SENSOR_ATTR(pwm3, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 2),
+	SENSOR_ATTR(pwm3_enable, S_IRUGO|S_IWUSR, show_pwm_enable, 
+		store_pwm_enable, 2),
+	SENSOR_ATTR(pwm3_interpolate, S_IRUGO|S_IWUSR, 
+		show_pwm_interpolate, store_pwm_interpolate, 2),
+	SENSOR_ATTR(pwm3_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp_hyst, store_pwm_auto_point_temp_hyst, 
+		2),
+	SENSOR_ATTR(pwm3_auto_channels_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_channel, store_pwm_auto_point_channel, 2),
+	SENSOR_ATTR(pwm3_auto_point1_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(2,0)),
+	SENSOR_ATTR(pwm3_auto_point2_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(2,1)),
+	SENSOR_ATTR(pwm3_auto_point3_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(2,2)),
+	SENSOR_ATTR(pwm3_auto_point4_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(2,3)),
+	SENSOR_ATTR(pwm3_auto_point5_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(2,4)),
+	SENSOR_ATTR(pwm3_auto_point1_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		AUTO_POINT(2,0)),
+	SENSOR_ATTR(pwm3_auto_point2_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		AUTO_POINT(2,1)),
+	SENSOR_ATTR(pwm3_auto_point3_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		AUTO_POINT(2,2)),
+	SENSOR_ATTR(pwm3_auto_point4_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		AUTO_POINT(2,3)),
+
+	SENSOR_ATTR(pwm4, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 3),
+	SENSOR_ATTR(pwm4_enable, S_IRUGO|S_IWUSR, show_pwm_enable, 
+		store_pwm_enable, 3),
+	SENSOR_ATTR(pwm4_interpolate, S_IRUGO|S_IWUSR, 
+		show_pwm_interpolate, store_pwm_interpolate, 3),
+	SENSOR_ATTR(pwm4_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp_hyst, store_pwm_auto_point_temp_hyst, 
+		3),
+	SENSOR_ATTR(pwm4_auto_channels_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_channel, store_pwm_auto_point_channel, 3),
+	SENSOR_ATTR(pwm4_auto_point1_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(3,0)),
+	SENSOR_ATTR(pwm4_auto_point2_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(3,1)),
+	SENSOR_ATTR(pwm4_auto_point3_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(3,2)),
+	SENSOR_ATTR(pwm4_auto_point4_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(3,3)),
+	SENSOR_ATTR(pwm4_auto_point5_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		AUTO_POINT(3,4)),
+	SENSOR_ATTR(pwm4_auto_point1_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		AUTO_POINT(3,0)),
+	SENSOR_ATTR(pwm4_auto_point2_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		AUTO_POINT(3,1)),
+	SENSOR_ATTR(pwm4_auto_point3_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		AUTO_POINT(3,2)),
+	SENSOR_ATTR(pwm4_auto_point4_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		AUTO_POINT(3,3))
 };
 
 
@@ -310,6 +539,11 @@
 	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;
@@ -338,6 +572,15 @@
 	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);
@@ -399,9 +642,37 @@
 
 		data->fan_status = f71882fg_read8(data,
 						F71882FG_REG_FAN_STATUS);
-		for (nr = 0; nr < 4; nr++)
+		data->pwm_type = f71882fg_read8(data,
+						F71882FG_REG_PWM_TYPE);
+		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->fan[nr] = f71882fg_read16(data,
-						F71882FG_REG_FAN(nr));
+				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->pwm_auto_point_mapping[nr] = f71882fg_read8(data,
+				F71882FG_REG_POINT_MAPPING(nr));
+			for(point=0;point<4;point++) {
+				data->pwm_auto_point_pwm[nr][point]+					f71882fg_read8(data,
+						F71882FG_REG_POINT_PWM(nr,
+							point));
+				data->pwm_auto_point_temp[nr][point]+					f71882fg_read8(data,
+						F71882FG_REG_POINT_TEMP(nr,
+							point));
+			}
+                }
 
 		data->in_status = f71882fg_read8(data,
 						F71882FG_REG_IN_STATUS);
@@ -432,6 +703,34 @@
 	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(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(devattr)->index;
+	int val = simple_strtoul(buf, NULL, 10);
+
+	mutex_lock(&data->update_lock);
+	if(data->pwm_enable&(1<<(2*nr)))
+		count=-EINVAL;
+	else
+		f71882fg_write16(data, F71882FG_REG_FAN_FULL_SPEED(nr), 
+			fan_to_reg(val));
+	mutex_unlock(&data->update_lock);
+
+	return count;
+}
+
+
 static ssize_t show_fan_beep(struct device *dev, struct device_attribute
 	*devattr, char *buf)
 {
@@ -739,6 +1038,291 @@
 		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(devattr)->index;
+	if(data->pwm_enable&(1<<(2*nr)))
+		// PWM mode
+		valÚta->pwm[nr];
+	else
+		// RPM mode
+		val%5*fan_from_reg(data->fan_target[nr])
+			/fan_from_reg(data->fan_full_speed[nr]);
+	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(devattr)->index;
+	int val = simple_strtoul(buf, NULL, 10);
+
+	mutex_lock(&data->update_lock);
+	if(data->pwm_enable&(1<<(2*nr)))
+		// PWM mode
+		f71882fg_write8(data, F71882FG_REG_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));
+	}
+	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(devattr)->index;
+
+	if(2&(data->pwm_enable>>(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(devattr)->index;
+	int val = simple_strtoul(buf, NULL, 10);
+	if(val<0 || 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;	// RPM mode
+	case 2: data->pwm_enable&=~(1<<(2*nr)); break;	// Duty cycle mode
+	}
+	f71882fg_write8(data, F71882FG_REG_PWM_ENABLE, data->pwm_enable);
+	mutex_unlock(&data->update_lock);
+	if(!val)
+		store_pwm(dev,devattr,"255",4);
+
+	return count;
+}
+
+static ssize_t show_pwm_auto_point_pwm(struct device *dev, 
+	struct device_attribute *devattr,
+	char *buf)
+{
+	int result, point, pwm;
+	struct f71882fg_data *data = f71882fg_update_device(dev);
+	int nr = to_sensor_dev_attr(devattr)->index;
+
+	pwm=AUTO_POINT_PWM(nr);
+	point=AUTO_POINT_POINT(nr);
+
+	if(data->pwm_enable&(1<<(2*pwm))) {
+		// PWM mode
+		resultÚta->pwm_auto_point_pwm[pwm][point];
+	} else {
+		// RPM mode
+		result2*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);
+	int point, pwm;
+	struct f71882fg_data *data = f71882fg_update_device(dev);
+	int nr = to_sensor_dev_attr(devattr)->index;
+	int val = simple_strtoul(buf, NULL, 10);
+
+	pwm=AUTO_POINT_PWM(nr);
+	point=AUTO_POINT_POINT(nr);
+
+	mutex_lock(&data->update_lock);
+	if(data->pwm_enable&(1<<(2*pwm))) {
+		// PWM mode
+		f71882fg_write8(data, F71882FG_REG_POINT_PWM(pwm,point), val);
+		printk(KERN_INFO "nr=%d writing %d to 0x%x\n",
+			nr, val, F71882FG_REG_POINT_PWM(pwm,point));
+	} else {
+		// RPM mode
+		if(val<29)	// Prevent negative numbers
+			val%5;
+		else
+			val=(255-val)*32/val;
+		f71882fg_write8(data, F71882FG_REG_POINT_PWM(pwm,point),val);
+		printk(KERN_INFO "RPM nr=%d writing %d to 0x%x\n",
+			nr, val, F71882FG_REG_POINT_PWM(pwm,point));
+	}
+	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;
+	struct f71882fg_data *data = f71882fg_update_device(dev);
+	int nr = to_sensor_dev_attr(devattr)->index;
+
+	switch(nr) {
+	case 0: resultÚta->pwm_auto_point_hyst[0]&15; break;
+	case 1: resultÚta->pwm_auto_point_hyst[0]>>4; break;
+	case 2: resultÚta->pwm_auto_point_hyst[1]&15; break;
+	case 3: resultÚta->pwm_auto_point_hyst[1]>>4; break;
+	default: result=-1;
+	}
+
+	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 = dev_get_drvdata(dev);
+	struct f71882fg_data *data = f71882fg_update_device(dev);
+	int nr = to_sensor_dev_attr(devattr)->index;
+	int val = simple_strtoul(buf, NULL, 10);
+	val&\x15;
+	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;
+	}
+
+	mutex_lock(&data->update_lock);
+	if(nr=0 || nr=1)
+		f71882fg_write8(data, F71882FG_REG_FAN_HYST0, val); 
+	else
+		f71882fg_write8(data, F71882FG_REG_FAN_HYST1, 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(devattr)->index;
+
+	result=1&(data->pwm_auto_point_mapping[nr]>>4);
+
+	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(devattr)->index;
+	int val = simple_strtoul(buf, NULL, 10);
+	if(val)
+		val|Úta->pwm_auto_point_mapping[nr]|(1<<4);
+	else
+		val&Úta->pwm_auto_point_mapping[nr]|(~(1<<4));
+	mutex_lock(&data->update_lock);
+	f71882fg_write8(data, F71882FG_REG_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(devattr)->index;
+
+	result=1<<(data->pwm_auto_point_mapping[nr]&3);
+	result>>=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(devattr)->index;
+	int val = simple_strtoul(buf, NULL, 10);
+	if(val&1)
+		val=1;
+	else if(val&2)
+		val=2;
+	else
+		val=3;
+	val|Úta->pwm_auto_point_mapping[nr]&0xfc;
+	mutex_lock(&data->update_lock);
+	f71882fg_write8(data, F71882FG_REG_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, point, pwm;
+	struct f71882fg_data *data = f71882fg_update_device(dev);
+	int nr = to_sensor_dev_attr(devattr)->index;
+
+	pwm=AUTO_POINT_PWM(nr);
+	point=AUTO_POINT_POINT(nr);
+
+	resultÚta->pwm_auto_point_temp[pwm][point];
+	return sprintf(buf, "%d\n", 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);
+	int point, pwm;
+	struct f71882fg_data *data = f71882fg_update_device(dev);
+	int nr = to_sensor_dev_attr(devattr)->index;
+	int val = simple_strtoul(buf, NULL, 10);
+
+	pwm=AUTO_POINT_PWM(nr);
+	point=AUTO_POINT_POINT(nr);
+
+	mutex_lock(&data->update_lock);
+	printk(KERN_INFO "writing %x\n",F71882FG_REG_POINT_TEMP(pwm,point));
+	f71882fg_write8(data, F71882FG_REG_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)
 {


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [lm-sensors] [PATCH 2.6.25.4] f71882.c driver,
  2008-06-11  5:20 [lm-sensors] [PATCH 2.6.25.4] f71882.c driver, Mark van Doesburg
                   ` (3 preceding siblings ...)
  2008-06-25  6:14 ` Mark van Doesburg
@ 2008-06-25  7:22 ` Hans de Goede
  2008-06-30 19:23 ` Mark van Doesburg
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Hans de Goede @ 2008-06-25  7:22 UTC (permalink / raw)
  To: lm-sensors

Mark van Doesburg wrote:
> Hi,
> 
> I've finally added some missing features to the driver.

Good work, thanks!

> There are still
> some things unimplemented, such as:
> 
> 	1. Changing the fan target reached timeout.
> 	2. Something to set FAN?_RATE_SEL.  
> 	3. FAN?_STOP_DUTY
> 	4. FAN?_JUMP_HIGH_EN and FAN?_JUMP_LOW_EN 
> 
> Mostly because I don't need them, and there is no default name for
> these features.
> 

Yes, I saw all those when reviewing the datasheet too, and I agree its best to 
just not export them.

> I did add one entry to set/reset the interpolation mode of the auto_point
> follower, to make the pwm control act as a P-regulator. Simply because
> I like it, it prevents rapid speedups and slowdowns of the fan.
> 

Ok.

> Since the auto_point follower is able to track only one temperature at
> a time, I gave a preference to the lowest numbered one. Maybe returning
> an error would be better if the user tries to set an impossible value ?
> 

Yes, please just return -EINVAL on an impossible value.

I've taken a quick peek at it, and all in all it looks good, except for the 
stuffing of pwm and point together in one integer that isn't necessary, I 
believe it would be the best to instead use struct sensor_device_attribute_2 
instead of struct sensor_device_attribute for the f71882fg_fan_attr array, as 
that allows specifying both an index and a nr per attr, so instead of:

static struct sensor_device_attribute f71882fg_fan_attr[] {
   	SENSOR_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0),
...
	SENSOR_ATTR(pwm1_auto_point1_pwm, S_IRUGO|S_IWUSR,
		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
		AUTO_POINT(0,0)),


You would write:

static struct sensor_device_attribute f71882fg_fan_attr[] {
	SENSOR_ATTR_2(fan1_input, S_IRUGO, show_fan, NULL, 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),


And then instead of:

static ssize_t store_pwm_auto_point_temp(struct device *dev,
	struct device_attribute *devattr,
	const char *buf, size_t count)
{
	int point, pwm;
	struct f71882fg_data *data = f71882fg_update_device(dev);
	int nr = to_sensor_dev_attr(devattr)->index;
	int val = simple_strtoul(buf, NULL, 10);

	pwm=AUTO_POINT_PWM(nr);
	point=AUTO_POINT_POINT(nr);
...

You would write:

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 = 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);
...


If you could make a new patch with this changed (and returning -EINVAL for 
trying to bind a auto pwm to more then one temp), I'll do a full review and run 
a few tests on my f71882fg test system. I think it would be best if you could 
split this new patch in 2 parts:
1) Convert the current f71882.c from "struct sensor_device_attribute" ->
    "struct sensor_device_attribute_2" without any functional changes
2) Your patch adding pwm support (thanks!!) on top of this

I would like to see it this way for easier review.

Thanks & Regards,

Hans


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [lm-sensors] [PATCH 2.6.25.4] f71882.c driver,
  2008-06-11  5:20 [lm-sensors] [PATCH 2.6.25.4] f71882.c driver, Mark van Doesburg
                   ` (4 preceding siblings ...)
  2008-06-25  7:22 ` Hans de Goede
@ 2008-06-30 19:23 ` Mark van Doesburg
  2008-06-30 21:02 ` Mark van Doesburg
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Mark van Doesburg @ 2008-06-30 19:23 UTC (permalink / raw)
  To: lm-sensors

Hello Hans,

I've finisched the two patches, so here they are.

Regards,

Mark.


--- /tmp/linux-2.6.25.4/drivers/hwmon/f71882fg.c	2008-05-15 17:00:12.000000000 +0200
+++ drivers/hwmon/f71882fg.c	2008-06-30 21:02:45.000000000 +0200
@@ -194,79 +194,79 @@
 	__ATTR( name, S_IRUGO, show_name, NULL ),
 };
 
-static struct sensor_device_attribute f71882fg_in_temp_attr[] +static struct sensor_device_attribute_2 f71882fg_in_temp_attr[]  {
-	SENSOR_ATTR(in0_input, S_IRUGO, show_in, NULL, 0),
-	SENSOR_ATTR(in1_input, S_IRUGO, show_in, NULL, 1),
-	SENSOR_ATTR(in1_max, S_IRUGO|S_IWUSR, show_in_max, store_in_max, 1),
-	SENSOR_ATTR(in1_beep, S_IRUGO|S_IWUSR, show_in_beep, store_in_beep, 1),
-	SENSOR_ATTR(in1_alarm, S_IRUGO, show_in_alarm, NULL, 1),
-	SENSOR_ATTR(in2_input, S_IRUGO, show_in, NULL, 2),
-	SENSOR_ATTR(in3_input, S_IRUGO, show_in, NULL, 3),
-	SENSOR_ATTR(in4_input, S_IRUGO, show_in, NULL, 4),
-	SENSOR_ATTR(in5_input, S_IRUGO, show_in, NULL, 5),
-	SENSOR_ATTR(in6_input, S_IRUGO, show_in, NULL, 6),
-	SENSOR_ATTR(in7_input, S_IRUGO, show_in, NULL, 7),
-	SENSOR_ATTR(in8_input, S_IRUGO, show_in, NULL, 8),
-	SENSOR_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0),
-	SENSOR_ATTR(temp1_max, S_IRUGO|S_IWUSR, show_temp_max,
-		store_temp_max, 0),
-	SENSOR_ATTR(temp1_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
-		store_temp_max_hyst, 0),
-	SENSOR_ATTR(temp1_crit, S_IRUGO|S_IWUSR, show_temp_crit,
-		store_temp_crit, 0),
-	SENSOR_ATTR(temp1_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0),
-	SENSOR_ATTR(temp1_type, S_IRUGO, show_temp_type, NULL, 0),
-	SENSOR_ATTR(temp1_beep, S_IRUGO|S_IWUSR, show_temp_beep,
-		store_temp_beep, 0),
-	SENSOR_ATTR(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0),
-	SENSOR_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0),
-	SENSOR_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1),
-	SENSOR_ATTR(temp2_max, S_IRUGO|S_IWUSR, show_temp_max,
-		store_temp_max, 1),
-	SENSOR_ATTR(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
-		store_temp_max_hyst, 1),
-	SENSOR_ATTR(temp2_crit, S_IRUGO|S_IWUSR, show_temp_crit,
-		store_temp_crit, 1),
-	SENSOR_ATTR(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 1),
-	SENSOR_ATTR(temp2_type, S_IRUGO, show_temp_type, NULL, 1),
-	SENSOR_ATTR(temp2_beep, S_IRUGO|S_IWUSR, show_temp_beep,
-		store_temp_beep, 1),
-	SENSOR_ATTR(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 1),
-	SENSOR_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1),
-	SENSOR_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2),
-	SENSOR_ATTR(temp3_max, S_IRUGO|S_IWUSR, show_temp_max,
-		store_temp_max, 2),
-	SENSOR_ATTR(temp3_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
-		store_temp_max_hyst, 2),
-	SENSOR_ATTR(temp3_crit, S_IRUGO|S_IWUSR, show_temp_crit,
-		store_temp_crit, 2),
-	SENSOR_ATTR(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 2),
-	SENSOR_ATTR(temp3_type, S_IRUGO, show_temp_type, NULL, 2),
-	SENSOR_ATTR(temp3_beep, S_IRUGO|S_IWUSR, show_temp_beep,
-		store_temp_beep, 2),
-	SENSOR_ATTR(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 2),
-	SENSOR_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2)
+	SENSOR_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, 0),
+	SENSOR_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 1, 0),
+	SENSOR_ATTR_2(in1_max, S_IRUGO|S_IWUSR, show_in_max, store_in_max, 1, 0),
+	SENSOR_ATTR_2(in1_beep, S_IRUGO|S_IWUSR, show_in_beep, store_in_beep, 1, 0),
+	SENSOR_ATTR_2(in1_alarm, S_IRUGO, show_in_alarm, NULL, 1, 0),
+	SENSOR_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 2, 0),
+	SENSOR_ATTR_2(in3_input, S_IRUGO, show_in, NULL, 3, 0),
+	SENSOR_ATTR_2(in4_input, S_IRUGO, show_in, NULL, 4, 0),
+	SENSOR_ATTR_2(in5_input, S_IRUGO, show_in, NULL, 5, 0),
+	SENSOR_ATTR_2(in6_input, S_IRUGO, show_in, NULL, 6, 0),
+	SENSOR_ATTR_2(in7_input, S_IRUGO, show_in, NULL, 7, 0),
+	SENSOR_ATTR_2(in8_input, S_IRUGO, show_in, NULL, 8, 0),
+	SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0),
+	SENSOR_ATTR_2(temp1_max, S_IRUGO|S_IWUSR, show_temp_max,
+		store_temp_max, 0, 0),
+	SENSOR_ATTR_2(temp1_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
+		store_temp_max_hyst, 0, 0),
+	SENSOR_ATTR_2(temp1_crit, S_IRUGO|S_IWUSR, show_temp_crit,
+		store_temp_crit, 0, 0),
+	SENSOR_ATTR_2(temp1_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0, 0),
+	SENSOR_ATTR_2(temp1_type, S_IRUGO, show_temp_type, NULL, 0, 0),
+	SENSOR_ATTR_2(temp1_beep, S_IRUGO|S_IWUSR, show_temp_beep,
+		store_temp_beep, 0, 0),
+	SENSOR_ATTR_2(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 0),
+	SENSOR_ATTR_2(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0, 0),
+	SENSOR_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 1, 0),
+	SENSOR_ATTR_2(temp2_max, S_IRUGO|S_IWUSR, show_temp_max,
+		store_temp_max, 1, 0),
+	SENSOR_ATTR_2(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
+		store_temp_max_hyst, 1, 0),
+	SENSOR_ATTR_2(temp2_crit, S_IRUGO|S_IWUSR, show_temp_crit,
+		store_temp_crit, 1, 0),
+	SENSOR_ATTR_2(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 1, 0),
+	SENSOR_ATTR_2(temp2_type, S_IRUGO, show_temp_type, NULL, 1, 0),
+	SENSOR_ATTR_2(temp2_beep, S_IRUGO|S_IWUSR, show_temp_beep,
+		store_temp_beep, 1, 0),
+	SENSOR_ATTR_2(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 1, 0),
+	SENSOR_ATTR_2(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1, 0),
+	SENSOR_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 2, 0),
+	SENSOR_ATTR_2(temp3_max, S_IRUGO|S_IWUSR, show_temp_max,
+		store_temp_max, 2, 0),
+	SENSOR_ATTR_2(temp3_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
+		store_temp_max_hyst, 2, 0),
+	SENSOR_ATTR_2(temp3_crit, S_IRUGO|S_IWUSR, show_temp_crit,
+		store_temp_crit, 2, 0),
+	SENSOR_ATTR_2(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 2, 0),
+	SENSOR_ATTR_2(temp3_type, S_IRUGO, show_temp_type, NULL, 2, 0),
+	SENSOR_ATTR_2(temp3_beep, S_IRUGO|S_IWUSR, show_temp_beep,
+		store_temp_beep, 2, 0),
+	SENSOR_ATTR_2(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 2, 0),
+	SENSOR_ATTR_2(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2, 0)
 };
 
-static struct sensor_device_attribute f71882fg_fan_attr[] +static struct sensor_device_attribute_2 f71882fg_fan_attr[]  {
-	SENSOR_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0),
-	SENSOR_ATTR(fan1_beep, S_IRUGO|S_IWUSR, show_fan_beep,
-		store_fan_beep, 0),
-	SENSOR_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0),
-	SENSOR_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1),
-	SENSOR_ATTR(fan2_beep, S_IRUGO|S_IWUSR, show_fan_beep,
-		store_fan_beep, 1),
-	SENSOR_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1),
-	SENSOR_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2),
-	SENSOR_ATTR(fan3_beep, S_IRUGO|S_IWUSR, show_fan_beep,
-		store_fan_beep, 2),
-	SENSOR_ATTR(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 2),
-	SENSOR_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3),
-	SENSOR_ATTR(fan4_beep, S_IRUGO|S_IWUSR, show_fan_beep,
-		store_fan_beep, 3),
-	SENSOR_ATTR(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 3)
+	SENSOR_ATTR_2(fan1_input, S_IRUGO, show_fan, NULL, 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, 1, 0),
+	SENSOR_ATTR_2(fan2_beep, S_IRUGO|S_IWUSR, show_fan_beep,
+		store_fan_beep, 1, 0),
+	SENSOR_ATTR_2(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1, 0),
+	SENSOR_ATTR_2(fan3_input, S_IRUGO, show_fan, NULL, 2, 0),
+	SENSOR_ATTR_2(fan3_beep, S_IRUGO|S_IWUSR, show_fan_beep,
+		store_fan_beep, 2, 0),
+	SENSOR_ATTR_2(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 2, 0),
+	SENSOR_ATTR_2(fan4_input, S_IRUGO, show_fan, NULL, 3, 0),
+	SENSOR_ATTR_2(fan4_beep, S_IRUGO|S_IWUSR, show_fan_beep,
+		store_fan_beep, 3, 0),
+	SENSOR_ATTR_2(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 3, 0)
 };
 
 
@@ -423,7 +423,7 @@
 	char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 	int speed = fan_from_reg(data->fan[nr]);
 
 	if (speed = FAN_MIN_DETECT)
@@ -436,7 +436,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	if (data->fan_beep & (1 << nr))
 		return sprintf(buf, "1\n");
@@ -448,7 +448,7 @@
 	*devattr, const char *buf, size_t count)
 {
 	struct f71882fg_data *data = dev_get_drvdata(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 	int val = simple_strtoul(buf, NULL, 10);
 
 	mutex_lock(&data->update_lock);
@@ -467,7 +467,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	if (data->fan_status & (1 << nr))
 		return sprintf(buf, "1\n");
@@ -479,7 +479,7 @@
 	char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	return sprintf(buf, "%d\n", data->in[nr] * 8);
 }
@@ -513,7 +513,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	if (data->in_beep & (1 << nr))
 		return sprintf(buf, "1\n");
@@ -525,7 +525,7 @@
 	*devattr, const char *buf, size_t count)
 {
 	struct f71882fg_data *data = dev_get_drvdata(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 	int val = simple_strtoul(buf, NULL, 10);
 
 	mutex_lock(&data->update_lock);
@@ -544,7 +544,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	if (data->in_status & (1 << nr))
 		return sprintf(buf, "1\n");
@@ -556,7 +556,7 @@
 	char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	return sprintf(buf, "%d\n", data->temp[nr] * 1000);
 }
@@ -565,7 +565,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	return sprintf(buf, "%d\n", data->temp_high[nr] * 1000);
 }
@@ -574,7 +574,7 @@
 	*devattr, const char *buf, size_t count)
 {
 	struct f71882fg_data *data = dev_get_drvdata(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 	int val = simple_strtoul(buf, NULL, 10) / 1000;
 
 	if (val > 255)
@@ -592,7 +592,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	return sprintf(buf, "%d\n",
 		(data->temp_high[nr] - data->temp_hyst[nr]) * 1000);
@@ -602,7 +602,7 @@
 	*devattr, const char *buf, size_t count)
 {
 	struct f71882fg_data *data = dev_get_drvdata(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 	int val = simple_strtoul(buf, NULL, 10) / 1000;
 	ssize_t ret = count;
 
@@ -642,7 +642,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	return sprintf(buf, "%d\n", data->temp_ovt[nr] * 1000);
 }
@@ -651,7 +651,7 @@
 	*devattr, const char *buf, size_t count)
 {
 	struct f71882fg_data *data = dev_get_drvdata(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 	int val = simple_strtoul(buf, NULL, 10) / 1000;
 
 	if (val > 255)
@@ -669,7 +669,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	return sprintf(buf, "%d\n",
 		(data->temp_ovt[nr] - data->temp_hyst[nr]) * 1000);
@@ -679,7 +679,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	return sprintf(buf, "%d\n", data->temp_type[nr]);
 }
@@ -688,7 +688,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	if (data->temp_beep & (1 << (nr + 1)))
 		return sprintf(buf, "1\n");
@@ -700,7 +700,7 @@
 	*devattr, const char *buf, size_t count)
 {
 	struct f71882fg_data *data = dev_get_drvdata(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 	int val = simple_strtoul(buf, NULL, 10);
 
 	mutex_lock(&data->update_lock);
@@ -719,7 +719,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	if (data->temp_status & (1 << (nr + 1)))
 		return sprintf(buf, "1\n");
@@ -731,7 +731,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	if (data->temp_diode_open & (1 << (nr + 1)))
 		return sprintf(buf, "1\n");


--- attr2	2008-06-30 21:02:45.000000000 +0200
+++ f71882fg.c	2008-06-30 21:12:57.000000000 +0200
@@ -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
 
@@ -70,6 +72,18 @@
 #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 +92,12 @@
 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, 0);
+MODULE_PARM_DESC(fan_mode, 
+	"List of fan modes (for four fans) (0=don't change, 1=pwm, 2=rpm");
+
+
 static struct platform_device *f71882fg_pdev = NULL;
 
 /* Super-I/O Function prototypes */
@@ -88,6 +108,7 @@
 static inline void superio_exit(int base);
 
 static inline u16 fan_from_reg ( u16 reg );
+static inline u16 fan_to_reg ( u16 reg );
 
 struct f71882fg_data {
 	unsigned short addr;
@@ -104,6 +125,8 @@
 	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];
@@ -114,11 +137,19 @@
 	u8	temp_status;
 	u8	temp_beep;
 	u8	temp_diode_open;
+	u8	pwm[4];
+	u8	pwm_enable;
+	u8	pwm_type;
+	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];
 };
 
 static u8 f71882fg_read8(struct f71882fg_data *data, u8 reg);
 static u16 f71882fg_read16(struct f71882fg_data *data, u8 reg);
 static void f71882fg_write8(struct f71882fg_data *data, u8 reg, u8 val);
+static void f71882fg_write16(struct f71882fg_data *data, u8 reg, u16 val);
 
 /* Sysfs in*/
 static ssize_t show_in(struct device *dev, struct device_attribute *devattr,
@@ -136,6 +167,10 @@
 /* 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
@@ -173,6 +208,36 @@
 static ssize_t show_name(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);
+
 static int __devinit f71882fg_probe(struct platform_device * pdev);
 static int __devexit f71882fg_remove(struct platform_device *pdev);
 static int __init f71882fg_init(void);
@@ -252,21 +317,181 @@
 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, 1, 0),
+	SENSOR_ATTR_2(fan2_full_speed, S_IRUGO|S_IWUSR, show_fan_full_speed, 
+		store_fan_full_speed, 1, 0),
 	SENSOR_ATTR_2(fan2_beep, S_IRUGO|S_IWUSR, show_fan_beep,
 		store_fan_beep, 1, 0),
 	SENSOR_ATTR_2(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1, 0),
 	SENSOR_ATTR_2(fan3_input, S_IRUGO, show_fan, NULL, 2, 0),
+	SENSOR_ATTR_2(fan3_full_speed, S_IRUGO|S_IWUSR, show_fan_full_speed, 
+		store_fan_full_speed, 2, 0),
 	SENSOR_ATTR_2(fan3_beep, S_IRUGO|S_IWUSR, show_fan_beep,
 		store_fan_beep, 2, 0),
 	SENSOR_ATTR_2(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 2, 0),
 	SENSOR_ATTR_2(fan4_input, S_IRUGO, show_fan, NULL, 3, 0),
+	SENSOR_ATTR_2(fan4_full_speed, S_IRUGO|S_IWUSR, show_fan_full_speed, 
+		store_fan_full_speed, 3, 0),
 	SENSOR_ATTR_2(fan4_beep, S_IRUGO|S_IWUSR, show_fan_beep,
 		store_fan_beep, 3, 0),
-	SENSOR_ATTR_2(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 3, 0)
+	SENSOR_ATTR_2(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 3, 0),
+
+	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_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_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, 
+		0, 1),
+	SENSOR_ATTR_2(pwm1_auto_point3_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		0, 2),
+	SENSOR_ATTR_2(pwm1_auto_point4_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		0, 3),
+	SENSOR_ATTR_2(pwm1_auto_point5_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		0, 4),
+	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, 
+		0, 1),
+	SENSOR_ATTR_2(pwm1_auto_point3_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		0, 2),
+	SENSOR_ATTR_2(pwm1_auto_point4_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		0, 3),
+
+	SENSOR_ATTR_2(pwm2, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 1, 0),
+	SENSOR_ATTR_2(pwm2_enable, S_IRUGO|S_IWUSR, show_pwm_enable, 
+		store_pwm_enable, 1, 0),
+	SENSOR_ATTR_2(pwm2_interpolate, S_IRUGO|S_IWUSR, 
+		show_pwm_interpolate, store_pwm_interpolate, 1, 0),
+	SENSOR_ATTR_2(pwm2_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp_hyst, store_pwm_auto_point_temp_hyst, 
+		1, 0),
+	SENSOR_ATTR_2(pwm2_auto_channels_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_channel, store_pwm_auto_point_channel, 1, 0),
+	SENSOR_ATTR_2(pwm2_auto_point1_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		1, 0),
+	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, 
+		1, 2),
+	SENSOR_ATTR_2(pwm2_auto_point4_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		1, 3),
+	SENSOR_ATTR_2(pwm2_auto_point5_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		1, 4),
+	SENSOR_ATTR_2(pwm2_auto_point1_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		1, 0),
+	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, 
+		1, 2),
+	SENSOR_ATTR_2(pwm2_auto_point4_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		1, 3),
+
+	SENSOR_ATTR_2(pwm3, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 2, 0),
+	SENSOR_ATTR_2(pwm3_enable, S_IRUGO|S_IWUSR, show_pwm_enable, 
+		store_pwm_enable, 2, 0),
+	SENSOR_ATTR_2(pwm3_interpolate, S_IRUGO|S_IWUSR, 
+		show_pwm_interpolate, store_pwm_interpolate, 2, 0),
+	SENSOR_ATTR_2(pwm3_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp_hyst, store_pwm_auto_point_temp_hyst, 
+		2, 0),
+	SENSOR_ATTR_2(pwm3_auto_channels_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_channel, store_pwm_auto_point_channel, 2, 0),
+	SENSOR_ATTR_2(pwm3_auto_point1_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		2, 0),
+	SENSOR_ATTR_2(pwm3_auto_point2_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		2, 1),
+	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, 
+		2, 3),
+	SENSOR_ATTR_2(pwm3_auto_point5_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		2, 4),
+	SENSOR_ATTR_2(pwm3_auto_point1_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		2, 0),
+	SENSOR_ATTR_2(pwm3_auto_point2_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		2, 1),
+	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, 
+		2, 3),
+
+	SENSOR_ATTR_2(pwm4, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 3, 0),
+	SENSOR_ATTR_2(pwm4_enable, S_IRUGO|S_IWUSR, show_pwm_enable, 
+		store_pwm_enable, 3, 0),
+	SENSOR_ATTR_2(pwm4_interpolate, S_IRUGO|S_IWUSR, 
+		show_pwm_interpolate, store_pwm_interpolate, 3, 0),
+	SENSOR_ATTR_2(pwm4_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp_hyst, store_pwm_auto_point_temp_hyst, 
+		3, 0),
+	SENSOR_ATTR_2(pwm4_auto_channels_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_channel, store_pwm_auto_point_channel, 3, 0),
+	SENSOR_ATTR_2(pwm4_auto_point1_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		3, 0),
+	SENSOR_ATTR_2(pwm4_auto_point2_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		3, 1),
+	SENSOR_ATTR_2(pwm4_auto_point3_pwm, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, 
+		3, 2),
+	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, 
+		3, 4),
+	SENSOR_ATTR_2(pwm4_auto_point1_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		3, 0),
+	SENSOR_ATTR_2(pwm4_auto_point2_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		3, 1),
+	SENSOR_ATTR_2(pwm4_auto_point3_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		3, 2),
+	SENSOR_ATTR_2(pwm4_auto_point4_temp, S_IRUGO|S_IWUSR, 
+		show_pwm_auto_point_temp, store_pwm_auto_point_temp, 
+		3, 3)
 };
 
 
@@ -310,6 +535,11 @@
 	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;
@@ -338,6 +568,15 @@
 	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);
@@ -399,9 +638,37 @@
 
 		data->fan_status = f71882fg_read8(data,
 						F71882FG_REG_FAN_STATUS);
-		for (nr = 0; nr < 4; nr++)
+		data->pwm_type = f71882fg_read8(data,
+						F71882FG_REG_PWM_TYPE);
+		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->fan[nr] = f71882fg_read16(data,
-						F71882FG_REG_FAN(nr));
+				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->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));
+				data->pwm_auto_point_temp[nr][point]+					f71882fg_read8(data,
+						F71882FG_REG_POINT_TEMP(nr,
+							point));
+			}
+                }
 
 		data->in_status = f71882fg_read8(data,
 						F71882FG_REG_IN_STATUS);
@@ -432,6 +699,34 @@
 	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;
+	int val = simple_strtoul(buf, NULL, 10);
+
+	mutex_lock(&data->update_lock);
+	if(data->pwm_enable&(1<<(2*nr)))
+		count=-EINVAL;
+	else
+		f71882fg_write16(data, F71882FG_REG_FAN_FULL_SPEED(nr), 
+			fan_to_reg(val));
+	mutex_unlock(&data->update_lock);
+
+	return count;
+}
+
+
 static ssize_t show_fan_beep(struct device *dev, struct device_attribute
 	*devattr, char *buf)
 {
@@ -739,6 +1034,276 @@
 		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Úta->pwm[nr];
+	else
+		// RPM mode
+		val%5*fan_from_reg(data->fan_target[nr])
+			/fan_from_reg(data->fan_full_speed[nr]);
+	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;
+	int val = simple_strtoul(buf, NULL, 10);
+
+	mutex_lock(&data->update_lock);
+	if(data->pwm_enable&(1<<(2*nr)))
+		// PWM mode
+		f71882fg_write8(data, F71882FG_REG_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));
+	}
+	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(2&(data->pwm_enable>>(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;
+	int val = simple_strtoul(buf, NULL, 10);
+	if(val<0 || 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;	// RPM mode
+	case 2: data->pwm_enable&=~(1<<(2*nr)); break;	// Duty cycle mode
+	}
+	f71882fg_write8(data, F71882FG_REG_PWM_ENABLE, data->pwm_enable);
+	mutex_unlock(&data->update_lock);
+	if(!val)
+		store_pwm(dev,devattr,"255",4);
+
+	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Úta->pwm_auto_point_pwm[pwm][point];
+	} else {
+		// RPM mode
+		result2*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);
+
+	mutex_lock(&data->update_lock);
+	if(data->pwm_enable&(1<<(2*pwm))) {
+		// PWM mode
+		f71882fg_write8(data, F71882FG_REG_POINT_PWM(pwm,point), val);
+	} else {
+		// RPM mode
+		if(val<29)	// Prevent negative numbers
+			val%5;
+		else
+			val=(255-val)*32/val;
+		f71882fg_write8(data, F71882FG_REG_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;
+	struct f71882fg_data *data = f71882fg_update_device(dev);
+	int nr = to_sensor_dev_attr_2(devattr)->index;
+
+	switch(nr) {
+	case 0: resultÚta->pwm_auto_point_hyst[0]&15; break;
+	case 1: resultÚta->pwm_auto_point_hyst[0]>>4; break;
+	case 2: resultÚta->pwm_auto_point_hyst[1]&15; break;
+	case 3: resultÚta->pwm_auto_point_hyst[1]>>4; break;
+	default: result=-1;
+	}
+
+	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 = 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);
+	val&\x15;
+	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;
+	}
+
+	mutex_lock(&data->update_lock);
+	if(nr=0 || nr=1)
+		f71882fg_write8(data, F71882FG_REG_FAN_HYST0, val); 
+	else
+		f71882fg_write8(data, F71882FG_REG_FAN_HYST1, 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=1&(data->pwm_auto_point_mapping[nr]>>4);
+
+	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);
+	if(val)
+		valÚta->pwm_auto_point_mapping[nr]|(1<<4);
+	else
+		valÚta->pwm_auto_point_mapping[nr]&(~(1<<4));
+	mutex_lock(&data->update_lock);
+	f71882fg_write8(data, F71882FG_REG_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);
+	result>>=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;
+	int val = simple_strtoul(buf, NULL, 10);
+	switch(val) {
+	case 1: val=1; break;
+	case 2: val=2; break;
+	case 4: val=3; break;
+	default: return -EINVAL;
+	}
+	val|Úta->pwm_auto_point_mapping[nr]&0xfc;
+	mutex_lock(&data->update_lock);
+	f71882fg_write8(data, F71882FG_REG_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Úta->pwm_auto_point_temp[pwm][point];
+	return sprintf(buf, "%d\n", 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;
+	int val = simple_strtoul(buf, NULL, 10);
+
+	mutex_lock(&data->update_lock);
+	f71882fg_write8(data, F71882FG_REG_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)
 {


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [lm-sensors] [PATCH 2.6.25.4] f71882.c driver,
  2008-06-11  5:20 [lm-sensors] [PATCH 2.6.25.4] f71882.c driver, Mark van Doesburg
                   ` (5 preceding siblings ...)
  2008-06-30 19:23 ` Mark van Doesburg
@ 2008-06-30 21:02 ` Mark van Doesburg
  2008-08-04 14:05 ` Hans de Goede
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Mark van Doesburg @ 2008-06-30 21:02 UTC (permalink / raw)
  To: lm-sensors

Hello Hans,

I'm sorry for the previous mail, I got the parameters for ATTR_2 reversed.
So here are the patches again, and now it works.

Regards,

Mark.


--- /tmp/linux-2.6.25.4/drivers/hwmon/f71882fg.c	2008-05-15 17:00:12.000000000 +0200
+++ attr2	2008-06-30 22:57:54.000000000 +0200
@@ -194,79 +194,79 @@
 	__ATTR( name, S_IRUGO, show_name, NULL ),
 };
 
-static struct sensor_device_attribute f71882fg_in_temp_attr[] +static struct sensor_device_attribute_2 f71882fg_in_temp_attr[]  {
-	SENSOR_ATTR(in0_input, S_IRUGO, show_in, NULL, 0),
-	SENSOR_ATTR(in1_input, S_IRUGO, show_in, NULL, 1),
-	SENSOR_ATTR(in1_max, S_IRUGO|S_IWUSR, show_in_max, store_in_max, 1),
-	SENSOR_ATTR(in1_beep, S_IRUGO|S_IWUSR, show_in_beep, store_in_beep, 1),
-	SENSOR_ATTR(in1_alarm, S_IRUGO, show_in_alarm, NULL, 1),
-	SENSOR_ATTR(in2_input, S_IRUGO, show_in, NULL, 2),
-	SENSOR_ATTR(in3_input, S_IRUGO, show_in, NULL, 3),
-	SENSOR_ATTR(in4_input, S_IRUGO, show_in, NULL, 4),
-	SENSOR_ATTR(in5_input, S_IRUGO, show_in, NULL, 5),
-	SENSOR_ATTR(in6_input, S_IRUGO, show_in, NULL, 6),
-	SENSOR_ATTR(in7_input, S_IRUGO, show_in, NULL, 7),
-	SENSOR_ATTR(in8_input, S_IRUGO, show_in, NULL, 8),
-	SENSOR_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0),
-	SENSOR_ATTR(temp1_max, S_IRUGO|S_IWUSR, show_temp_max,
-		store_temp_max, 0),
-	SENSOR_ATTR(temp1_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
-		store_temp_max_hyst, 0),
-	SENSOR_ATTR(temp1_crit, S_IRUGO|S_IWUSR, show_temp_crit,
-		store_temp_crit, 0),
-	SENSOR_ATTR(temp1_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0),
-	SENSOR_ATTR(temp1_type, S_IRUGO, show_temp_type, NULL, 0),
-	SENSOR_ATTR(temp1_beep, S_IRUGO|S_IWUSR, show_temp_beep,
-		store_temp_beep, 0),
-	SENSOR_ATTR(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0),
-	SENSOR_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0),
-	SENSOR_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1),
-	SENSOR_ATTR(temp2_max, S_IRUGO|S_IWUSR, show_temp_max,
-		store_temp_max, 1),
-	SENSOR_ATTR(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
-		store_temp_max_hyst, 1),
-	SENSOR_ATTR(temp2_crit, S_IRUGO|S_IWUSR, show_temp_crit,
-		store_temp_crit, 1),
-	SENSOR_ATTR(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 1),
-	SENSOR_ATTR(temp2_type, S_IRUGO, show_temp_type, NULL, 1),
-	SENSOR_ATTR(temp2_beep, S_IRUGO|S_IWUSR, show_temp_beep,
-		store_temp_beep, 1),
-	SENSOR_ATTR(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 1),
-	SENSOR_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1),
-	SENSOR_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2),
-	SENSOR_ATTR(temp3_max, S_IRUGO|S_IWUSR, show_temp_max,
-		store_temp_max, 2),
-	SENSOR_ATTR(temp3_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
-		store_temp_max_hyst, 2),
-	SENSOR_ATTR(temp3_crit, S_IRUGO|S_IWUSR, show_temp_crit,
-		store_temp_crit, 2),
-	SENSOR_ATTR(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 2),
-	SENSOR_ATTR(temp3_type, S_IRUGO, show_temp_type, NULL, 2),
-	SENSOR_ATTR(temp3_beep, S_IRUGO|S_IWUSR, show_temp_beep,
-		store_temp_beep, 2),
-	SENSOR_ATTR(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 2),
-	SENSOR_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2)
+	SENSOR_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, 0),
+	SENSOR_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 0, 1),
+	SENSOR_ATTR_2(in1_max, S_IRUGO|S_IWUSR, show_in_max, store_in_max, 0, 1),
+	SENSOR_ATTR_2(in1_beep, S_IRUGO|S_IWUSR, show_in_beep, store_in_beep, 0, 1),
+	SENSOR_ATTR_2(in1_alarm, S_IRUGO, show_in_alarm, NULL, 0, 1),
+	SENSOR_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 0, 2),
+	SENSOR_ATTR_2(in3_input, S_IRUGO, show_in, NULL, 0, 3),
+	SENSOR_ATTR_2(in4_input, S_IRUGO, show_in, NULL, 0, 4),
+	SENSOR_ATTR_2(in5_input, S_IRUGO, show_in, NULL, 0, 5),
+	SENSOR_ATTR_2(in6_input, S_IRUGO, show_in, NULL, 0, 6),
+	SENSOR_ATTR_2(in7_input, S_IRUGO, show_in, NULL, 0, 7),
+	SENSOR_ATTR_2(in8_input, S_IRUGO, show_in, NULL, 0, 8),
+	SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0),
+	SENSOR_ATTR_2(temp1_max, S_IRUGO|S_IWUSR, show_temp_max,
+		store_temp_max, 0, 0),
+	SENSOR_ATTR_2(temp1_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
+		store_temp_max_hyst, 0, 0),
+	SENSOR_ATTR_2(temp1_crit, S_IRUGO|S_IWUSR, show_temp_crit,
+		store_temp_crit, 0, 0),
+	SENSOR_ATTR_2(temp1_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0, 0),
+	SENSOR_ATTR_2(temp1_type, S_IRUGO, show_temp_type, NULL, 0, 0),
+	SENSOR_ATTR_2(temp1_beep, S_IRUGO|S_IWUSR, show_temp_beep,
+		store_temp_beep, 0, 0),
+	SENSOR_ATTR_2(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 0),
+	SENSOR_ATTR_2(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0, 0),
+	SENSOR_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 0, 1),
+	SENSOR_ATTR_2(temp2_max, S_IRUGO|S_IWUSR, show_temp_max,
+		store_temp_max, 0, 1),
+	SENSOR_ATTR_2(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
+		store_temp_max_hyst, 0, 1),
+	SENSOR_ATTR_2(temp2_crit, S_IRUGO|S_IWUSR, show_temp_crit,
+		store_temp_crit, 0, 1),
+	SENSOR_ATTR_2(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0, 1),
+	SENSOR_ATTR_2(temp2_type, S_IRUGO, show_temp_type, NULL, 0, 1),
+	SENSOR_ATTR_2(temp2_beep, S_IRUGO|S_IWUSR, show_temp_beep,
+		store_temp_beep, 0, 1),
+	SENSOR_ATTR_2(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 1),
+	SENSOR_ATTR_2(temp2_fault, S_IRUGO, show_temp_fault, NULL, 0, 1),
+	SENSOR_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 0, 2),
+	SENSOR_ATTR_2(temp3_max, S_IRUGO|S_IWUSR, show_temp_max,
+		store_temp_max, 0, 2),
+	SENSOR_ATTR_2(temp3_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
+		store_temp_max_hyst, 0, 2),
+	SENSOR_ATTR_2(temp3_crit, S_IRUGO|S_IWUSR, show_temp_crit,
+		store_temp_crit, 0, 2),
+	SENSOR_ATTR_2(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0, 2),
+	SENSOR_ATTR_2(temp3_type, S_IRUGO, show_temp_type, NULL, 0, 2),
+	SENSOR_ATTR_2(temp3_beep, S_IRUGO|S_IWUSR, show_temp_beep,
+		store_temp_beep, 0, 2),
+	SENSOR_ATTR_2(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 2),
+	SENSOR_ATTR_2(temp3_fault, S_IRUGO, show_temp_fault, NULL, 0, 2)
 };
 
-static struct sensor_device_attribute f71882fg_fan_attr[] +static struct sensor_device_attribute_2 f71882fg_fan_attr[]  {
-	SENSOR_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0),
-	SENSOR_ATTR(fan1_beep, S_IRUGO|S_IWUSR, show_fan_beep,
-		store_fan_beep, 0),
-	SENSOR_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0),
-	SENSOR_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1),
-	SENSOR_ATTR(fan2_beep, S_IRUGO|S_IWUSR, show_fan_beep,
-		store_fan_beep, 1),
-	SENSOR_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1),
-	SENSOR_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2),
-	SENSOR_ATTR(fan3_beep, S_IRUGO|S_IWUSR, show_fan_beep,
-		store_fan_beep, 2),
-	SENSOR_ATTR(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 2),
-	SENSOR_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3),
-	SENSOR_ATTR(fan4_beep, S_IRUGO|S_IWUSR, show_fan_beep,
-		store_fan_beep, 3),
-	SENSOR_ATTR(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 3)
+	SENSOR_ATTR_2(fan1_input, S_IRUGO, show_fan, NULL, 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_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_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_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)
 };
 
 
@@ -423,7 +423,7 @@
 	char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 	int speed = fan_from_reg(data->fan[nr]);
 
 	if (speed = FAN_MIN_DETECT)
@@ -436,7 +436,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	if (data->fan_beep & (1 << nr))
 		return sprintf(buf, "1\n");
@@ -448,7 +448,7 @@
 	*devattr, const char *buf, size_t count)
 {
 	struct f71882fg_data *data = dev_get_drvdata(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 	int val = simple_strtoul(buf, NULL, 10);
 
 	mutex_lock(&data->update_lock);
@@ -467,7 +467,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	if (data->fan_status & (1 << nr))
 		return sprintf(buf, "1\n");
@@ -479,7 +479,7 @@
 	char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	return sprintf(buf, "%d\n", data->in[nr] * 8);
 }
@@ -513,7 +513,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	if (data->in_beep & (1 << nr))
 		return sprintf(buf, "1\n");
@@ -525,7 +525,7 @@
 	*devattr, const char *buf, size_t count)
 {
 	struct f71882fg_data *data = dev_get_drvdata(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 	int val = simple_strtoul(buf, NULL, 10);
 
 	mutex_lock(&data->update_lock);
@@ -544,7 +544,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	if (data->in_status & (1 << nr))
 		return sprintf(buf, "1\n");
@@ -556,7 +556,7 @@
 	char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	return sprintf(buf, "%d\n", data->temp[nr] * 1000);
 }
@@ -565,7 +565,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	return sprintf(buf, "%d\n", data->temp_high[nr] * 1000);
 }
@@ -574,7 +574,7 @@
 	*devattr, const char *buf, size_t count)
 {
 	struct f71882fg_data *data = dev_get_drvdata(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 	int val = simple_strtoul(buf, NULL, 10) / 1000;
 
 	if (val > 255)
@@ -592,7 +592,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	return sprintf(buf, "%d\n",
 		(data->temp_high[nr] - data->temp_hyst[nr]) * 1000);
@@ -602,7 +602,7 @@
 	*devattr, const char *buf, size_t count)
 {
 	struct f71882fg_data *data = dev_get_drvdata(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 	int val = simple_strtoul(buf, NULL, 10) / 1000;
 	ssize_t ret = count;
 
@@ -642,7 +642,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	return sprintf(buf, "%d\n", data->temp_ovt[nr] * 1000);
 }
@@ -651,7 +651,7 @@
 	*devattr, const char *buf, size_t count)
 {
 	struct f71882fg_data *data = dev_get_drvdata(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 	int val = simple_strtoul(buf, NULL, 10) / 1000;
 
 	if (val > 255)
@@ -669,7 +669,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	return sprintf(buf, "%d\n",
 		(data->temp_ovt[nr] - data->temp_hyst[nr]) * 1000);
@@ -679,7 +679,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	return sprintf(buf, "%d\n", data->temp_type[nr]);
 }
@@ -688,7 +688,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	if (data->temp_beep & (1 << (nr + 1)))
 		return sprintf(buf, "1\n");
@@ -700,7 +700,7 @@
 	*devattr, const char *buf, size_t count)
 {
 	struct f71882fg_data *data = dev_get_drvdata(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 	int val = simple_strtoul(buf, NULL, 10);
 
 	mutex_lock(&data->update_lock);
@@ -719,7 +719,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	if (data->temp_status & (1 << (nr + 1)))
 		return sprintf(buf, "1\n");
@@ -731,7 +731,7 @@
 	*devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
-	int nr = to_sensor_dev_attr(devattr)->index;
+	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	if (data->temp_diode_open & (1 << (nr + 1)))
 		return sprintf(buf, "1\n");



--- attr2	2008-06-30 22:57:54.000000000 +0200
+++ f71882fg.c	2008-06-30 22:54:22.000000000 +0200
@@ -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
 
@@ -70,6 +72,18 @@
 #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 +92,12 @@
 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, 0);
+MODULE_PARM_DESC(fan_mode, 
+	"List of fan modes (for four fans) (0=don't change, 1=pwm, 2=rpm");
+
+
 static struct platform_device *f71882fg_pdev = NULL;
 
 /* Super-I/O Function prototypes */
@@ -88,6 +108,7 @@
 static inline void superio_exit(int base);
 
 static inline u16 fan_from_reg ( u16 reg );
+static inline u16 fan_to_reg ( u16 reg );
 
 struct f71882fg_data {
 	unsigned short addr;
@@ -104,6 +125,8 @@
 	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];
@@ -114,11 +137,19 @@
 	u8	temp_status;
 	u8	temp_beep;
 	u8	temp_diode_open;
+	u8	pwm[4];
+	u8	pwm_enable;
+	u8	pwm_type;
+	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];
 };
 
 static u8 f71882fg_read8(struct f71882fg_data *data, u8 reg);
 static u16 f71882fg_read16(struct f71882fg_data *data, u8 reg);
 static void f71882fg_write8(struct f71882fg_data *data, u8 reg, u8 val);
+static void f71882fg_write16(struct f71882fg_data *data, u8 reg, u16 val);
 
 /* Sysfs in*/
 static ssize_t show_in(struct device *dev, struct device_attribute *devattr,
@@ -136,6 +167,10 @@
 /* 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
@@ -173,6 +208,36 @@
 static ssize_t show_name(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);
+
 static int __devinit f71882fg_probe(struct platform_device * pdev);
 static int __devexit f71882fg_remove(struct platform_device *pdev);
 static int __init f71882fg_init(void);
@@ -252,21 +317,181 @@
 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(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_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_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(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_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_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(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_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_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(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_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_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)
 };
 
 
@@ -310,6 +535,11 @@
 	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;
@@ -338,6 +568,15 @@
 	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);
@@ -399,9 +638,37 @@
 
 		data->fan_status = f71882fg_read8(data,
 						F71882FG_REG_FAN_STATUS);
-		for (nr = 0; nr < 4; nr++)
+		data->pwm_type = f71882fg_read8(data,
+						F71882FG_REG_PWM_TYPE);
+		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->fan[nr] = f71882fg_read16(data,
-						F71882FG_REG_FAN(nr));
+				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->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));
+				data->pwm_auto_point_temp[nr][point]+					f71882fg_read8(data,
+						F71882FG_REG_POINT_TEMP(nr,
+							point));
+			}
+                }
 
 		data->in_status = f71882fg_read8(data,
 						F71882FG_REG_IN_STATUS);
@@ -432,6 +699,34 @@
 	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;
+	int val = simple_strtoul(buf, NULL, 10);
+
+	mutex_lock(&data->update_lock);
+	if(data->pwm_enable&(1<<(2*nr)))
+		count=-EINVAL;
+	else
+		f71882fg_write16(data, F71882FG_REG_FAN_FULL_SPEED(nr), 
+			fan_to_reg(val));
+	mutex_unlock(&data->update_lock);
+
+	return count;
+}
+
+
 static ssize_t show_fan_beep(struct device *dev, struct device_attribute
 	*devattr, char *buf)
 {
@@ -739,6 +1034,276 @@
 		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Úta->pwm[nr];
+	else
+		// RPM mode
+		val%5*fan_from_reg(data->fan_target[nr])
+			/fan_from_reg(data->fan_full_speed[nr]);
+	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;
+	int val = simple_strtoul(buf, NULL, 10);
+
+	mutex_lock(&data->update_lock);
+	if(data->pwm_enable&(1<<(2*nr)))
+		// PWM mode
+		f71882fg_write8(data, F71882FG_REG_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));
+	}
+	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(2&(data->pwm_enable>>(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;
+	int val = simple_strtoul(buf, NULL, 10);
+	if(val<0 || 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;	// RPM mode
+	case 2: data->pwm_enable&=~(1<<(2*nr)); break;	// Duty cycle mode
+	}
+	f71882fg_write8(data, F71882FG_REG_PWM_ENABLE, data->pwm_enable);
+	mutex_unlock(&data->update_lock);
+	if(!val)
+		store_pwm(dev,devattr,"255",4);
+
+	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Úta->pwm_auto_point_pwm[pwm][point];
+	} else {
+		// RPM mode
+		result2*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);
+
+	mutex_lock(&data->update_lock);
+	if(data->pwm_enable&(1<<(2*pwm))) {
+		// PWM mode
+		f71882fg_write8(data, F71882FG_REG_POINT_PWM(pwm,point), val);
+	} else {
+		// RPM mode
+		if(val<29)	// Prevent negative numbers
+			val%5;
+		else
+			val=(255-val)*32/val;
+		f71882fg_write8(data, F71882FG_REG_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;
+	struct f71882fg_data *data = f71882fg_update_device(dev);
+	int nr = to_sensor_dev_attr_2(devattr)->index;
+
+	switch(nr) {
+	case 0: resultÚta->pwm_auto_point_hyst[0]&15; break;
+	case 1: resultÚta->pwm_auto_point_hyst[0]>>4; break;
+	case 2: resultÚta->pwm_auto_point_hyst[1]&15; break;
+	case 3: resultÚta->pwm_auto_point_hyst[1]>>4; break;
+	default: result=-1;
+	}
+
+	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 = 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);
+	val&\x15;
+	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;
+	}
+
+	mutex_lock(&data->update_lock);
+	if(nr=0 || nr=1)
+		f71882fg_write8(data, F71882FG_REG_FAN_HYST0, val); 
+	else
+		f71882fg_write8(data, F71882FG_REG_FAN_HYST1, 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=1&(data->pwm_auto_point_mapping[nr]>>4);
+
+	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);
+	if(val)
+		valÚta->pwm_auto_point_mapping[nr]|(1<<4);
+	else
+		valÚta->pwm_auto_point_mapping[nr]&(~(1<<4));
+	mutex_lock(&data->update_lock);
+	f71882fg_write8(data, F71882FG_REG_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);
+	result>>=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;
+	int val = simple_strtoul(buf, NULL, 10);
+	switch(val) {
+	case 1: val=1; break;
+	case 2: val=2; break;
+	case 4: val=3; break;
+	default: return -EINVAL;
+	}
+	val|Úta->pwm_auto_point_mapping[nr]&0xfc;
+	mutex_lock(&data->update_lock);
+	f71882fg_write8(data, F71882FG_REG_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Úta->pwm_auto_point_temp[pwm][point];
+	return sprintf(buf, "%d\n", 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;
+	int val = simple_strtoul(buf, NULL, 10);
+
+	mutex_lock(&data->update_lock);
+	f71882fg_write8(data, F71882FG_REG_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)
 {


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [lm-sensors] [PATCH 2.6.25.4] f71882.c driver,
  2008-06-11  5:20 [lm-sensors] [PATCH 2.6.25.4] f71882.c driver, Mark van Doesburg
                   ` (6 preceding siblings ...)
  2008-06-30 21:02 ` Mark van Doesburg
@ 2008-08-04 14:05 ` Hans de Goede
  2008-10-07 18:36 ` Mark van Doesburg
  2008-10-10 22:57 ` Andrew Morton
  9 siblings, 0 replies; 11+ messages in thread
From: Hans de Goede @ 2008-08-04 14:05 UTC (permalink / raw)
  To: lm-sensors

Mark van Doesburg wrote:
> Hello Hans,
> 
> I'm sorry for the previous mail, I got the parameters for ATTR_2 reversed.
> So here are the patches again, and now it works.
> 
> Regards,
> 
> Mark.
> 

Hi Mark,

Sorry for taking so long, it has been a month since you submitted this patch :(

Anyways I've done a very detailed review, and you will need to fix some things 
before your patch can be applied. You can do so in another incremental patch, 
or redo the second patch of your set, whichever you prefer.

Here are the results of my review, I've been taking quick notes while digging 
through the code, so some things may be written down a bit short, if you don't 
understand please ask me to clarify!

MUST FIX
--------
* pwm#_point1_temp_hyst should be an absolute value (the temp value when the
   switch back to a lower speed zone is made), not a delta
* All pwm#_point#_temp's have a hyst, so report it for all, as its shared only
   make it writable for point1
* there are only 4 temp points per pwm but in f71882fg_update_device() 5 get
   read.
* in store_pwm_enable when disabling pwm not only set pwm to 255 but also make
   sure its not in automatic mode.
* forcing pwm and rpm *comments* are reversed in store_pwm_enable()
* in all store_foo functions update the copy of registers in your data struct
   when you write these registers, and do this before unlocking the data struct
   lock, otherwise a read done quickly after a write could return the old value.
   Or worse a write done quickly after another write on a register shared
   between multiple pwm's could undo the changes done to that reg for the other
   pwm!!
* in all store_foo functions take the lock earlier, before reading any
   register copies from the data struct, otherwise 2 writes running concurrently
   could result in bogus data being written when they use a shared register
* in all store_foo functions clamp the return value from simple_strtol to the
   allowable range to avoid writing bogus values like for example storing a pwm
   setting of 355 (max is 255, so clamp this to 255). Do this campling using
   the SENSORS_LIMIT macro, for more on this see the "sysfs attribute writes
   interpretation" chapter at the end of Documentation/hwmon/sysfs-interface


SHOULD FIX
----------
* fix many code style issues, the linux kernel code style mandates that you
   always put a space between an operation and its operands so do not write:
   "a+b" but write "a + b", there is a script called check-patch in the kernel
   tree which you can run on your patch which should detect most code style
   issues. Note that this script will complain about the use of simple_strtoul,
   ignore this, the script is right that recently a better solution has become
   available, but as all hwmon drivers currently use simple_strtoul, please
   keep using that for consistency.
* pwm_type is read but not used.
* in show_pwm_auto_point_temp_hyst() :
   * drop the default case in the switch statement, nr never is anything
     but 0 - 3, and if it would be anything different returning -EINVAL
     would be much more appropriate

Once I receive a new patch from you fixing all above MUST FIX items, I'll 
forward it to Andrew Morton for 2.6.27 inclusion (assuming you are quicker then 
me in fixing your patch).

Thans & Regards,

Hans



> 
> --- /tmp/linux-2.6.25.4/drivers/hwmon/f71882fg.c	2008-05-15 17:00:12.000000000 +0200
> +++ attr2	2008-06-30 22:57:54.000000000 +0200
> @@ -194,79 +194,79 @@
>  	__ATTR( name, S_IRUGO, show_name, NULL ),
>  };
>  
> -static struct sensor_device_attribute f71882fg_in_temp_attr[] > +static struct sensor_device_attribute_2 f71882fg_in_temp_attr[] >  {
> -	SENSOR_ATTR(in0_input, S_IRUGO, show_in, NULL, 0),
> -	SENSOR_ATTR(in1_input, S_IRUGO, show_in, NULL, 1),
> -	SENSOR_ATTR(in1_max, S_IRUGO|S_IWUSR, show_in_max, store_in_max, 1),
> -	SENSOR_ATTR(in1_beep, S_IRUGO|S_IWUSR, show_in_beep, store_in_beep, 1),
> -	SENSOR_ATTR(in1_alarm, S_IRUGO, show_in_alarm, NULL, 1),
> -	SENSOR_ATTR(in2_input, S_IRUGO, show_in, NULL, 2),
> -	SENSOR_ATTR(in3_input, S_IRUGO, show_in, NULL, 3),
> -	SENSOR_ATTR(in4_input, S_IRUGO, show_in, NULL, 4),
> -	SENSOR_ATTR(in5_input, S_IRUGO, show_in, NULL, 5),
> -	SENSOR_ATTR(in6_input, S_IRUGO, show_in, NULL, 6),
> -	SENSOR_ATTR(in7_input, S_IRUGO, show_in, NULL, 7),
> -	SENSOR_ATTR(in8_input, S_IRUGO, show_in, NULL, 8),
> -	SENSOR_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0),
> -	SENSOR_ATTR(temp1_max, S_IRUGO|S_IWUSR, show_temp_max,
> -		store_temp_max, 0),
> -	SENSOR_ATTR(temp1_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
> -		store_temp_max_hyst, 0),
> -	SENSOR_ATTR(temp1_crit, S_IRUGO|S_IWUSR, show_temp_crit,
> -		store_temp_crit, 0),
> -	SENSOR_ATTR(temp1_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0),
> -	SENSOR_ATTR(temp1_type, S_IRUGO, show_temp_type, NULL, 0),
> -	SENSOR_ATTR(temp1_beep, S_IRUGO|S_IWUSR, show_temp_beep,
> -		store_temp_beep, 0),
> -	SENSOR_ATTR(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0),
> -	SENSOR_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0),
> -	SENSOR_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1),
> -	SENSOR_ATTR(temp2_max, S_IRUGO|S_IWUSR, show_temp_max,
> -		store_temp_max, 1),
> -	SENSOR_ATTR(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
> -		store_temp_max_hyst, 1),
> -	SENSOR_ATTR(temp2_crit, S_IRUGO|S_IWUSR, show_temp_crit,
> -		store_temp_crit, 1),
> -	SENSOR_ATTR(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 1),
> -	SENSOR_ATTR(temp2_type, S_IRUGO, show_temp_type, NULL, 1),
> -	SENSOR_ATTR(temp2_beep, S_IRUGO|S_IWUSR, show_temp_beep,
> -		store_temp_beep, 1),
> -	SENSOR_ATTR(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 1),
> -	SENSOR_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1),
> -	SENSOR_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2),
> -	SENSOR_ATTR(temp3_max, S_IRUGO|S_IWUSR, show_temp_max,
> -		store_temp_max, 2),
> -	SENSOR_ATTR(temp3_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
> -		store_temp_max_hyst, 2),
> -	SENSOR_ATTR(temp3_crit, S_IRUGO|S_IWUSR, show_temp_crit,
> -		store_temp_crit, 2),
> -	SENSOR_ATTR(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 2),
> -	SENSOR_ATTR(temp3_type, S_IRUGO, show_temp_type, NULL, 2),
> -	SENSOR_ATTR(temp3_beep, S_IRUGO|S_IWUSR, show_temp_beep,
> -		store_temp_beep, 2),
> -	SENSOR_ATTR(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 2),
> -	SENSOR_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2)
> +	SENSOR_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, 0),
> +	SENSOR_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 0, 1),
> +	SENSOR_ATTR_2(in1_max, S_IRUGO|S_IWUSR, show_in_max, store_in_max, 0, 1),
> +	SENSOR_ATTR_2(in1_beep, S_IRUGO|S_IWUSR, show_in_beep, store_in_beep, 0, 1),
> +	SENSOR_ATTR_2(in1_alarm, S_IRUGO, show_in_alarm, NULL, 0, 1),
> +	SENSOR_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 0, 2),
> +	SENSOR_ATTR_2(in3_input, S_IRUGO, show_in, NULL, 0, 3),
> +	SENSOR_ATTR_2(in4_input, S_IRUGO, show_in, NULL, 0, 4),
> +	SENSOR_ATTR_2(in5_input, S_IRUGO, show_in, NULL, 0, 5),
> +	SENSOR_ATTR_2(in6_input, S_IRUGO, show_in, NULL, 0, 6),
> +	SENSOR_ATTR_2(in7_input, S_IRUGO, show_in, NULL, 0, 7),
> +	SENSOR_ATTR_2(in8_input, S_IRUGO, show_in, NULL, 0, 8),
> +	SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0),
> +	SENSOR_ATTR_2(temp1_max, S_IRUGO|S_IWUSR, show_temp_max,
> +		store_temp_max, 0, 0),
> +	SENSOR_ATTR_2(temp1_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
> +		store_temp_max_hyst, 0, 0),
> +	SENSOR_ATTR_2(temp1_crit, S_IRUGO|S_IWUSR, show_temp_crit,
> +		store_temp_crit, 0, 0),
> +	SENSOR_ATTR_2(temp1_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0, 0),
> +	SENSOR_ATTR_2(temp1_type, S_IRUGO, show_temp_type, NULL, 0, 0),
> +	SENSOR_ATTR_2(temp1_beep, S_IRUGO|S_IWUSR, show_temp_beep,
> +		store_temp_beep, 0, 0),
> +	SENSOR_ATTR_2(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 0),
> +	SENSOR_ATTR_2(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0, 0),
> +	SENSOR_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 0, 1),
> +	SENSOR_ATTR_2(temp2_max, S_IRUGO|S_IWUSR, show_temp_max,
> +		store_temp_max, 0, 1),
> +	SENSOR_ATTR_2(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
> +		store_temp_max_hyst, 0, 1),
> +	SENSOR_ATTR_2(temp2_crit, S_IRUGO|S_IWUSR, show_temp_crit,
> +		store_temp_crit, 0, 1),
> +	SENSOR_ATTR_2(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0, 1),
> +	SENSOR_ATTR_2(temp2_type, S_IRUGO, show_temp_type, NULL, 0, 1),
> +	SENSOR_ATTR_2(temp2_beep, S_IRUGO|S_IWUSR, show_temp_beep,
> +		store_temp_beep, 0, 1),
> +	SENSOR_ATTR_2(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 1),
> +	SENSOR_ATTR_2(temp2_fault, S_IRUGO, show_temp_fault, NULL, 0, 1),
> +	SENSOR_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 0, 2),
> +	SENSOR_ATTR_2(temp3_max, S_IRUGO|S_IWUSR, show_temp_max,
> +		store_temp_max, 0, 2),
> +	SENSOR_ATTR_2(temp3_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
> +		store_temp_max_hyst, 0, 2),
> +	SENSOR_ATTR_2(temp3_crit, S_IRUGO|S_IWUSR, show_temp_crit,
> +		store_temp_crit, 0, 2),
> +	SENSOR_ATTR_2(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0, 2),
> +	SENSOR_ATTR_2(temp3_type, S_IRUGO, show_temp_type, NULL, 0, 2),
> +	SENSOR_ATTR_2(temp3_beep, S_IRUGO|S_IWUSR, show_temp_beep,
> +		store_temp_beep, 0, 2),
> +	SENSOR_ATTR_2(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 2),
> +	SENSOR_ATTR_2(temp3_fault, S_IRUGO, show_temp_fault, NULL, 0, 2)
>  };
>  
> -static struct sensor_device_attribute f71882fg_fan_attr[] > +static struct sensor_device_attribute_2 f71882fg_fan_attr[] >  {
> -	SENSOR_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0),
> -	SENSOR_ATTR(fan1_beep, S_IRUGO|S_IWUSR, show_fan_beep,
> -		store_fan_beep, 0),
> -	SENSOR_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0),
> -	SENSOR_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1),
> -	SENSOR_ATTR(fan2_beep, S_IRUGO|S_IWUSR, show_fan_beep,
> -		store_fan_beep, 1),
> -	SENSOR_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1),
> -	SENSOR_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2),
> -	SENSOR_ATTR(fan3_beep, S_IRUGO|S_IWUSR, show_fan_beep,
> -		store_fan_beep, 2),
> -	SENSOR_ATTR(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 2),
> -	SENSOR_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3),
> -	SENSOR_ATTR(fan4_beep, S_IRUGO|S_IWUSR, show_fan_beep,
> -		store_fan_beep, 3),
> -	SENSOR_ATTR(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 3)
> +	SENSOR_ATTR_2(fan1_input, S_IRUGO, show_fan, NULL, 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_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_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_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)
>  };
>  
>  
> @@ -423,7 +423,7 @@
>  	char *buf)
>  {
>  	struct f71882fg_data *data = f71882fg_update_device(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  	int speed = fan_from_reg(data->fan[nr]);
>  
>  	if (speed = FAN_MIN_DETECT)
> @@ -436,7 +436,7 @@
>  	*devattr, char *buf)
>  {
>  	struct f71882fg_data *data = f71882fg_update_device(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  
>  	if (data->fan_beep & (1 << nr))
>  		return sprintf(buf, "1\n");
> @@ -448,7 +448,7 @@
>  	*devattr, const char *buf, size_t count)
>  {
>  	struct f71882fg_data *data = dev_get_drvdata(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  	int val = simple_strtoul(buf, NULL, 10);
>  
>  	mutex_lock(&data->update_lock);
> @@ -467,7 +467,7 @@
>  	*devattr, char *buf)
>  {
>  	struct f71882fg_data *data = f71882fg_update_device(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  
>  	if (data->fan_status & (1 << nr))
>  		return sprintf(buf, "1\n");
> @@ -479,7 +479,7 @@
>  	char *buf)
>  {
>  	struct f71882fg_data *data = f71882fg_update_device(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  
>  	return sprintf(buf, "%d\n", data->in[nr] * 8);
>  }
> @@ -513,7 +513,7 @@
>  	*devattr, char *buf)
>  {
>  	struct f71882fg_data *data = f71882fg_update_device(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  
>  	if (data->in_beep & (1 << nr))
>  		return sprintf(buf, "1\n");
> @@ -525,7 +525,7 @@
>  	*devattr, const char *buf, size_t count)
>  {
>  	struct f71882fg_data *data = dev_get_drvdata(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  	int val = simple_strtoul(buf, NULL, 10);
>  
>  	mutex_lock(&data->update_lock);
> @@ -544,7 +544,7 @@
>  	*devattr, char *buf)
>  {
>  	struct f71882fg_data *data = f71882fg_update_device(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  
>  	if (data->in_status & (1 << nr))
>  		return sprintf(buf, "1\n");
> @@ -556,7 +556,7 @@
>  	char *buf)
>  {
>  	struct f71882fg_data *data = f71882fg_update_device(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  
>  	return sprintf(buf, "%d\n", data->temp[nr] * 1000);
>  }
> @@ -565,7 +565,7 @@
>  	*devattr, char *buf)
>  {
>  	struct f71882fg_data *data = f71882fg_update_device(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  
>  	return sprintf(buf, "%d\n", data->temp_high[nr] * 1000);
>  }
> @@ -574,7 +574,7 @@
>  	*devattr, const char *buf, size_t count)
>  {
>  	struct f71882fg_data *data = dev_get_drvdata(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  	int val = simple_strtoul(buf, NULL, 10) / 1000;
>  
>  	if (val > 255)
> @@ -592,7 +592,7 @@
>  	*devattr, char *buf)
>  {
>  	struct f71882fg_data *data = f71882fg_update_device(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  
>  	return sprintf(buf, "%d\n",
>  		(data->temp_high[nr] - data->temp_hyst[nr]) * 1000);
> @@ -602,7 +602,7 @@
>  	*devattr, const char *buf, size_t count)
>  {
>  	struct f71882fg_data *data = dev_get_drvdata(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  	int val = simple_strtoul(buf, NULL, 10) / 1000;
>  	ssize_t ret = count;
>  
> @@ -642,7 +642,7 @@
>  	*devattr, char *buf)
>  {
>  	struct f71882fg_data *data = f71882fg_update_device(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  
>  	return sprintf(buf, "%d\n", data->temp_ovt[nr] * 1000);
>  }
> @@ -651,7 +651,7 @@
>  	*devattr, const char *buf, size_t count)
>  {
>  	struct f71882fg_data *data = dev_get_drvdata(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  	int val = simple_strtoul(buf, NULL, 10) / 1000;
>  
>  	if (val > 255)
> @@ -669,7 +669,7 @@
>  	*devattr, char *buf)
>  {
>  	struct f71882fg_data *data = f71882fg_update_device(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  
>  	return sprintf(buf, "%d\n",
>  		(data->temp_ovt[nr] - data->temp_hyst[nr]) * 1000);
> @@ -679,7 +679,7 @@
>  	*devattr, char *buf)
>  {
>  	struct f71882fg_data *data = f71882fg_update_device(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  
>  	return sprintf(buf, "%d\n", data->temp_type[nr]);
>  }
> @@ -688,7 +688,7 @@
>  	*devattr, char *buf)
>  {
>  	struct f71882fg_data *data = f71882fg_update_device(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  
>  	if (data->temp_beep & (1 << (nr + 1)))
>  		return sprintf(buf, "1\n");
> @@ -700,7 +700,7 @@
>  	*devattr, const char *buf, size_t count)
>  {
>  	struct f71882fg_data *data = dev_get_drvdata(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  	int val = simple_strtoul(buf, NULL, 10);
>  
>  	mutex_lock(&data->update_lock);
> @@ -719,7 +719,7 @@
>  	*devattr, char *buf)
>  {
>  	struct f71882fg_data *data = f71882fg_update_device(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  
>  	if (data->temp_status & (1 << (nr + 1)))
>  		return sprintf(buf, "1\n");
> @@ -731,7 +731,7 @@
>  	*devattr, char *buf)
>  {
>  	struct f71882fg_data *data = f71882fg_update_device(dev);
> -	int nr = to_sensor_dev_attr(devattr)->index;
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
>  
>  	if (data->temp_diode_open & (1 << (nr + 1)))
>  		return sprintf(buf, "1\n");
> 
> 
> 
> --- attr2	2008-06-30 22:57:54.000000000 +0200
> +++ f71882fg.c	2008-06-30 22:54:22.000000000 +0200
> @@ -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
>  
> @@ -70,6 +72,18 @@
>  #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 +92,12 @@
>  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, 0);
> +MODULE_PARM_DESC(fan_mode, 
> +	"List of fan modes (for four fans) (0=don't change, 1=pwm, 2=rpm");
> +
> +
>  static struct platform_device *f71882fg_pdev = NULL;
>  
>  /* Super-I/O Function prototypes */
> @@ -88,6 +108,7 @@
>  static inline void superio_exit(int base);
>  
>  static inline u16 fan_from_reg ( u16 reg );
> +static inline u16 fan_to_reg ( u16 reg );
>  
>  struct f71882fg_data {
>  	unsigned short addr;
> @@ -104,6 +125,8 @@
>  	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];
> @@ -114,11 +137,19 @@
>  	u8	temp_status;
>  	u8	temp_beep;
>  	u8	temp_diode_open;
> +	u8	pwm[4];
> +	u8	pwm_enable;
> +	u8	pwm_type;
> +	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];
>  };
>  
>  static u8 f71882fg_read8(struct f71882fg_data *data, u8 reg);
>  static u16 f71882fg_read16(struct f71882fg_data *data, u8 reg);
>  static void f71882fg_write8(struct f71882fg_data *data, u8 reg, u8 val);
> +static void f71882fg_write16(struct f71882fg_data *data, u8 reg, u16 val);
>  
>  /* Sysfs in*/
>  static ssize_t show_in(struct device *dev, struct device_attribute *devattr,
> @@ -136,6 +167,10 @@
>  /* 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
> @@ -173,6 +208,36 @@
>  static ssize_t show_name(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);
> +
>  static int __devinit f71882fg_probe(struct platform_device * pdev);
>  static int __devexit f71882fg_remove(struct platform_device *pdev);
>  static int __init f71882fg_init(void);
> @@ -252,21 +317,181 @@
>  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(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_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_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(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_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_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(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_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_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(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_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_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)
>  };
>  
>  
> @@ -310,6 +535,11 @@
>  	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;
> @@ -338,6 +568,15 @@
>  	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);
> @@ -399,9 +638,37 @@
>  
>  		data->fan_status = f71882fg_read8(data,
>  						F71882FG_REG_FAN_STATUS);
> -		for (nr = 0; nr < 4; nr++)
> +		data->pwm_type = f71882fg_read8(data,
> +						F71882FG_REG_PWM_TYPE);
> +		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->fan[nr] = f71882fg_read16(data,
> -						F71882FG_REG_FAN(nr));
> +				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->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));
> +				data->pwm_auto_point_temp[nr][point]> +					f71882fg_read8(data,
> +						F71882FG_REG_POINT_TEMP(nr,
> +							point));
> +			}
> +                }
>  
>  		data->in_status = f71882fg_read8(data,
>  						F71882FG_REG_IN_STATUS);
> @@ -432,6 +699,34 @@
>  	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;
> +	int val = simple_strtoul(buf, NULL, 10);
> +
> +	mutex_lock(&data->update_lock);
> +	if(data->pwm_enable&(1<<(2*nr)))
> +		count=-EINVAL;
> +	else
> +		f71882fg_write16(data, F71882FG_REG_FAN_FULL_SPEED(nr), 
> +			fan_to_reg(val));
> +	mutex_unlock(&data->update_lock);
> +
> +	return count;
> +}
> +
> +
>  static ssize_t show_fan_beep(struct device *dev, struct device_attribute
>  	*devattr, char *buf)
>  {
> @@ -739,6 +1034,276 @@
>  		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Úta->pwm[nr];
> +	else
> +		// RPM mode
> +		val%5*fan_from_reg(data->fan_target[nr])
> +			/fan_from_reg(data->fan_full_speed[nr]);
> +	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;
> +	int val = simple_strtoul(buf, NULL, 10);
> +
> +	mutex_lock(&data->update_lock);
> +	if(data->pwm_enable&(1<<(2*nr)))
> +		// PWM mode
> +		f71882fg_write8(data, F71882FG_REG_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));
> +	}
> +	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(2&(data->pwm_enable>>(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;
> +	int val = simple_strtoul(buf, NULL, 10);
> +	if(val<0 || 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;	// RPM mode
> +	case 2: data->pwm_enable&=~(1<<(2*nr)); break;	// Duty cycle mode
> +	}
> +	f71882fg_write8(data, F71882FG_REG_PWM_ENABLE, data->pwm_enable);
> +	mutex_unlock(&data->update_lock);
> +	if(!val)
> +		store_pwm(dev,devattr,"255",4);
> +
> +	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Úta->pwm_auto_point_pwm[pwm][point];
> +	} else {
> +		// RPM mode
> +		result2*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);
> +
> +	mutex_lock(&data->update_lock);
> +	if(data->pwm_enable&(1<<(2*pwm))) {
> +		// PWM mode
> +		f71882fg_write8(data, F71882FG_REG_POINT_PWM(pwm,point), val);
> +	} else {
> +		// RPM mode
> +		if(val<29)	// Prevent negative numbers
> +			val%5;
> +		else
> +			val=(255-val)*32/val;
> +		f71882fg_write8(data, F71882FG_REG_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;
> +	struct f71882fg_data *data = f71882fg_update_device(dev);
> +	int nr = to_sensor_dev_attr_2(devattr)->index;
> +
> +	switch(nr) {
> +	case 0: resultÚta->pwm_auto_point_hyst[0]&15; break;
> +	case 1: resultÚta->pwm_auto_point_hyst[0]>>4; break;
> +	case 2: resultÚta->pwm_auto_point_hyst[1]&15; break;
> +	case 3: resultÚta->pwm_auto_point_hyst[1]>>4; break;
> +	default: result=-1;
> +	}
> +
> +	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 = 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);
> +	val&\x15;
> +	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;
> +	}
> +
> +	mutex_lock(&data->update_lock);
> +	if(nr=0 || nr=1)
> +		f71882fg_write8(data, F71882FG_REG_FAN_HYST0, val); 
> +	else
> +		f71882fg_write8(data, F71882FG_REG_FAN_HYST1, 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=1&(data->pwm_auto_point_mapping[nr]>>4);
> +
> +	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);
> +	if(val)
> +		valÚta->pwm_auto_point_mapping[nr]|(1<<4);
> +	else
> +		valÚta->pwm_auto_point_mapping[nr]&(~(1<<4));
> +	mutex_lock(&data->update_lock);
> +	f71882fg_write8(data, F71882FG_REG_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);
> +	result>>=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;
> +	int val = simple_strtoul(buf, NULL, 10);
> +	switch(val) {
> +	case 1: val=1; break;
> +	case 2: val=2; break;
> +	case 4: val=3; break;
> +	default: return -EINVAL;
> +	}
> +	val|Úta->pwm_auto_point_mapping[nr]&0xfc;
> +	mutex_lock(&data->update_lock);
> +	f71882fg_write8(data, F71882FG_REG_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Úta->pwm_auto_point_temp[pwm][point];
> +	return sprintf(buf, "%d\n", 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;
> +	int val = simple_strtoul(buf, NULL, 10);
> +
> +	mutex_lock(&data->update_lock);
> +	f71882fg_write8(data, F71882FG_REG_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)
>  {
> 
> 


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [lm-sensors] [PATCH 2.6.25.4] f71882.c driver,
  2008-06-11  5:20 [lm-sensors] [PATCH 2.6.25.4] f71882.c driver, Mark van Doesburg
                   ` (7 preceding siblings ...)
  2008-08-04 14:05 ` Hans de Goede
@ 2008-10-07 18:36 ` Mark van Doesburg
  2008-10-10 22:57 ` Andrew Morton
  9 siblings, 0 replies; 11+ messages in thread
From: Mark van Doesburg @ 2008-10-07 18:36 UTC (permalink / raw)
  To: lm-sensors

[-- Attachment #1: Type: text/plain, Size: 44064 bytes --]

Hello Hans,

I've fixed the patch, and used scripts/Lindent to fix the style
issues. I've also removed a few assignments in conditions.

regards,

Mark.


Hans de Goede <j.w.r.degoede@hhs.nl> wrote:

	Mark van Doesburg wrote:
	> Hello Hans,
	> 
	> I'm sorry for the previous mail, I got the parameters for ATTR_2 reversed.
	> So here are the patches again, and now it works.
	> 
	> Regards,
	> 
	> Mark.
	> 

	Hi Mark,

	Sorry for taking so long, it has been a month since you submitted this patch :(

	Anyways I've done a very detailed review, and you will need to fix some things 
	before your patch can be applied. You can do so in another incremental patch, 
	or redo the second patch of your set, whichever you prefer.

	Here are the results of my review, I've been taking quick notes while digging 
	through the code, so some things may be written down a bit short, if you don't 
	understand please ask me to clarify!

	MUST FIX
	--------
	* pwm#_point1_temp_hyst should be an absolute value (the temp value when the
	   switch back to a lower speed zone is made), not a delta
	* All pwm#_point#_temp's have a hyst, so report it for all, as its shared only
	   make it writable for point1
	* there are only 4 temp points per pwm but in f71882fg_update_device() 5 get
	   read.
	* in store_pwm_enable when disabling pwm not only set pwm to 255 but also make
	   sure its not in automatic mode.
	* forcing pwm and rpm *comments* are reversed in store_pwm_enable()
	* in all store_foo functions update the copy of registers in your data struct
	   when you write these registers, and do this before unlocking the data struct
	   lock, otherwise a read done quickly after a write could return the old value.
	   Or worse a write done quickly after another write on a register shared
	   between multiple pwm's could undo the changes done to that reg for the other
	   pwm!!
	* in all store_foo functions take the lock earlier, before reading any
	   register copies from the data struct, otherwise 2 writes running concurrently
	   could result in bogus data being written when they use a shared register
	* in all store_foo functions clamp the return value from simple_strtol to the
	   allowable range to avoid writing bogus values like for example storing a pwm
	   setting of 355 (max is 255, so clamp this to 255). Do this campling using
	   the SENSORS_LIMIT macro, for more on this see the "sysfs attribute writes
	   interpretation" chapter at the end of Documentation/hwmon/sysfs-interface


	SHOULD FIX
	----------
	* fix many code style issues, the linux kernel code style mandates that you
	   always put a space between an operation and its operands so do not write:
	   "a+b" but write "a + b", there is a script called check-patch in the kernel
	   tree which you can run on your patch which should detect most code style
	   issues. Note that this script will complain about the use of simple_strtoul,
	   ignore this, the script is right that recently a better solution has become
	   available, but as all hwmon drivers currently use simple_strtoul, please
	   keep using that for consistency.
	* pwm_type is read but not used.
	* in show_pwm_auto_point_temp_hyst() :
	   * drop the default case in the switch statement, nr never is anything
	     but 0 - 3, and if it would be anything different returning -EINVAL
	     would be much more appropriate

	Once I receive a new patch from you fixing all above MUST FIX items, I'll 
	forward it to Andrew Morton for 2.6.27 inclusion (assuming you are quicker then 
	me in fixing your patch).

	Thans & Regards,

	Hans



	> 
	> --- /tmp/linux-2.6.25.4/drivers/hwmon/f71882fg.c	2008-05-15 17:00:12.000000000 +0200
	> +++ attr2	2008-06-30 22:57:54.000000000 +0200
	> @@ -194,79 +194,79 @@
	>  	__ATTR( name, S_IRUGO, show_name, NULL ),
	>  };
	>  
	> -static struct sensor_device_attribute f71882fg_in_temp_attr[] =
	> +static struct sensor_device_attribute_2 f71882fg_in_temp_attr[] =
	>  {
	> -	SENSOR_ATTR(in0_input, S_IRUGO, show_in, NULL, 0),
	> -	SENSOR_ATTR(in1_input, S_IRUGO, show_in, NULL, 1),
	> -	SENSOR_ATTR(in1_max, S_IRUGO|S_IWUSR, show_in_max, store_in_max, 1),
	> -	SENSOR_ATTR(in1_beep, S_IRUGO|S_IWUSR, show_in_beep, store_in_beep, 1),
	> -	SENSOR_ATTR(in1_alarm, S_IRUGO, show_in_alarm, NULL, 1),
	> -	SENSOR_ATTR(in2_input, S_IRUGO, show_in, NULL, 2),
	> -	SENSOR_ATTR(in3_input, S_IRUGO, show_in, NULL, 3),
	> -	SENSOR_ATTR(in4_input, S_IRUGO, show_in, NULL, 4),
	> -	SENSOR_ATTR(in5_input, S_IRUGO, show_in, NULL, 5),
	> -	SENSOR_ATTR(in6_input, S_IRUGO, show_in, NULL, 6),
	> -	SENSOR_ATTR(in7_input, S_IRUGO, show_in, NULL, 7),
	> -	SENSOR_ATTR(in8_input, S_IRUGO, show_in, NULL, 8),
	> -	SENSOR_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0),
	> -	SENSOR_ATTR(temp1_max, S_IRUGO|S_IWUSR, show_temp_max,
	> -		store_temp_max, 0),
	> -	SENSOR_ATTR(temp1_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
	> -		store_temp_max_hyst, 0),
	> -	SENSOR_ATTR(temp1_crit, S_IRUGO|S_IWUSR, show_temp_crit,
	> -		store_temp_crit, 0),
	> -	SENSOR_ATTR(temp1_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0),
	> -	SENSOR_ATTR(temp1_type, S_IRUGO, show_temp_type, NULL, 0),
	> -	SENSOR_ATTR(temp1_beep, S_IRUGO|S_IWUSR, show_temp_beep,
	> -		store_temp_beep, 0),
	> -	SENSOR_ATTR(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0),
	> -	SENSOR_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0),
	> -	SENSOR_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1),
	> -	SENSOR_ATTR(temp2_max, S_IRUGO|S_IWUSR, show_temp_max,
	> -		store_temp_max, 1),
	> -	SENSOR_ATTR(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
	> -		store_temp_max_hyst, 1),
	> -	SENSOR_ATTR(temp2_crit, S_IRUGO|S_IWUSR, show_temp_crit,
	> -		store_temp_crit, 1),
	> -	SENSOR_ATTR(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 1),
	> -	SENSOR_ATTR(temp2_type, S_IRUGO, show_temp_type, NULL, 1),
	> -	SENSOR_ATTR(temp2_beep, S_IRUGO|S_IWUSR, show_temp_beep,
	> -		store_temp_beep, 1),
	> -	SENSOR_ATTR(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 1),
	> -	SENSOR_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1),
	> -	SENSOR_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2),
	> -	SENSOR_ATTR(temp3_max, S_IRUGO|S_IWUSR, show_temp_max,
	> -		store_temp_max, 2),
	> -	SENSOR_ATTR(temp3_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
	> -		store_temp_max_hyst, 2),
	> -	SENSOR_ATTR(temp3_crit, S_IRUGO|S_IWUSR, show_temp_crit,
	> -		store_temp_crit, 2),
	> -	SENSOR_ATTR(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 2),
	> -	SENSOR_ATTR(temp3_type, S_IRUGO, show_temp_type, NULL, 2),
	> -	SENSOR_ATTR(temp3_beep, S_IRUGO|S_IWUSR, show_temp_beep,
	> -		store_temp_beep, 2),
	> -	SENSOR_ATTR(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 2),
	> -	SENSOR_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2)
	> +	SENSOR_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, 0),
	> +	SENSOR_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 0, 1),
	> +	SENSOR_ATTR_2(in1_max, S_IRUGO|S_IWUSR, show_in_max, store_in_max, 0, 1),
	> +	SENSOR_ATTR_2(in1_beep, S_IRUGO|S_IWUSR, show_in_beep, store_in_beep, 0, 1),
	> +	SENSOR_ATTR_2(in1_alarm, S_IRUGO, show_in_alarm, NULL, 0, 1),
	> +	SENSOR_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 0, 2),
	> +	SENSOR_ATTR_2(in3_input, S_IRUGO, show_in, NULL, 0, 3),
	> +	SENSOR_ATTR_2(in4_input, S_IRUGO, show_in, NULL, 0, 4),
	> +	SENSOR_ATTR_2(in5_input, S_IRUGO, show_in, NULL, 0, 5),
	> +	SENSOR_ATTR_2(in6_input, S_IRUGO, show_in, NULL, 0, 6),
	> +	SENSOR_ATTR_2(in7_input, S_IRUGO, show_in, NULL, 0, 7),
	> +	SENSOR_ATTR_2(in8_input, S_IRUGO, show_in, NULL, 0, 8),
	> +	SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0),
	> +	SENSOR_ATTR_2(temp1_max, S_IRUGO|S_IWUSR, show_temp_max,
	> +		store_temp_max, 0, 0),
	> +	SENSOR_ATTR_2(temp1_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
	> +		store_temp_max_hyst, 0, 0),
	> +	SENSOR_ATTR_2(temp1_crit, S_IRUGO|S_IWUSR, show_temp_crit,
	> +		store_temp_crit, 0, 0),
	> +	SENSOR_ATTR_2(temp1_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0, 0),
	> +	SENSOR_ATTR_2(temp1_type, S_IRUGO, show_temp_type, NULL, 0, 0),
	> +	SENSOR_ATTR_2(temp1_beep, S_IRUGO|S_IWUSR, show_temp_beep,
	> +		store_temp_beep, 0, 0),
	> +	SENSOR_ATTR_2(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 0),
	> +	SENSOR_ATTR_2(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0, 0),
	> +	SENSOR_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 0, 1),
	> +	SENSOR_ATTR_2(temp2_max, S_IRUGO|S_IWUSR, show_temp_max,
	> +		store_temp_max, 0, 1),
	> +	SENSOR_ATTR_2(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
	> +		store_temp_max_hyst, 0, 1),
	> +	SENSOR_ATTR_2(temp2_crit, S_IRUGO|S_IWUSR, show_temp_crit,
	> +		store_temp_crit, 0, 1),
	> +	SENSOR_ATTR_2(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0, 1),
	> +	SENSOR_ATTR_2(temp2_type, S_IRUGO, show_temp_type, NULL, 0, 1),
	> +	SENSOR_ATTR_2(temp2_beep, S_IRUGO|S_IWUSR, show_temp_beep,
	> +		store_temp_beep, 0, 1),
	> +	SENSOR_ATTR_2(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 1),
	> +	SENSOR_ATTR_2(temp2_fault, S_IRUGO, show_temp_fault, NULL, 0, 1),
	> +	SENSOR_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 0, 2),
	> +	SENSOR_ATTR_2(temp3_max, S_IRUGO|S_IWUSR, show_temp_max,
	> +		store_temp_max, 0, 2),
	> +	SENSOR_ATTR_2(temp3_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
	> +		store_temp_max_hyst, 0, 2),
	> +	SENSOR_ATTR_2(temp3_crit, S_IRUGO|S_IWUSR, show_temp_crit,
	> +		store_temp_crit, 0, 2),
	> +	SENSOR_ATTR_2(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0, 2),
	> +	SENSOR_ATTR_2(temp3_type, S_IRUGO, show_temp_type, NULL, 0, 2),
	> +	SENSOR_ATTR_2(temp3_beep, S_IRUGO|S_IWUSR, show_temp_beep,
	> +		store_temp_beep, 0, 2),
	> +	SENSOR_ATTR_2(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 2),
	> +	SENSOR_ATTR_2(temp3_fault, S_IRUGO, show_temp_fault, NULL, 0, 2)
	>  };
	>  
	> -static struct sensor_device_attribute f71882fg_fan_attr[] =
	> +static struct sensor_device_attribute_2 f71882fg_fan_attr[] =
	>  {
	> -	SENSOR_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0),
	> -	SENSOR_ATTR(fan1_beep, S_IRUGO|S_IWUSR, show_fan_beep,
	> -		store_fan_beep, 0),
	> -	SENSOR_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0),
	> -	SENSOR_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1),
	> -	SENSOR_ATTR(fan2_beep, S_IRUGO|S_IWUSR, show_fan_beep,
	> -		store_fan_beep, 1),
	> -	SENSOR_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1),
	> -	SENSOR_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2),
	> -	SENSOR_ATTR(fan3_beep, S_IRUGO|S_IWUSR, show_fan_beep,
	> -		store_fan_beep, 2),
	> -	SENSOR_ATTR(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 2),
	> -	SENSOR_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3),
	> -	SENSOR_ATTR(fan4_beep, S_IRUGO|S_IWUSR, show_fan_beep,
	> -		store_fan_beep, 3),
	> -	SENSOR_ATTR(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 3)
	> +	SENSOR_ATTR_2(fan1_input, S_IRUGO, show_fan, NULL, 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_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_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_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)
	>  };
	>  
	>  
	> @@ -423,7 +423,7 @@
	>  	char *buf)
	>  {
	>  	struct f71882fg_data *data = f71882fg_update_device(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  	int speed = fan_from_reg(data->fan[nr]);
	>  
	>  	if (speed == FAN_MIN_DETECT)
	> @@ -436,7 +436,7 @@
	>  	*devattr, char *buf)
	>  {
	>  	struct f71882fg_data *data = f71882fg_update_device(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  
	>  	if (data->fan_beep & (1 << nr))
	>  		return sprintf(buf, "1\n");
	> @@ -448,7 +448,7 @@
	>  	*devattr, const char *buf, size_t count)
	>  {
	>  	struct f71882fg_data *data = dev_get_drvdata(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  	int val = simple_strtoul(buf, NULL, 10);
	>  
	>  	mutex_lock(&data->update_lock);
	> @@ -467,7 +467,7 @@
	>  	*devattr, char *buf)
	>  {
	>  	struct f71882fg_data *data = f71882fg_update_device(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  
	>  	if (data->fan_status & (1 << nr))
	>  		return sprintf(buf, "1\n");
	> @@ -479,7 +479,7 @@
	>  	char *buf)
	>  {
	>  	struct f71882fg_data *data = f71882fg_update_device(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  
	>  	return sprintf(buf, "%d\n", data->in[nr] * 8);
	>  }
	> @@ -513,7 +513,7 @@
	>  	*devattr, char *buf)
	>  {
	>  	struct f71882fg_data *data = f71882fg_update_device(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  
	>  	if (data->in_beep & (1 << nr))
	>  		return sprintf(buf, "1\n");
	> @@ -525,7 +525,7 @@
	>  	*devattr, const char *buf, size_t count)
	>  {
	>  	struct f71882fg_data *data = dev_get_drvdata(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  	int val = simple_strtoul(buf, NULL, 10);
	>  
	>  	mutex_lock(&data->update_lock);
	> @@ -544,7 +544,7 @@
	>  	*devattr, char *buf)
	>  {
	>  	struct f71882fg_data *data = f71882fg_update_device(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  
	>  	if (data->in_status & (1 << nr))
	>  		return sprintf(buf, "1\n");
	> @@ -556,7 +556,7 @@
	>  	char *buf)
	>  {
	>  	struct f71882fg_data *data = f71882fg_update_device(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  
	>  	return sprintf(buf, "%d\n", data->temp[nr] * 1000);
	>  }
	> @@ -565,7 +565,7 @@
	>  	*devattr, char *buf)
	>  {
	>  	struct f71882fg_data *data = f71882fg_update_device(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  
	>  	return sprintf(buf, "%d\n", data->temp_high[nr] * 1000);
	>  }
	> @@ -574,7 +574,7 @@
	>  	*devattr, const char *buf, size_t count)
	>  {
	>  	struct f71882fg_data *data = dev_get_drvdata(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  	int val = simple_strtoul(buf, NULL, 10) / 1000;
	>  
	>  	if (val > 255)
	> @@ -592,7 +592,7 @@
	>  	*devattr, char *buf)
	>  {
	>  	struct f71882fg_data *data = f71882fg_update_device(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  
	>  	return sprintf(buf, "%d\n",
	>  		(data->temp_high[nr] - data->temp_hyst[nr]) * 1000);
	> @@ -602,7 +602,7 @@
	>  	*devattr, const char *buf, size_t count)
	>  {
	>  	struct f71882fg_data *data = dev_get_drvdata(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  	int val = simple_strtoul(buf, NULL, 10) / 1000;
	>  	ssize_t ret = count;
	>  
	> @@ -642,7 +642,7 @@
	>  	*devattr, char *buf)
	>  {
	>  	struct f71882fg_data *data = f71882fg_update_device(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  
	>  	return sprintf(buf, "%d\n", data->temp_ovt[nr] * 1000);
	>  }
	> @@ -651,7 +651,7 @@
	>  	*devattr, const char *buf, size_t count)
	>  {
	>  	struct f71882fg_data *data = dev_get_drvdata(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  	int val = simple_strtoul(buf, NULL, 10) / 1000;
	>  
	>  	if (val > 255)
	> @@ -669,7 +669,7 @@
	>  	*devattr, char *buf)
	>  {
	>  	struct f71882fg_data *data = f71882fg_update_device(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  
	>  	return sprintf(buf, "%d\n",
	>  		(data->temp_ovt[nr] - data->temp_hyst[nr]) * 1000);
	> @@ -679,7 +679,7 @@
	>  	*devattr, char *buf)
	>  {
	>  	struct f71882fg_data *data = f71882fg_update_device(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  
	>  	return sprintf(buf, "%d\n", data->temp_type[nr]);
	>  }
	> @@ -688,7 +688,7 @@
	>  	*devattr, char *buf)
	>  {
	>  	struct f71882fg_data *data = f71882fg_update_device(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  
	>  	if (data->temp_beep & (1 << (nr + 1)))
	>  		return sprintf(buf, "1\n");
	> @@ -700,7 +700,7 @@
	>  	*devattr, const char *buf, size_t count)
	>  {
	>  	struct f71882fg_data *data = dev_get_drvdata(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  	int val = simple_strtoul(buf, NULL, 10);
	>  
	>  	mutex_lock(&data->update_lock);
	> @@ -719,7 +719,7 @@
	>  	*devattr, char *buf)
	>  {
	>  	struct f71882fg_data *data = f71882fg_update_device(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  
	>  	if (data->temp_status & (1 << (nr + 1)))
	>  		return sprintf(buf, "1\n");
	> @@ -731,7 +731,7 @@
	>  	*devattr, char *buf)
	>  {
	>  	struct f71882fg_data *data = f71882fg_update_device(dev);
	> -	int nr = to_sensor_dev_attr(devattr)->index;
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	>  
	>  	if (data->temp_diode_open & (1 << (nr + 1)))
	>  		return sprintf(buf, "1\n");
	> 
	> 
	> 
	> --- attr2	2008-06-30 22:57:54.000000000 +0200
	> +++ f71882fg.c	2008-06-30 22:54:22.000000000 +0200
	> @@ -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
	>  
	> @@ -70,6 +72,18 @@
	>  #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 +92,12 @@
	>  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, 0);
	> +MODULE_PARM_DESC(fan_mode, 
	> +	"List of fan modes (for four fans) (0=don't change, 1=pwm, 2=rpm");
	> +
	> +
	>  static struct platform_device *f71882fg_pdev = NULL;
	>  
	>  /* Super-I/O Function prototypes */
	> @@ -88,6 +108,7 @@
	>  static inline void superio_exit(int base);
	>  
	>  static inline u16 fan_from_reg ( u16 reg );
	> +static inline u16 fan_to_reg ( u16 reg );
	>  
	>  struct f71882fg_data {
	>  	unsigned short addr;
	> @@ -104,6 +125,8 @@
	>  	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];
	> @@ -114,11 +137,19 @@
	>  	u8	temp_status;
	>  	u8	temp_beep;
	>  	u8	temp_diode_open;
	> +	u8	pwm[4];
	> +	u8	pwm_enable;
	> +	u8	pwm_type;
	> +	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];
	>  };
	>  
	>  static u8 f71882fg_read8(struct f71882fg_data *data, u8 reg);
	>  static u16 f71882fg_read16(struct f71882fg_data *data, u8 reg);
	>  static void f71882fg_write8(struct f71882fg_data *data, u8 reg, u8 val);
	> +static void f71882fg_write16(struct f71882fg_data *data, u8 reg, u16 val);
	>  
	>  /* Sysfs in*/
	>  static ssize_t show_in(struct device *dev, struct device_attribute *devattr,
	> @@ -136,6 +167,10 @@
	>  /* 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
	> @@ -173,6 +208,36 @@
	>  static ssize_t show_name(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);
	> +
	>  static int __devinit f71882fg_probe(struct platform_device * pdev);
	>  static int __devexit f71882fg_remove(struct platform_device *pdev);
	>  static int __init f71882fg_init(void);
	> @@ -252,21 +317,181 @@
	>  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(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_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_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(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_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_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(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_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_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(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_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_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)
	>  };
	>  
	>  
	> @@ -310,6 +535,11 @@
	>  	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;
	> @@ -338,6 +568,15 @@
	>  	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);
	> @@ -399,9 +638,37 @@
	>  
	>  		data->fan_status = f71882fg_read8(data,
	>  						F71882FG_REG_FAN_STATUS);
	> -		for (nr = 0; nr < 4; nr++)
	> +		data->pwm_type = f71882fg_read8(data,
	> +						F71882FG_REG_PWM_TYPE);
	> +		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->fan[nr] = f71882fg_read16(data,
	> -						F71882FG_REG_FAN(nr));
	> +				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->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));
	> +				data->pwm_auto_point_temp[nr][point]=
	> +					f71882fg_read8(data,
	> +						F71882FG_REG_POINT_TEMP(nr,
	> +							point));
	> +			}
	> +                }
	>  
	>  		data->in_status = f71882fg_read8(data,
	>  						F71882FG_REG_IN_STATUS);
	> @@ -432,6 +699,34 @@
	>  	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;
	> +	int val = simple_strtoul(buf, NULL, 10);
	> +
	> +	mutex_lock(&data->update_lock);
	> +	if(data->pwm_enable&(1<<(2*nr)))
	> +		count=-EINVAL;
	> +	else
	> +		f71882fg_write16(data, F71882FG_REG_FAN_FULL_SPEED(nr), 
	> +			fan_to_reg(val));
	> +	mutex_unlock(&data->update_lock);
	> +
	> +	return count;
	> +}
	> +
	> +
	>  static ssize_t show_fan_beep(struct device *dev, struct device_attribute
	>  	*devattr, char *buf)
	>  {
	> @@ -739,6 +1034,276 @@
	>  		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
	> +		val=255*fan_from_reg(data->fan_target[nr])
	> +			/fan_from_reg(data->fan_full_speed[nr]);
	> +	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;
	> +	int val = simple_strtoul(buf, NULL, 10);
	> +
	> +	mutex_lock(&data->update_lock);
	> +	if(data->pwm_enable&(1<<(2*nr)))
	> +		// PWM mode
	> +		f71882fg_write8(data, F71882FG_REG_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));
	> +	}
	> +	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(2&(data->pwm_enable>>(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;
	> +	int val = simple_strtoul(buf, NULL, 10);
	> +	if(val<0 || 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;	// RPM mode
	> +	case 2: data->pwm_enable&=~(1<<(2*nr)); break;	// Duty cycle mode
	> +	}
	> +	f71882fg_write8(data, F71882FG_REG_PWM_ENABLE, data->pwm_enable);
	> +	mutex_unlock(&data->update_lock);
	> +	if(!val)
	> +		store_pwm(dev,devattr,"255",4);
	> +
	> +	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);
	> +
	> +	mutex_lock(&data->update_lock);
	> +	if(data->pwm_enable&(1<<(2*pwm))) {
	> +		// PWM mode
	> +		f71882fg_write8(data, F71882FG_REG_POINT_PWM(pwm,point), val);
	> +	} 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);
	> +	}
	> +	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;
	> +	struct f71882fg_data *data = f71882fg_update_device(dev);
	> +	int nr = to_sensor_dev_attr_2(devattr)->index;
	> +
	> +	switch(nr) {
	> +	case 0: result=data->pwm_auto_point_hyst[0]&15; break;
	> +	case 1: result=data->pwm_auto_point_hyst[0]>>4; break;
	> +	case 2: result=data->pwm_auto_point_hyst[1]&15; break;
	> +	case 3: result=data->pwm_auto_point_hyst[1]>>4; break;
	> +	default: result=-1;
	> +	}
	> +
	> +	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 = 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);
	> +	val&=15;
	> +	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;
	> +	}
	> +
	> +	mutex_lock(&data->update_lock);
	> +	if(nr==0 || nr==1)
	> +		f71882fg_write8(data, F71882FG_REG_FAN_HYST0, val); 
	> +	else
	> +		f71882fg_write8(data, F71882FG_REG_FAN_HYST1, 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=1&(data->pwm_auto_point_mapping[nr]>>4);
	> +
	> +	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);
	> +	if(val)
	> +		val=data->pwm_auto_point_mapping[nr]|(1<<4);
	> +	else
	> +		val=data->pwm_auto_point_mapping[nr]&(~(1<<4));
	> +	mutex_lock(&data->update_lock);
	> +	f71882fg_write8(data, F71882FG_REG_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);
	> +	result>>=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;
	> +	int val = simple_strtoul(buf, NULL, 10);
	> +	switch(val) {
	> +	case 1: val=1; break;
	> +	case 2: val=2; break;
	> +	case 4: val=3; break;
	> +	default: return -EINVAL;
	> +	}
	> +	val|=data->pwm_auto_point_mapping[nr]&0xfc;
	> +	mutex_lock(&data->update_lock);
	> +	f71882fg_write8(data, F71882FG_REG_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", 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;
	> +	int val = simple_strtoul(buf, NULL, 10);
	> +
	> +	mutex_lock(&data->update_lock);
	> +	f71882fg_write8(data, F71882FG_REG_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)
	>  {
	> 
	> 

[-- Attachment #2: f71882fg_fixed.patch --]
[-- Type: text/plain, Size: 50945 bytes --]

--- attr2	2008-06-30 22:57:54.000000000 +0200
+++ f71882fg.c	2008-10-07 20:31:26.000000000 +0200
@@ -27,11 +27,11 @@
 #include <linux/hwmon-sysfs.h>
 #include <linux/err.h>
 #include <linux/mutex.h>
-#include <asm/io.h>
+#include <linux/io.h>
 
 #define DRVNAME "f71882fg"
 
-#define SIO_F71882FG_LD_HWM	0x04	/* Hardware monitor logical device*/
+#define SIO_F71882FG_LD_HWM	0x04	/* Hardware monitor logical device */
 #define SIO_UNLOCK_KEY		0x87	/* Key to enable Super-I/O */
 #define SIO_LOCK_KEY		0xAA	/* Key to diasble Super-I/O */
 
@@ -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
 
@@ -70,15 +72,31 @@
 #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 */
+#define FAN_MIN_DETECT			366	/* Lowest detectable fanspeed */
 
 static unsigned short force_id;
 module_param(force_id, ushort, 0);
 MODULE_PARM_DESC(force_id, "Override the detected device ID");
 
-static struct platform_device *f71882fg_pdev = NULL;
+static int fan_mode[4] = { 0, 0, 0, 0 };
+module_param_array(fan_mode, int, NULL, 0);
+MODULE_PARM_DESC(fan_mode, "List of fan modes (for four fans) "
+		 "(0=don't change, 1=pwm, 2=rpm");
+
+static struct platform_device *f71882fg_pdev;
 
 /* Super-I/O Function prototypes */
 static inline int superio_inb(int base, int reg);
@@ -87,93 +105,149 @@
 static inline void superio_select(int base, int ld);
 static inline void superio_exit(int base);
 
-static inline u16 fan_from_reg ( u16 reg );
+static inline u16 fan_from_reg(u16 reg);
+static inline u16 fan_to_reg(u16 reg);
 
 struct f71882fg_data {
 	unsigned short addr;
 	struct device *hwmon_dev;
 
 	struct mutex update_lock;
-	char valid;			/* !=0 if following fields are valid */
+	char valid;		/* !=0 if following fields are valid */
 	unsigned long last_updated;	/* In jiffies */
 	unsigned long last_limits;	/* In jiffies */
 
 	/* Register Values */
-	u8	in[9];
-	u8	in1_max;
-	u8	in_status;
-	u8	in_beep;
-	u16	fan[4];
-	u8	fan_status;
-	u8	fan_beep;
-	u8	temp[3];
-	u8	temp_ovt[3];
-	u8	temp_high[3];
-	u8	temp_hyst[3];
-	u8	temp_type[3];
-	u8	temp_status;
-	u8	temp_beep;
-	u8	temp_diode_open;
+	u8 in[9];
+	u8 in1_max;
+	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];
+	u8 temp_ovt[3];
+	u8 temp_high[3];
+	u8 temp_hyst[3];
+	u8 temp_type[3];
+	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];
 };
 
 static u8 f71882fg_read8(struct f71882fg_data *data, u8 reg);
 static u16 f71882fg_read16(struct f71882fg_data *data, u8 reg);
 static void f71882fg_write8(struct f71882fg_data *data, u8 reg, u8 val);
+static void f71882fg_write16(struct f71882fg_data *data, u8 reg, u16 val);
 
 /* Sysfs in*/
 static ssize_t show_in(struct device *dev, struct device_attribute *devattr,
-	char *buf);
-static ssize_t show_in_max(struct device *dev, struct device_attribute
-	*devattr, char *buf);
-static ssize_t store_in_max(struct device *dev, struct device_attribute
-	*devattr, const char *buf, size_t count);
-static ssize_t show_in_beep(struct device *dev, struct device_attribute
-	*devattr, char *buf);
+		       char *buf);
+static ssize_t show_in_max(struct device *dev,
+			   struct device_attribute *devattr, char *buf);
+static ssize_t store_in_max(struct device *dev,
+			    struct device_attribute *devattr, const char *buf,
+			    size_t count);
+static ssize_t show_in_beep(struct device *dev,
+			    struct device_attribute *devattr, char *buf);
 static ssize_t store_in_beep(struct device *dev, struct device_attribute
-	*devattr, const char *buf, size_t count);
+			     *devattr, const char *buf, size_t count);
 static ssize_t show_in_alarm(struct device *dev, struct device_attribute
-	*devattr, char *buf);
+			     *devattr, char *buf);
 /* Sysfs Fan */
-static ssize_t show_fan(struct device *dev, struct device_attribute *devattr,
-	char *buf);
+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);
+			     *devattr, char *buf);
 static ssize_t store_fan_beep(struct device *dev, struct device_attribute
-	*devattr, const char *buf, size_t count);
+			      *devattr, const char *buf, size_t count);
 static ssize_t show_fan_alarm(struct device *dev, struct device_attribute
-	*devattr, char *buf);
+			      *devattr, char *buf);
 /* Sysfs Temp */
 static ssize_t show_temp(struct device *dev, struct device_attribute
-	*devattr, char *buf);
+			 *devattr, char *buf);
 static ssize_t show_temp_max(struct device *dev, struct device_attribute
-	*devattr, char *buf);
+			     *devattr, char *buf);
 static ssize_t store_temp_max(struct device *dev, struct device_attribute
-	*devattr, const char *buf, size_t count);
+			      *devattr, const char *buf, size_t count);
 static ssize_t show_temp_max_hyst(struct device *dev, struct device_attribute
-	*devattr, char *buf);
+				  *devattr, char *buf);
 static ssize_t store_temp_max_hyst(struct device *dev, struct device_attribute
-	*devattr, const char *buf, size_t count);
+				   *devattr, const char *buf, size_t count);
 static ssize_t show_temp_crit(struct device *dev, struct device_attribute
-	*devattr, char *buf);
+			      *devattr, char *buf);
 static ssize_t store_temp_crit(struct device *dev, struct device_attribute
-	*devattr, const char *buf, size_t count);
+			       *devattr, const char *buf, size_t count);
 static ssize_t show_temp_crit_hyst(struct device *dev, struct device_attribute
-	*devattr, char *buf);
+				   *devattr, char *buf);
 static ssize_t show_temp_type(struct device *dev, struct device_attribute
-	*devattr, char *buf);
+			      *devattr, char *buf);
 static ssize_t show_temp_beep(struct device *dev, struct device_attribute
-	*devattr, char *buf);
+			      *devattr, char *buf);
 static ssize_t store_temp_beep(struct device *dev, struct device_attribute
-	*devattr, const char *buf, size_t count);
+			       *devattr, const char *buf, size_t count);
 static ssize_t show_temp_alarm(struct device *dev, struct device_attribute
-	*devattr, char *buf);
+			       *devattr, char *buf);
 static ssize_t show_temp_fault(struct device *dev, struct device_attribute
-	*devattr, char *buf);
+			       *devattr, char *buf);
 /* Sysfs misc */
-static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
-	char *buf);
+static ssize_t show_name(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);
 
-static int __devinit f71882fg_probe(struct platform_device * pdev);
+static int __devinit f71882fg_probe(struct platform_device *pdev);
 static int __devexit f71882fg_remove(struct platform_device *pdev);
 static int __init f71882fg_init(void);
 static int __init f71882fg_find(int sioaddr, unsigned short *address);
@@ -182,24 +256,24 @@
 
 static struct platform_driver f71882fg_driver = {
 	.driver = {
-		.owner	= THIS_MODULE,
-		.name	= DRVNAME,
-	},
-	.probe		= f71882fg_probe,
-	.remove		= __devexit_p(f71882fg_remove),
+		   .owner = THIS_MODULE,
+		   .name = DRVNAME,
+		   },
+	.probe = f71882fg_probe,
+	.remove = __devexit_p(f71882fg_remove),
 };
 
-static struct device_attribute f71882fg_dev_attr[] =
-{
-	__ATTR( name, S_IRUGO, show_name, NULL ),
+static struct device_attribute f71882fg_dev_attr[] = {
+	__ATTR(name, S_IRUGO, show_name, NULL),
 };
 
-static struct sensor_device_attribute_2 f71882fg_in_temp_attr[] =
-{
+static struct sensor_device_attribute_2 f71882fg_in_temp_attr[] = {
 	SENSOR_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, 0),
 	SENSOR_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 0, 1),
-	SENSOR_ATTR_2(in1_max, S_IRUGO|S_IWUSR, show_in_max, store_in_max, 0, 1),
-	SENSOR_ATTR_2(in1_beep, S_IRUGO|S_IWUSR, show_in_beep, store_in_beep, 0, 1),
+	SENSOR_ATTR_2(in1_max, S_IRUGO | S_IWUSR, show_in_max,
+		      store_in_max, 0, 1),
+	SENSOR_ATTR_2(in1_beep, S_IRUGO | S_IWUSR, show_in_beep,
+		      store_in_beep, 0, 1),
 	SENSOR_ATTR_2(in1_alarm, S_IRUGO, show_in_alarm, NULL, 0, 1),
 	SENSOR_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 0, 2),
 	SENSOR_ATTR_2(in3_input, S_IRUGO, show_in, NULL, 0, 3),
@@ -209,67 +283,267 @@
 	SENSOR_ATTR_2(in7_input, S_IRUGO, show_in, NULL, 0, 7),
 	SENSOR_ATTR_2(in8_input, S_IRUGO, show_in, NULL, 0, 8),
 	SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0),
-	SENSOR_ATTR_2(temp1_max, S_IRUGO|S_IWUSR, show_temp_max,
-		store_temp_max, 0, 0),
-	SENSOR_ATTR_2(temp1_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
-		store_temp_max_hyst, 0, 0),
-	SENSOR_ATTR_2(temp1_crit, S_IRUGO|S_IWUSR, show_temp_crit,
-		store_temp_crit, 0, 0),
-	SENSOR_ATTR_2(temp1_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0, 0),
+	SENSOR_ATTR_2(temp1_max, S_IRUGO | S_IWUSR, show_temp_max,
+		      store_temp_max, 0, 0),
+	SENSOR_ATTR_2(temp1_max_hyst, S_IRUGO | S_IWUSR,
+		      show_temp_max_hyst,
+		      store_temp_max_hyst, 0, 0),
+	SENSOR_ATTR_2(temp1_crit, S_IRUGO | S_IWUSR, show_temp_crit,
+		      store_temp_crit, 0, 0),
+	SENSOR_ATTR_2(temp1_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL,
+		      0, 0),
 	SENSOR_ATTR_2(temp1_type, S_IRUGO, show_temp_type, NULL, 0, 0),
-	SENSOR_ATTR_2(temp1_beep, S_IRUGO|S_IWUSR, show_temp_beep,
-		store_temp_beep, 0, 0),
+	SENSOR_ATTR_2(temp1_beep, S_IRUGO | S_IWUSR, show_temp_beep,
+		      store_temp_beep, 0, 0),
 	SENSOR_ATTR_2(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 0),
 	SENSOR_ATTR_2(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0, 0),
 	SENSOR_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 0, 1),
-	SENSOR_ATTR_2(temp2_max, S_IRUGO|S_IWUSR, show_temp_max,
-		store_temp_max, 0, 1),
-	SENSOR_ATTR_2(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
-		store_temp_max_hyst, 0, 1),
-	SENSOR_ATTR_2(temp2_crit, S_IRUGO|S_IWUSR, show_temp_crit,
-		store_temp_crit, 0, 1),
-	SENSOR_ATTR_2(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0, 1),
+	SENSOR_ATTR_2(temp2_max, S_IRUGO | S_IWUSR, show_temp_max,
+		      store_temp_max, 0, 1),
+	SENSOR_ATTR_2(temp2_max_hyst, S_IRUGO | S_IWUSR,
+		      show_temp_max_hyst,
+		      store_temp_max_hyst, 0, 1),
+	SENSOR_ATTR_2(temp2_crit, S_IRUGO | S_IWUSR, show_temp_crit,
+		      store_temp_crit, 0, 1),
+	SENSOR_ATTR_2(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL,
+		      0, 1),
 	SENSOR_ATTR_2(temp2_type, S_IRUGO, show_temp_type, NULL, 0, 1),
-	SENSOR_ATTR_2(temp2_beep, S_IRUGO|S_IWUSR, show_temp_beep,
-		store_temp_beep, 0, 1),
+	SENSOR_ATTR_2(temp2_beep, S_IRUGO | S_IWUSR, show_temp_beep,
+		      store_temp_beep, 0, 1),
 	SENSOR_ATTR_2(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 1),
 	SENSOR_ATTR_2(temp2_fault, S_IRUGO, show_temp_fault, NULL, 0, 1),
 	SENSOR_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 0, 2),
-	SENSOR_ATTR_2(temp3_max, S_IRUGO|S_IWUSR, show_temp_max,
-		store_temp_max, 0, 2),
-	SENSOR_ATTR_2(temp3_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
-		store_temp_max_hyst, 0, 2),
-	SENSOR_ATTR_2(temp3_crit, S_IRUGO|S_IWUSR, show_temp_crit,
-		store_temp_crit, 0, 2),
-	SENSOR_ATTR_2(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0, 2),
+	SENSOR_ATTR_2(temp3_max, S_IRUGO | S_IWUSR, show_temp_max,
+		      store_temp_max, 0, 2),
+	SENSOR_ATTR_2(temp3_max_hyst, S_IRUGO | S_IWUSR,
+		      show_temp_max_hyst,
+		      store_temp_max_hyst, 0, 2),
+	SENSOR_ATTR_2(temp3_crit, S_IRUGO | S_IWUSR, show_temp_crit,
+		      store_temp_crit, 0, 2),
+	SENSOR_ATTR_2(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL,
+		      0, 2),
 	SENSOR_ATTR_2(temp3_type, S_IRUGO, show_temp_type, NULL, 0, 2),
-	SENSOR_ATTR_2(temp3_beep, S_IRUGO|S_IWUSR, show_temp_beep,
-		store_temp_beep, 0, 2),
+	SENSOR_ATTR_2(temp3_beep, S_IRUGO | S_IWUSR, show_temp_beep,
+		      store_temp_beep, 0, 2),
 	SENSOR_ATTR_2(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 2),
 	SENSOR_ATTR_2(temp3_fault, S_IRUGO, show_temp_fault, NULL, 0, 2)
 };
 
-static struct sensor_device_attribute_2 f71882fg_fan_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_beep, S_IRUGO|S_IWUSR, show_fan_beep,
-		store_fan_beep, 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_beep, S_IRUGO|S_IWUSR, show_fan_beep,
-		store_fan_beep, 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_beep, S_IRUGO|S_IWUSR, show_fan_beep,
-		store_fan_beep, 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_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(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)
 };
 
-
 /* Super I/O functions */
 static inline int superio_inb(int base, int reg)
 {
@@ -290,11 +564,11 @@
 static inline void superio_enter(int base)
 {
 	/* according to the datasheet the key must be send twice! */
-	outb( SIO_UNLOCK_KEY, base);
-	outb( SIO_UNLOCK_KEY, base);
+	outb(SIO_UNLOCK_KEY, base);
+	outb(SIO_UNLOCK_KEY, base);
 }
 
-static inline void superio_select( int base, int ld)
+static inline void superio_select(int base, int ld)
 {
 	outb(SIO_REG_LDSEL, base);
 	outb(ld, base + 1);
@@ -310,6 +584,11 @@
 	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;
@@ -338,7 +617,15 @@
 	outb(val, data->addr + DATA_REG_OFFSET);
 }
 
-static struct f71882fg_data *f71882fg_update_device(struct device * dev)
+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);
 	int nr, reg, reg2;
@@ -346,34 +633,33 @@
 	mutex_lock(&data->update_lock);
 
 	/* Update once every 60 seconds */
-	if ( time_after(jiffies, data->last_limits + 60 * HZ ) ||
-			!data->valid) {
+	if (time_after(jiffies, data->last_limits + 60 * HZ) || !data->valid) {
 		data->in1_max = f71882fg_read8(data, F71882FG_REG_IN1_HIGH);
 		data->in_beep = f71882fg_read8(data, F71882FG_REG_IN_BEEP);
 
-		/* Get High & boundary temps*/
+		/* Get High & boundary temps */
 		for (nr = 0; nr < 3; nr++) {
 			data->temp_ovt[nr] = f71882fg_read8(data,
-						F71882FG_REG_TEMP_OVT(nr));
-			data->temp_high[nr] = f71882fg_read8(data,
-						F71882FG_REG_TEMP_HIGH(nr));
+						    F71882FG_REG_TEMP_OVT(nr));
+			data->temp_high[nr] =
+			    f71882fg_read8(data, F71882FG_REG_TEMP_HIGH(nr));
 		}
 
-		/* Have to hardcode hyst*/
+		/* Have to hardcode hyst */
 		data->temp_hyst[0] = f71882fg_read8(data,
-						F71882FG_REG_TEMP_HYST1) >> 4;
+					    F71882FG_REG_TEMP_HYST1) >> 4;
 		/* Hyst temps 2 & 3 stored in same register */
 		reg = f71882fg_read8(data, F71882FG_REG_TEMP_HYST23);
 		data->temp_hyst[1] = reg & 0x0F;
 		data->temp_hyst[2] = reg >> 4;
 
 		/* Have to hardcode type, because temp1 is special */
-		reg  = f71882fg_read8(data, F71882FG_REG_TEMP_TYPE);
+		reg = f71882fg_read8(data, F71882FG_REG_TEMP_TYPE);
 		reg2 = f71882fg_read8(data, F71882FG_REG_PECI);
 		if ((reg2 & 0x03) == 0x01)
-			data->temp_type[0] = 6 /* PECI */;
+			data->temp_type[0] = 6 /* PECI */ ;
 		else if ((reg2 & 0x03) == 0x02)
-			data->temp_type[0] = 5 /* AMDSI */;
+			data->temp_type[0] = 5 /* AMDSI */ ;
 		else
 			data->temp_type[0] = (reg & 0x02) ? 2 : 4;
 
@@ -390,24 +676,53 @@
 	/* Update every second */
 	if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
 		data->temp_status = f71882fg_read8(data,
-						F71882FG_REG_TEMP_STATUS);
+						   F71882FG_REG_TEMP_STATUS);
 		data->temp_diode_open = f71882fg_read8(data,
-						F71882FG_REG_TEMP_DIODE_OPEN);
+					       F71882FG_REG_TEMP_DIODE_OPEN);
 		for (nr = 0; nr < 3; nr++)
 			data->temp[nr] = f71882fg_read8(data,
-						F71882FG_REG_TEMP(nr));
+							F71882FG_REG_TEMP(nr));
 
 		data->fan_status = f71882fg_read8(data,
-						F71882FG_REG_FAN_STATUS);
-		for (nr = 0; nr < 4; nr++)
+						  F71882FG_REG_FAN_STATUS);
+		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->fan[nr] = f71882fg_read16(data,
-						F71882FG_REG_FAN(nr));
+							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->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->in_status = f71882fg_read8(data,
-						F71882FG_REG_IN_STATUS);
+		data->in_status = f71882fg_read8(data, F71882FG_REG_IN_STATUS);
 		for (nr = 0; nr < 9; nr++)
 			data->in[nr] = f71882fg_read8(data,
-						F71882FG_REG_IN(nr));
+						      F71882FG_REG_IN(nr));
 
 		data->last_updated = jiffies;
 		data->valid = 1;
@@ -419,8 +734,8 @@
 }
 
 /* Sysfs Interface */
-static ssize_t show_fan(struct device *dev, struct device_attribute *devattr,
-	char *buf)
+static ssize_t show_fan(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;
@@ -432,8 +747,38 @@
 	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;
+	int val = simple_strtoul(buf, NULL, 10);
+
+	mutex_lock(&data->update_lock);
+	if (data->pwm_enable & (1 << (2 * nr)))
+		count = -EINVAL;
+	else {
+		f71882fg_write16(data, F71882FG_REG_FAN_FULL_SPEED(nr),
+				 fan_to_reg(val));
+		data->fan_full_speed[nr] = fan_to_reg(val);
+	}
+	mutex_unlock(&data->update_lock);
+
+	return count;
+}
+
 static ssize_t show_fan_beep(struct device *dev, struct device_attribute
-	*devattr, char *buf)
+			     *devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
 	int nr = to_sensor_dev_attr_2(devattr)->index;
@@ -445,7 +790,7 @@
 }
 
 static ssize_t store_fan_beep(struct device *dev, struct device_attribute
-	*devattr, const char *buf, size_t count)
+			      *devattr, const char *buf, size_t count)
 {
 	struct f71882fg_data *data = dev_get_drvdata(dev);
 	int nr = to_sensor_dev_attr_2(devattr)->index;
@@ -464,7 +809,7 @@
 }
 
 static ssize_t show_fan_alarm(struct device *dev, struct device_attribute
-	*devattr, char *buf)
+			      *devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
 	int nr = to_sensor_dev_attr_2(devattr)->index;
@@ -475,8 +820,8 @@
 		return sprintf(buf, "0\n");
 }
 
-static ssize_t show_in(struct device *dev, struct device_attribute *devattr,
-	char *buf)
+static ssize_t show_in(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;
@@ -485,7 +830,7 @@
 }
 
 static ssize_t show_in_max(struct device *dev, struct device_attribute
-	*devattr, char *buf)
+			   *devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
 
@@ -493,7 +838,7 @@
 }
 
 static ssize_t store_in_max(struct device *dev, struct device_attribute
-	*devattr, const char *buf, size_t count)
+			    *devattr, const char *buf, size_t count)
 {
 	struct f71882fg_data *data = dev_get_drvdata(dev);
 	int val = simple_strtoul(buf, NULL, 10) / 8;
@@ -510,7 +855,7 @@
 }
 
 static ssize_t show_in_beep(struct device *dev, struct device_attribute
-	*devattr, char *buf)
+			    *devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
 	int nr = to_sensor_dev_attr_2(devattr)->index;
@@ -522,7 +867,7 @@
 }
 
 static ssize_t store_in_beep(struct device *dev, struct device_attribute
-	*devattr, const char *buf, size_t count)
+			     *devattr, const char *buf, size_t count)
 {
 	struct f71882fg_data *data = dev_get_drvdata(dev);
 	int nr = to_sensor_dev_attr_2(devattr)->index;
@@ -541,7 +886,7 @@
 }
 
 static ssize_t show_in_alarm(struct device *dev, struct device_attribute
-	*devattr, char *buf)
+			     *devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
 	int nr = to_sensor_dev_attr_2(devattr)->index;
@@ -552,8 +897,8 @@
 		return sprintf(buf, "0\n");
 }
 
-static ssize_t show_temp(struct device *dev, struct device_attribute *devattr,
-	char *buf)
+static ssize_t show_temp(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;
@@ -562,7 +907,7 @@
 }
 
 static ssize_t show_temp_max(struct device *dev, struct device_attribute
-	*devattr, char *buf)
+			     *devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
 	int nr = to_sensor_dev_attr_2(devattr)->index;
@@ -571,7 +916,7 @@
 }
 
 static ssize_t store_temp_max(struct device *dev, struct device_attribute
-	*devattr, const char *buf, size_t count)
+			      *devattr, const char *buf, size_t count)
 {
 	struct f71882fg_data *data = dev_get_drvdata(dev);
 	int nr = to_sensor_dev_attr_2(devattr)->index;
@@ -589,17 +934,17 @@
 }
 
 static ssize_t show_temp_max_hyst(struct device *dev, struct device_attribute
-	*devattr, char *buf)
+				  *devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
 	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	return sprintf(buf, "%d\n",
-		(data->temp_high[nr] - data->temp_hyst[nr]) * 1000);
+		       (data->temp_high[nr] - data->temp_hyst[nr]) * 1000);
 }
 
 static ssize_t store_temp_max_hyst(struct device *dev, struct device_attribute
-	*devattr, const char *buf, size_t count)
+				   *devattr, const char *buf, size_t count)
 {
 	struct f71882fg_data *data = dev_get_drvdata(dev);
 	int nr = to_sensor_dev_attr_2(devattr)->index;
@@ -619,19 +964,19 @@
 
 	/* convert value to register contents */
 	switch (nr) {
-		case 0:
-			val = val << 4;
-			break;
-		case 1:
-			val = val | (data->temp_hyst[2] << 4);
-			break;
-		case 2:
-			val = data->temp_hyst[1] | (val << 4);
-			break;
+	case 0:
+		val = val << 4;
+		break;
+	case 1:
+		val = val | (data->temp_hyst[2] << 4);
+		break;
+	case 2:
+		val = data->temp_hyst[1] | (val << 4);
+		break;
 	}
 
 	f71882fg_write8(data, nr ? F71882FG_REG_TEMP_HYST23 :
-		F71882FG_REG_TEMP_HYST1, val);
+			F71882FG_REG_TEMP_HYST1, val);
 
 store_temp_max_hyst_exit:
 	mutex_unlock(&data->update_lock);
@@ -639,7 +984,7 @@
 }
 
 static ssize_t show_temp_crit(struct device *dev, struct device_attribute
-	*devattr, char *buf)
+			      *devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
 	int nr = to_sensor_dev_attr_2(devattr)->index;
@@ -648,7 +993,7 @@
 }
 
 static ssize_t store_temp_crit(struct device *dev, struct device_attribute
-	*devattr, const char *buf, size_t count)
+			       *devattr, const char *buf, size_t count)
 {
 	struct f71882fg_data *data = dev_get_drvdata(dev);
 	int nr = to_sensor_dev_attr_2(devattr)->index;
@@ -666,17 +1011,17 @@
 }
 
 static ssize_t show_temp_crit_hyst(struct device *dev, struct device_attribute
-	*devattr, char *buf)
+				   *devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
 	int nr = to_sensor_dev_attr_2(devattr)->index;
 
 	return sprintf(buf, "%d\n",
-		(data->temp_ovt[nr] - data->temp_hyst[nr]) * 1000);
+		       (data->temp_ovt[nr] - data->temp_hyst[nr]) * 1000);
 }
 
 static ssize_t show_temp_type(struct device *dev, struct device_attribute
-	*devattr, char *buf)
+			      *devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
 	int nr = to_sensor_dev_attr_2(devattr)->index;
@@ -685,7 +1030,7 @@
 }
 
 static ssize_t show_temp_beep(struct device *dev, struct device_attribute
-	*devattr, char *buf)
+			      *devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
 	int nr = to_sensor_dev_attr_2(devattr)->index;
@@ -697,7 +1042,7 @@
 }
 
 static ssize_t store_temp_beep(struct device *dev, struct device_attribute
-	*devattr, const char *buf, size_t count)
+			       *devattr, const char *buf, size_t count)
 {
 	struct f71882fg_data *data = dev_get_drvdata(dev);
 	int nr = to_sensor_dev_attr_2(devattr)->index;
@@ -716,7 +1061,7 @@
 }
 
 static ssize_t show_temp_alarm(struct device *dev, struct device_attribute
-	*devattr, char *buf)
+			       *devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
 	int nr = to_sensor_dev_attr_2(devattr)->index;
@@ -728,7 +1073,7 @@
 }
 
 static ssize_t show_temp_fault(struct device *dev, struct device_attribute
-	*devattr, char *buf)
+			       *devattr, char *buf)
 {
 	struct f71882fg_data *data = f71882fg_update_device(dev);
 	int nr = to_sensor_dev_attr_2(devattr)->index;
@@ -739,20 +1084,330 @@
 		return sprintf(buf, "0\n");
 }
 
-static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
-	char *buf)
+static ssize_t show_pwm(struct device *dev,
+			struct device_attribute *devattr, char *buf)
 {
-	return sprintf(buf, DRVNAME "\n");
+	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 */
+		val = 255 * fan_from_reg(data->fan_target[nr])
+		    / fan_from_reg(data->fan_full_speed[nr]);
+	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;
+	int val = simple_strtoul(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 (2 & (data->pwm_enable >> (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;
+	int val = simple_strtoul(buf, NULL, 10);
+	if (val < 0 || val > 2)
+		return -EINVAL;
+
+	mutex_lock(&data->update_lock);
+	switch (val) {
+	case 0:
+	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);
+	if (!val)
+		store_pwm(dev, devattr, "255", 4);
+
+	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;
+	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;
+
+	switch (nr) {
+	case 0:
+		result = data->pwm_auto_point_hyst[0] & 15;
+		break;
+	case 1:
+		result = data->pwm_auto_point_hyst[0] >> 4;
+		break;
+	case 2:
+		result = data->pwm_auto_point_hyst[1] & 15;
+		break;
+	case 3:
+		result = data->pwm_auto_point_hyst[1] >> 4;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return sprintf(buf, "%d\n",
+		       data->pwm_auto_point_temp[nr][point] - 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 = 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);
+	val -= data->pwm_auto_point_temp[nr][0];
+	val = SENSORS_LIMIT(val, 0, 15);
+
+	mutex_lock(&data->update_lock);
+	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);
+	else
+		f71882fg_write8(data, F71882FG_REG_FAN_HYST1, 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 = 1 & (data->pwm_auto_point_mapping[nr] >> 4);
+
+	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);
+	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);
+	result >>= 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;
+	int val = simple_strtoul(buf, NULL, 10);
+	switch (val) {
+	case 1:
+		val = 1;
+		break;
+	case 2:
+		val = 2;
+		break;
+	case 4:
+		val = 3;
+		break;
+	default:
+		return -EINVAL;
+	}
+	val |= data->pwm_auto_point_mapping[nr] & 0xfc;
+	mutex_lock(&data->update_lock);
+	f71882fg_write8(data, F71882FG_REG_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", 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;
+	int val = simple_strtoul(buf, NULL, 10);
+	val = SENSORS_LIMIT(val, 0, 255);
+
+	mutex_lock(&data->update_lock);
+	f71882fg_write8(data, F71882FG_REG_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)
+{
+	return sprintf(buf, DRVNAME "\n");
+}
 
-static int __devinit f71882fg_probe(struct platform_device * pdev)
+static int __devinit f71882fg_probe(struct platform_device *pdev)
 {
 	struct f71882fg_data *data;
 	int err, i;
 	u8 start_reg;
 
-	if (!(data = kzalloc(sizeof(struct f71882fg_data), GFP_KERNEL)))
+	data = kzalloc(sizeof(struct f71882fg_data), GFP_KERNEL);
+	if (!data)
 		return -ENOMEM;
 
 	data->addr = platform_get_resource(pdev, IORESOURCE_IO, 0)->start;
@@ -770,7 +1425,8 @@
 	if (start_reg & 0x01) {
 		for (i = 0; i < ARRAY_SIZE(f71882fg_in_temp_attr); i++) {
 			err = device_create_file(&pdev->dev,
-					&f71882fg_in_temp_attr[i].dev_attr);
+						 &f71882fg_in_temp_attr[i].
+						 dev_attr);
 			if (err)
 				goto exit_unregister_sysfs;
 		}
@@ -779,7 +1435,8 @@
 	if (start_reg & 0x02) {
 		for (i = 0; i < ARRAY_SIZE(f71882fg_fan_attr); i++) {
 			err = device_create_file(&pdev->dev,
-					&f71882fg_fan_attr[i].dev_attr);
+						 &f71882fg_fan_attr[i].
+						 dev_attr);
 			if (err)
 				goto exit_unregister_sysfs;
 		}
@@ -799,7 +1456,7 @@
 
 	for (i = 0; i < ARRAY_SIZE(f71882fg_in_temp_attr); i++)
 		device_remove_file(&pdev->dev,
-					&f71882fg_in_temp_attr[i].dev_attr);
+				   &f71882fg_in_temp_attr[i].dev_attr);
 
 	for (i = 0; i < ARRAY_SIZE(f71882fg_fan_attr); i++)
 		device_remove_file(&pdev->dev, &f71882fg_fan_attr[i].dev_attr);
@@ -822,7 +1479,7 @@
 
 	for (i = 0; i < ARRAY_SIZE(f71882fg_in_temp_attr); i++)
 		device_remove_file(&pdev->dev,
-					&f71882fg_in_temp_attr[i].dev_attr);
+				   &f71882fg_in_temp_attr[i].dev_attr);
 
 	for (i = 0; i < ARRAY_SIZE(f71882fg_fan_attr); i++)
 		device_remove_file(&pdev->dev, &f71882fg_fan_attr[i].dev_attr);
@@ -860,8 +1517,7 @@
 	}
 
 	*address = superio_inw(sioaddr, SIO_REG_ADDR);
-	if (*address == 0)
-	{
+	if (*address == 0) {
 		printk(KERN_WARNING DRVNAME ": Base address not set\n");
 		goto exit;
 	}
@@ -871,14 +1527,15 @@
 	start_reg = f71882fg_read8(&data, F71882FG_REG_START);
 	if (!(start_reg & 0x03)) {
 		printk(KERN_WARNING DRVNAME
-			": Hardware monitoring not activated\n");
+		       ": Hardware monitoring not activated\n");
 		goto exit;
 	}
 
 	err = 0;
-	printk(KERN_INFO DRVNAME ": Found F71882FG chip at %#x, revision %d\n",
-		(unsigned int)*address,
-		(int)superio_inb(sioaddr, SIO_REG_DEVREV));
+	printk(KERN_INFO DRVNAME
+	       ": Found F71882FG chip at %#x, revision %d\n",
+	       (unsigned int)*address, (int)superio_inb(sioaddr,
+							SIO_REG_DEVREV));
 exit:
 	superio_exit(sioaddr);
 	return err;
@@ -887,9 +1544,9 @@
 static int __init f71882fg_device_add(unsigned short address)
 {
 	struct resource res = {
-		.start	= address,
-		.end	= address + REGION_LENGTH - 1,
-		.flags	= IORESOURCE_IO,
+		.start = address,
+		.end = address + REGION_LENGTH - 1,
+		.flags = IORESOURCE_IO,
 	};
 	int err;
 
@@ -926,10 +1583,12 @@
 	if (f71882fg_find(0x2e, &address) && f71882fg_find(0x4e, &address))
 		goto exit;
 
-	if ((err = platform_driver_register(&f71882fg_driver)))
+	err = platform_driver_register(&f71882fg_driver);
+	if (err)
 		goto exit;
 
-	if ((err = f71882fg_device_add(address)))
+	err = f71882fg_device_add(address);
+	if (err)
 		goto exit_driver;
 
 	return 0;

[-- Attachment #3: Type: text/plain, Size: 153 bytes --]

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [lm-sensors] [PATCH 2.6.25.4] f71882.c driver,
  2008-06-11  5:20 [lm-sensors] [PATCH 2.6.25.4] f71882.c driver, Mark van Doesburg
                   ` (8 preceding siblings ...)
  2008-10-07 18:36 ` Mark van Doesburg
@ 2008-10-10 22:57 ` Andrew Morton
  9 siblings, 0 replies; 11+ messages in thread
From: Andrew Morton @ 2008-10-10 22:57 UTC (permalink / raw)
  To: lm-sensors

On Tue, 07 Oct 2008 20:36:17 +0200
Mark van Doesburg <mark.vandoesburg@hetnet.nl> wrote:

> Hello Hans,
> 
> I've fixed the patch, and used scripts/Lindent to fix the style
> issues. I've also removed a few assignments in conditions.

<tries to remember what this was all about>

The patch you sent was unchangelogged, was not signed off and was not
in `patch -p1' form so I won't be doing anything with it.

Please do a full resend if needed, when convenient, thanks.

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2008-10-10 22:57 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-06-11  5:20 [lm-sensors] [PATCH 2.6.25.4] f71882.c driver, Mark van Doesburg
2008-06-11  8:18 ` Goede, J.W.R. de
2008-06-11 19:41 ` Mark van Doesburg
2008-06-12  8:05 ` Hans de Goede
2008-06-25  6:14 ` Mark van Doesburg
2008-06-25  7:22 ` Hans de Goede
2008-06-30 19:23 ` Mark van Doesburg
2008-06-30 21:02 ` Mark van Doesburg
2008-08-04 14:05 ` Hans de Goede
2008-10-07 18:36 ` Mark van Doesburg
2008-10-10 22:57 ` Andrew Morton

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.