/*
    vt1211.c - Part of lm_sensors, Linux kernel modules
                for hardware monitoring
                
    Copyright (c) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.



    Adapted to Linux 2.6 kernel by Lars Ekman <emil71se@yahoo.com>

    Updates (latest first):

    Fri Nov 17 15:46:00 CET 2006 (Folkert van Heusden <folkert@vanheusden.com>)
        Update for 2.6.18.2
    Wed Apr 26 18:29:55 CEST 2006 (Przemyslaw Bruski <pbruskispam at op.pl>)
	Update for 2.6.16
	    - owner and name fields are now in inner struct
	    - I2C_DRIVERID_VT1211 was no longer definde
    Mon Jan 16 15:00 PST 2006 (Eugene Weiss <eweiss@barracudanetworks.com>)
        Update for 2.6.15
            - Remove owner and name fields from i2c_driver structure.
            - Remove flags field from 12c_driver structure.
            - Change obsolete MODULE_PARM to module_param.
    Sun Sep 25 08:55:00 CEST 2005 -
	Update for 2.6.14-rc1. New i2c handling.
    Sun Sep  4 08:18:59 CEST 2005 -
	Update for 2.6.13; new device_attribute added in sys-file init.
    Sat Mar 19 11:23:36 CET 2005 -
	Preparations for official release;
	    - Remove all the #ifdef stuff (ALL_SENSORS and no convertion 
	      is now default).
	    - Remove normal_xxx_range defines which cause compile warnings.
	    - Remove uch_config  initialization in vt1211_init_client 
	      (trust the BIOS initialization instead, or use setting in 
	      sensors.conf)
    Tue Oct  5 17:17:01 CEST 2004 -
	Corrected faulty return value in "show_temp_hyst()".
    Sun Sep 26 09:14:36 CEST 2004 -
	Register conversion optional (disabled by default)

*/

/* Supports VIA VT1211 Super I/O sensors via ISA (LPC) accesses only. */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/moduleparam.h>
#include <linux/i2c.h>
#include <linux/i2c-isa.h>
#include <linux/hwmon.h>
#include <linux/hwmon-vid.h>
#include <linux/err.h>
#include <asm/io.h>

/*
  Even if the register values are not converted to uasable units in
  the driver, the values should still be multiplied with some
  factor. This factor seems to be 10 for 2.4 versions and 1000 now for
  2.6. The multiplier for "in" values (voltages) was commented out in
  the 2.4 version, and I don't know the correct setting for 2.6 yet.
  Since there is still some confusion around this I made
  easy-to-change macros for the factors.
 */
#define TEMP3_MULTIPLIER 1000
#define TEMP_MULTIPLIER 1000
#define IN_MULTIPLIER 10		/* Or what? */


static int force_addr = 0;
module_param(force_addr, int, 0644);
MODULE_PARM_DESC(force_addr,
		 "Initialize the base address of the sensors");

static unsigned short normal_i2c[] = { I2C_CLIENT_END };
static int isa_address = 0;

I2C_CLIENT_INSMOD_1(vt1211);

/* modified from kernel/include/traps.c */
#define	REG	0x2e	/* The register to read/write */
#define	DEV	0x07	/* Register: Logical device select */
#define	VAL	0x2f	/* The value to read/write */
#define PME	0x0b	/* The device with the hardware monitor */
#define	DEVID	0x20	/* Register: Device ID */

static inline void
superio_outb(int reg, int val)
{
	outb(reg, REG);
	outb(val, VAL);
}

static inline int
superio_inb(int reg)
{
	outb(reg, REG);
	return inb(VAL);
}

static inline void
superio_select(void)
{
	outb(DEV, REG);
	outb(PME, VAL);
}

static inline void
superio_enter(void)
{
	outb(0x87, REG);
	outb(0x87, REG);
}

static inline void
superio_exit(void)
{
	outb(0xAA, REG);
}


#define VT1211_DEVID 0x3c
#define VT1211_ACT_REG 0x30
#define VT1211_BASE_REG 0x60
#define VT1211_EXTENT 0x80

/* pwm numbered 1-2 */
#define VT1211_REG_PWM(nr) (0x5f + (nr))
#define VT1211_REG_PWM_CTL 0x51

/* The VT1211 registers */
/* We define the sensors as follows. Somewhat convoluted to minimize
   changes from via686a.
	Sensor		Voltage Mode	Temp Mode
	--------	------------	---------
	Reading 1			temp3
	Reading 3			temp1	not in vt1211
	UCH1/Reading2	in0		temp2
	UCH2		in1		temp4
	UCH3		in2		temp5
	UCH4		in3		temp6
	UCH5		in4		temp7
	3.3V		in5
	-12V		in6			not in vt1211
*/

/* ins numbered 0-6 */
#define VT1211_REG_IN_MAX(nr) ((nr)==0 ? 0x3d : 0x29 + ((nr) * 2))
#define VT1211_REG_IN_MIN(nr) ((nr)==0 ? 0x3e : 0x2a + ((nr) * 2))
#define VT1211_REG_IN(nr)     (0x21 + (nr))

/* fans numbered 1-2 */
#define VT1211_REG_FAN_MIN(nr) (0x3a + (nr))
#define VT1211_REG_FAN(nr)     (0x28 + (nr))

static const u8 regtemp[] = { 0x20, 0x21, 0x1f, 0x22, 0x23, 0x24, 0x25 };
static const u8 regover[] = { 0x39, 0x3d, 0x1d, 0x2b, 0x2d, 0x2f, 0x31 };
static const u8 reghyst[] = { 0x3a, 0x3e, 0x1e, 0x2c, 0x2e, 0x30, 0x32 };

/* temps numbered 1-7 */
#define VT1211_REG_TEMP(nr)		(regtemp[(nr) - 1])
#define VT1211_REG_TEMP_OVER(nr)	(regover[(nr) - 1])
#define VT1211_REG_TEMP_HYST(nr)	(reghyst[(nr) - 1])
#define VT1211_REG_TEMP_LOW3	0x4b	/* bits 7-6 */
#define VT1211_REG_TEMP_LOW2	0x49	/* bits 5-4 */
#define VT1211_REG_TEMP_LOW47	0x4d

#define VT1211_REG_CONFIG 0x40
#define VT1211_REG_ALARM1 0x41
#define VT1211_REG_ALARM2 0x42
#define VT1211_REG_VID    0x45
#define VT1211_REG_FANDIV 0x47
#define VT1211_REG_UCH_CONFIG 0x4a
#define VT1211_REG_TEMP1_CONFIG 0x4b
#define VT1211_REG_TEMP2_CONFIG 0x4c

/* temps 1-7; voltages 0-6 */
#define ISTEMP(i, ch_config) ((i) == 1 ? 1 : \
			      (i) == 3 ? 1 : \
			      (i) == 2 ? ((ch_config) >> 2) & 0x01 : \
			                 ((ch_config) >> ((i)-1)) & 0x01)
#define ISVOLT(i, ch_config) ((i) > 4 ? 1 : !(((ch_config) >> ((i)+2)) & 0x01))

#define DIV_FROM_REG(val) (1 << (val))
#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1)
#define PWM_FROM_REG(val) (val)
#define PWM_TO_REG(val) SENSORS_LIMIT((val), 0, 255)

#define TEMP3_FROM_REG(val) ((val)*TEMP3_MULTIPLIER)
#define TEMP3_FROM_REG10(val) (((val)*TEMP3_MULTIPLIER)/4)
#define TEMP3_TO_REG(val) (SENSORS_LIMIT(((val)<0? \
	(((val)-(TEMP3_MULTIPLIER/2))/TEMP3_MULTIPLIER):\
	((val)+(TEMP3_MULTIPLIER/2))/TEMP3_MULTIPLIER),0,255))
#define TEMP_FROM_REG(val) ((val)*TEMP_MULTIPLIER)
#define TEMP_FROM_REG10(val) (((val)*TEMP_MULTIPLIER)/4)
#define TEMP_TO_REG(val) (SENSORS_LIMIT(((val)<0? \
	(((val)-(TEMP_MULTIPLIER/2))/TEMP_MULTIPLIER):\
	((val)+(TEMP_MULTIPLIER/2))/TEMP_MULTIPLIER),0,255))


/********* FAN RPM CONVERT ********/
/* But this chip saturates back at 0, not at 255 like all the other chips.
   So, 0 means 0 RPM */
static inline u8 FAN_TO_REG(long rpm, int div)
{
	if (rpm == 0)
		return 0;
	rpm = SENSORS_LIMIT(rpm, 1, 1000000);
	return SENSORS_LIMIT((1310720 + rpm * div / 2) / (rpm * div), 1, 255);
}

#define MIN_TO_REG(a,b) FAN_TO_REG(a,b)
#define FAN_FROM_REG(val,div) ((val)==0?0:(val)==255?0:1310720/((val)*(div)))



struct vt1211_data {
	struct i2c_client client;
        struct class_device *class_dev;
	struct semaphore lock;
	enum chips type;

	struct semaphore update_lock;
	char valid;		/* !=0 if following fields are valid */
	unsigned long last_updated;	/* In jiffies */

	u8 in[7];		/* Register value */
	u8 in_max[7];		/* Register value */
	u8 in_min[7];		/* Register value */
	u16 temp[7];		/* Register value 10 bit */
	u8 temp_over[7];	/* Register value */
	u8 temp_hyst[7];	/* Register value */
	u8 fan[2];		/* Register value */
	u8 fan_min[2];		/* Register value */
	u8 fan_div[2];		/* Register encoding, shifted right */
	u16 alarms;		/* Register encoding */
	u8 pwm[2];		/* Register value */
	u8 pwm_ctl;		/* Register value */
	u8 vid;			/* Register encoding */
	u8 vrm;
	u8 uch_config;
};

static int vt1211_attach_adapter(struct i2c_adapter *adapter);
static int vt1211_isa_attach_adapter(struct i2c_adapter *adapter);
static int vt1211_detach_client(struct i2c_client *client);
static struct vt1211_data *vt1211_update_device(struct device *dev);

#ifndef I2C_DRIVERID_VT1211
#define I2C_DRIVERID_VT1211 1032
#endif

static struct i2c_driver vt1211_driver = {
	.driver = {
		.owner	= THIS_MODULE,
		.name	= "vt1211",
	},
	.id		= I2C_DRIVERID_VT1211,
	.attach_adapter	= vt1211_attach_adapter,
	.detach_client	= vt1211_detach_client,
};

static struct i2c_driver vt1211_isa_driver = {
	.driver = {
		.owner	= THIS_MODULE,
		.name	= "vt1211-isa",
	},
	.attach_adapter	= vt1211_isa_attach_adapter,
	.detach_client	= vt1211_detach_client,
};

static inline int vt_rdval(struct i2c_client *client, u8 reg)
{
	return (inb_p(client->addr + reg));
}

static inline void vt1211_write_value(
	struct i2c_client *client, u8 reg, u8 value)
{
	outb_p(value, client->addr + reg);
}

static void vt1211_init_client(struct i2c_client *client)
{
	struct vt1211_data *data = i2c_get_clientdata(client);

	data->vrm = vid_which_vrm();
	/* set "default" interrupt mode for alarms, which isn't the default */
	vt1211_write_value(client, VT1211_REG_TEMP1_CONFIG, 0);
	vt1211_write_value(client, VT1211_REG_TEMP2_CONFIG, 0);
}

#define IN_FROM_REG(val,n) ((val)*IN_MULTIPLIER)
#define IN_TO_REG(val,n)  (SENSORS_LIMIT( \
	(((val)+(IN_MULTIPLIER/2))/IN_MULTIPLIER),0,255))

/* ----------------------------------------------------------------------
   Temerature file definitions;
*/

static ssize_t show_temp(struct device *dev, char *buf, int nr)
{
	struct vt1211_data *data = vt1211_update_device(dev);
	if (nr == 2) {
		return sprintf(buf, "%d\n", TEMP3_FROM_REG10(data->temp[nr]));
	}
	return sprintf(buf, "%d\n", TEMP_FROM_REG10(data->temp[nr]));
}
static ssize_t show_temp_over(struct device *dev, char *buf, int nr)
{
	struct vt1211_data *data = vt1211_update_device(dev);
	if (nr == 2) {
		return sprintf(buf, "%d\n", 
			       TEMP3_FROM_REG(data->temp_over[nr]));
	}
	return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_over[nr]));
}

static ssize_t set_temp_over(
	struct device *dev, const char *buf, size_t count, int nr)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct vt1211_data *data = i2c_get_clientdata(client);
	long val = simple_strtol(buf, NULL, 10);
	if (nr == 2) {
		data->temp_over[nr] = TEMP3_TO_REG(val);
	} else {
		data->temp_over[nr] = TEMP_TO_REG(val);
	}
	vt1211_write_value(client,
			   VT1211_REG_TEMP_OVER(nr + 1),
			   data->temp_over[nr]);
	return count;
}

static ssize_t show_temp_hyst(struct device *dev, char *buf, int nr)
{
	struct vt1211_data *data = vt1211_update_device(dev);
	if (nr == 2) {
		return sprintf(buf, "%d\n", 
			       TEMP3_FROM_REG(data->temp_hyst[nr]));
	}
	return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_hyst[nr]));
}

static ssize_t set_temp_hyst(
	struct device *dev, const char *buf, size_t count, int nr)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct vt1211_data *data = i2c_get_clientdata(client);
	long val = simple_strtol(buf, NULL, 10);
	if (nr == 2) {
		data->temp_hyst[nr] = TEMP3_TO_REG(val);
	} else {
		data->temp_hyst[nr] = TEMP_TO_REG(val);
	}
	vt1211_write_value(client,
			   VT1211_REG_TEMP_HYST(nr + 1),
			   data->temp_hyst[nr]);
	return count;
}

#define show_temp_offset(offset)					\
static ssize_t show_temp_##offset (struct device *dev, struct device_attribute *attr, char *buf)	\
{									\
	return show_temp(dev, buf, 0x##offset -1);			\
}									\
static ssize_t show_temp_over_##offset (struct device *dev, struct device_attribute *attr, char *buf)\
{									\
	return show_temp_over(dev, buf, 0x##offset -1);			\
}									\
static ssize_t set_temp_over_##offset (					\
	struct device *dev, struct device_attribute *attr, const char *buf, size_t count)		\
{									\
	return set_temp_over(dev, buf, count, 0x##offset - 1);		\
}									\
static ssize_t show_temp_hyst_##offset (struct device *dev, struct device_attribute *attr, char *buf)	\
{									\
	return show_temp_hyst(dev, buf, 0x##offset -1);			\
}									\
static ssize_t set_temp_hyst_##offset (					\
	struct device *dev, struct device_attribute *attr, const char *buf, size_t count)		\
{									\
	return set_temp_hyst(dev, buf, count, 0x##offset - 1);		\
}									\
static DEVICE_ATTR(temp##offset##_input, S_IRUGO, show_temp_##offset, NULL); \
static DEVICE_ATTR(temp##offset##_max, S_IRUGO | S_IWUSR,		\
		   show_temp_over_##offset, set_temp_over_##offset);	\
static DEVICE_ATTR(temp##offset##_max_hyst, S_IRUGO | S_IWUSR,		\
		show_temp_hyst_##offset, set_temp_hyst_##offset)


show_temp_offset(1);
show_temp_offset(2);
show_temp_offset(3);
show_temp_offset(4);
show_temp_offset(5);
show_temp_offset(6);
show_temp_offset(7);

/* ----------------------------------------------------------------------
   uch_config;
 */

static ssize_t show_uch_config(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct vt1211_data *data = vt1211_update_device(dev);
	return sprintf(buf, "%d\n", data->uch_config & 0x7c);
}

static ssize_t set_uch_config(
	struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct vt1211_data *data = vt1211_update_device(dev);
	long val = simple_strtol(buf, NULL, 10);
	data->uch_config = (data->uch_config & 0x83)|(val & 0x7c);
	vt1211_write_value(client, VT1211_REG_UCH_CONFIG, data->uch_config);
	return count;
}

static DEVICE_ATTR(uch_config, S_IRUGO | S_IWUSR, 
		   show_uch_config, set_uch_config);

/* ----------------------------------------------------------------------
   Fan definitions;
 */

static ssize_t show_fan(struct device *dev, char *buf, int nr)
{
	struct vt1211_data *data = vt1211_update_device(dev);
	return sprintf(buf, "%d\n", 
		       FAN_FROM_REG(data->fan[nr],
				    DIV_FROM_REG(data->fan_div[nr])));
}
static ssize_t show_fan_min(struct device *dev, char *buf, int nr)
{
	struct vt1211_data *data = vt1211_update_device(dev);
	return sprintf(buf, "%d\n", 
		       FAN_FROM_REG(data->fan_min[nr],
				    DIV_FROM_REG(data->fan_div[nr])));
}
static ssize_t set_fan_min(
	struct device *dev, const char *buf, size_t count, int nr)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct vt1211_data *data = i2c_get_clientdata(client);
	long val = simple_strtol(buf, NULL, 10);
	data->fan_min[nr] = MIN_TO_REG(val, DIV_FROM_REG(data->fan_div[nr]));
	vt1211_write_value(client, VT1211_REG_FAN_MIN(nr+1), 
			   data->fan_min[nr]);
	return count;
}
static ssize_t show_fan_div(struct device *dev, char *buf, int nr)
{
	struct vt1211_data *data = vt1211_update_device(dev);
	return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[nr]));
}
static ssize_t set_fan_div(
	struct device *dev, const char *buf, size_t count, int nr)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct vt1211_data *data = i2c_get_clientdata(client);
	long val = simple_strtol(buf, NULL, 10);
	int old = vt_rdval(client, VT1211_REG_FANDIV);
	if (nr == 0) {
		data->fan_div[0] = DIV_TO_REG(val);
		old = (old & 0xcf) | (data->fan_div[0] << 4);
	} else {
		data->fan_div[1] = DIV_TO_REG(val);
		old = (old & 0x3f) | (data->fan_div[1] << 6);
	}
	vt1211_write_value(client, VT1211_REG_FANDIV, old);
	return count;
}
static ssize_t show_fan_pwm(struct device *dev, char *buf, int nr)
{
	struct vt1211_data *data = vt1211_update_device(dev);
	return sprintf(buf, "%d\n", PWM_FROM_REG(data->pwm[nr]));
}
static ssize_t set_fan_pwm(
	struct device *dev, const char *buf, size_t count, int nr)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct vt1211_data *data = i2c_get_clientdata(client);
	long val = simple_strtol(buf, NULL, 10);
	data->pwm[nr] = PWM_TO_REG(val);
	vt1211_write_value(client, VT1211_REG_PWM(nr+1), data->pwm[nr]);
	return count;
}
static ssize_t show_fan_pwm_ctl(struct device *dev, char *buf, int nr)
{
	struct vt1211_data *data = vt1211_update_device(dev);
	return sprintf(buf, "%d\n",(data->pwm_ctl >> (3 + (4 * nr))) & 1);
}
static ssize_t set_fan_pwm_ctl(
	struct device *dev, const char *buf, size_t count, int nr)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct vt1211_data *data = i2c_get_clientdata(client);
	long val = simple_strtol(buf, NULL, 10);
	if (val) {
		data->pwm_ctl |= (0x08 << (4 * nr));
		vt1211_write_value(client, VT1211_REG_PWM_CTL, data->pwm_ctl);
	} else {
		data->pwm_ctl &= ~ (0x08 << (4 * nr));
		vt1211_write_value(client, VT1211_REG_PWM_CTL, data->pwm_ctl);
	}
	return count;
}

#define show_fan_offset(offset)						\
static ssize_t show_fan_##offset (struct device *dev, struct device_attribute *attr, char *buf)	\
{									\
	return show_fan(dev, buf, 0x##offset - 1);			\
}									\
static ssize_t show_fan_min_##offset (struct device *dev, struct device_attribute *attr, char *buf)	\
{									\
	return show_fan_min(dev, buf, 0x##offset - 1);			\
}									\
static ssize_t set_fan_min_##offset (					\
	struct device *dev, struct device_attribute *attr, const char *buf, size_t count)		\
{									\
	return set_fan_min(dev, buf, count, 0x##offset - 1);		\
}									\
static ssize_t show_fan_div_##offset (struct device *dev, struct device_attribute *attr, char *buf)	\
{									\
	return show_fan_div(dev, buf, 0x##offset - 1);			\
}									\
static ssize_t set_fan_div_##offset (					\
	struct device *dev, struct device_attribute *attr, const char *buf, size_t count)		\
{									\
	return set_fan_div(dev, buf, count, 0x##offset - 1);		\
}									\
static ssize_t show_fan_pwm_##offset (struct device *dev, struct device_attribute *attr, char *buf)	\
{									\
	return show_fan_pwm(dev, buf, 0x##offset - 1);			\
}									\
static ssize_t set_fan_pwm_##offset (					\
	struct device *dev, struct device_attribute *attr, const char *buf, size_t count)		\
{									\
	return set_fan_pwm(dev, buf, count, 0x##offset - 1);		\
}									\
static ssize_t show_fan_pwm_ctl_##offset (struct device *dev, struct device_attribute *attr, char *buf)\
{									\
	return show_fan_pwm_ctl(dev, buf, 0x##offset - 1);		\
}									\
static ssize_t set_fan_pwm_ctl_##offset (				\
	struct device *dev, struct device_attribute *attr, const char *buf, size_t count)		\
{									\
	return set_fan_pwm_ctl(dev, buf, count, 0x##offset - 1);	\
}									\
static DEVICE_ATTR(fan##offset##_input, S_IRUGO, show_fan_##offset, NULL); \
static DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR,		\
	show_fan_min_##offset, set_fan_min_##offset);			\
static DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR,		\
	 show_fan_div_##offset, set_fan_div_##offset);			\
static DEVICE_ATTR(fan##offset##_pwm, S_IRUGO | S_IWUSR,		\
	show_fan_pwm_##offset, set_fan_pwm_##offset);			\
static DEVICE_ATTR(fan##offset##_pwm_enable, S_IRUGO | S_IWUSR,		\
	show_fan_pwm_ctl_##offset, set_fan_pwm_ctl_##offset);

show_fan_offset(1);
show_fan_offset(2);

/* ----------------------------------------------------------------------
   Voltage (in) file definitions;
*/

static ssize_t show_in(struct device *dev, char *buf, int nr)
{
	struct vt1211_data *data = vt1211_update_device(dev);
	return sprintf(buf, "%d\n", IN_FROM_REG(data->in[nr], nr));
}
static ssize_t show_in_min(struct device *dev, char *buf, int nr)
{
	struct vt1211_data *data = vt1211_update_device(dev);
	return sprintf(buf, "%d\n", IN_FROM_REG(data->in_min[nr], nr));
}
static ssize_t set_in_min(
	struct device *dev, const char *buf, size_t count, int nr)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct vt1211_data *data = i2c_get_clientdata(client);
	long val = simple_strtol(buf, NULL, 10);
	data->in_min[nr] = IN_TO_REG(val, nr);
	vt1211_write_value(client, VT1211_REG_IN_MIN(nr), data->in_min[nr]);
	return count;
}
static ssize_t show_in_max(struct device *dev, char *buf, int nr)
{
	struct vt1211_data *data = vt1211_update_device(dev);
	return sprintf(buf, "%d\n", IN_FROM_REG(data->in_max[nr], nr));
}
static ssize_t set_in_max(
	struct device *dev, const char *buf, size_t count, int nr)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct vt1211_data *data = i2c_get_clientdata(client);
	long val = simple_strtol(buf, NULL, 10);
	data->in_max[nr] = IN_TO_REG(val, nr);
	vt1211_write_value(client, VT1211_REG_IN_MAX(nr), data->in_max[nr]);
	return count;
}

#define show_in_offset(offset)						\
static ssize_t show_in_##offset (struct device *dev, struct device_attribute *attr, char *buf)		\
{									\
	return show_in(dev, buf, 0x##offset);				\
}									\
static ssize_t show_in_min_##offset (struct device *dev, struct device_attribute *attr, char *buf)	\
{									\
	return show_in_min(dev, buf, 0x##offset);			\
}									\
static ssize_t set_in_min_##offset (					\
	struct device *dev, struct device_attribute *attr, const char *buf, size_t count)		\
{									\
	return set_in_min(dev, buf, count, 0x##offset);			\
}									\
static ssize_t show_in_max_##offset (struct device *dev, struct device_attribute *attr, char *buf)	\
{									\
	return show_in_max(dev, buf, 0x##offset);			\
}									\
static ssize_t set_in_max_##offset (					\
	struct device *dev, struct device_attribute *attr, const char *buf, size_t count)		\
{									\
	return set_in_max(dev, buf, count, 0x##offset);			\
}									\
static DEVICE_ATTR(in##offset##_input, S_IRUGO, show_in_##offset, NULL); \
static DEVICE_ATTR(in##offset##_min, S_IRUGO | S_IWUSR,			\
	show_in_min_##offset, set_in_min_##offset);			\
static DEVICE_ATTR(in##offset##_max, S_IRUGO | S_IWUSR,			\
	show_in_max_##offset, set_in_max_##offset);			\

show_in_offset(0);
show_in_offset(1);
show_in_offset(2);
show_in_offset(3);
show_in_offset(4);
show_in_offset(5);
show_in_offset(6);

static ssize_t show_vid(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct vt1211_data *data = vt1211_update_device(dev);
	return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm));
}
static DEVICE_ATTR(in0_ref, S_IRUGO, show_vid, NULL);

static ssize_t show_vrm(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct vt1211_data *data = vt1211_update_device(dev);
	return sprintf(buf, "%d\n", data->vrm);
}
static ssize_t set_vrm(
	struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct vt1211_data *data = i2c_get_clientdata(client);
	long val = simple_strtol(buf, NULL, 10);
	data->vrm = val;
	return count;
}
static DEVICE_ATTR(vrm, S_IRUGO | S_IWUSR, show_vrm, set_vrm);

/* ----------------------------------------------------------------------
   alarm file definitions;
*/

static ssize_t show_alarms(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct vt1211_data *data = vt1211_update_device(dev);
	return sprintf(buf, "%d\n", data->alarms);
}
static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);


/*
  END of file definitions
  ---------------------------------------------------------------------- */

/*
  (vt1211_update_device is almost unchanged since 2.4.x)
 */
static struct vt1211_data *vt1211_update_device(struct device *dev)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct vt1211_data *data = i2c_get_clientdata(client);

	down(&data->update_lock);

	if ((jiffies - data->last_updated > HZ + HZ / 2) ||
	    (jiffies < data->last_updated) || !data->valid) {

		int i, j;

		dev_dbg(&client->dev, "Starting vt1211 update\n");

		for (i = 0; i <= 5; i++) {
			if(ISVOLT(i, data->uch_config)) {
				data->in[i] =vt_rdval(client,VT1211_REG_IN(i));
				data->in_min[i] = vt_rdval(client,
				                        VT1211_REG_IN_MIN(i));
				data->in_max[i] = vt_rdval(client,
				                        VT1211_REG_IN_MAX(i));
			} else {
				data->in[i] = 0;
				data->in_min[i] = 0;
				data->in_max[i] = 0;
			}
		}
		for (i = 1; i <= 2; i++) {
			data->fan[i - 1] = vt_rdval(client, VT1211_REG_FAN(i));
			data->fan_min[i - 1] = vt_rdval(client,
						     VT1211_REG_FAN_MIN(i));
		}
		for (i = 2; i <= 7; i++) {
			if(ISTEMP(i, data->uch_config)) {
				data->temp[i - 1] = vt_rdval(client,
					             VT1211_REG_TEMP(i)) << 2;
				switch(i) {
					case 1:
						/* ? */
						j = 0;
						break;
					case 2:
						j = (vt_rdval(client,
						  VT1211_REG_TEMP_LOW2) &
						                    0x30) >> 4;
						break;
					case 3:
						j = (vt_rdval(client,
						  VT1211_REG_TEMP_LOW3) &
						                    0xc0) >> 6;
						break;
					case 4:
					case 5:
					case 6:
					case 7:
					default:
						j = (vt_rdval(client,
						  VT1211_REG_TEMP_LOW47) >>
						            ((i-4)*2)) & 0x03;
						break;
	
				}
				data->temp[i - 1] |= j;
				data->temp_over[i - 1] = vt_rdval(client,
					              VT1211_REG_TEMP_OVER(i));
				data->temp_hyst[i - 1] = vt_rdval(client,
					              VT1211_REG_TEMP_HYST(i));
			} else {
				data->temp[i - 1] = 0;
				data->temp_over[i - 1] = 0;
				data->temp_hyst[i - 1] = 0;
			}
		}

		for (i = 1; i <= 2; i++) {
			data->fan[i - 1] = vt_rdval(client, VT1211_REG_FAN(i));
			data->fan_min[i - 1] = vt_rdval(client,
			                                VT1211_REG_FAN_MIN(i));
			data->pwm[i - 1] = vt_rdval(client, VT1211_REG_PWM(i));
		}

		data->pwm_ctl = vt_rdval(client, VT1211_REG_PWM_CTL);
		i = vt_rdval(client, VT1211_REG_FANDIV);
		data->fan_div[0] = (i >> 4) & 0x03;
		data->fan_div[1] = i >> 6;
		data->alarms = vt_rdval(client, VT1211_REG_ALARM1) |
		                    (vt_rdval(client, VT1211_REG_ALARM2) << 8);
		data->vid= vt_rdval(client, VT1211_REG_VID) & 0x1f;

		data->last_updated = jiffies;
		data->valid = 1;
	}

	up(&data->update_lock);

	return data;
}

/* This function is called by i2c_probe */
int vt1211_detect(struct i2c_adapter *adapter, int address, int kind)
{
	struct i2c_client *new_client = 0;
	struct vt1211_data *data;
	int err = 0;
	u8 val;

	if (!i2c_is_isa_adapter(adapter)) {
		return 0;
	}

	if(force_addr) {
		printk("vt1211.o: forcing ISA address 0x%04X\n", address);
		address = force_addr & ~(VT1211_EXTENT - 1);
		superio_enter();
		superio_select();
		superio_outb(VT1211_BASE_REG, address >> 8);
		superio_outb(VT1211_BASE_REG+1, address & 0xff);
		superio_exit();
	}

	superio_enter();
	superio_select();
	if((val = 0x01 & superio_inb(VT1211_ACT_REG)) == 0)
		superio_outb(VT1211_ACT_REG, 1);
	superio_exit();

	if (!request_region(address, VT1211_EXTENT, vt1211_isa_driver.driver.name)) {
		return -EBUSY;
	}

	/* OK. For now, we presume we have a valid client. We now
	   create the client structure, even though we cannot fill it
	   completely yet.
	   */

	if (!(data = kmalloc(sizeof(struct vt1211_data), GFP_KERNEL))) {
		err = -ENOMEM;
		goto ERROR1;
	}
	memset(data, 0, sizeof(struct vt1211_data));
	new_client = &data->client;

	init_MUTEX(&data->lock);
	i2c_set_clientdata(new_client, data);
	new_client->addr = address;
	new_client->adapter = adapter;
	new_client->driver = &vt1211_isa_driver;

	/* Fill in the remaining client fields and put into the global list */
	strlcpy(new_client->name, "vt1211", I2C_NAME_SIZE);
	data->type = vt1211;

	data->valid = 0;
	init_MUTEX(&data->update_lock);

	/* Tell the I2C layer a new client has arrived */
	if ((err = i2c_attach_client(new_client)))
		goto ERROR1;

	vt1211_init_client(new_client);

	data->class_dev = hwmon_device_register(&new_client->dev);
	if (IS_ERR(data->class_dev)) {
		err = PTR_ERR(data->class_dev);
		goto ERROR2;
	}


	device_create_file(&new_client->dev, &dev_attr_uch_config);

	device_create_file(&new_client->dev, &dev_attr_temp1_input);
	device_create_file(&new_client->dev, &dev_attr_temp1_max);
	device_create_file(&new_client->dev, &dev_attr_temp1_max_hyst);
	device_create_file(&new_client->dev, &dev_attr_temp2_input);
	device_create_file(&new_client->dev, &dev_attr_temp2_max);
	device_create_file(&new_client->dev, &dev_attr_temp2_max_hyst);
	device_create_file(&new_client->dev, &dev_attr_temp3_input);
	device_create_file(&new_client->dev, &dev_attr_temp3_max);
	device_create_file(&new_client->dev, &dev_attr_temp3_max_hyst);
	device_create_file(&new_client->dev, &dev_attr_temp4_input);
	device_create_file(&new_client->dev, &dev_attr_temp4_max);
	device_create_file(&new_client->dev, &dev_attr_temp4_max_hyst);
	device_create_file(&new_client->dev, &dev_attr_temp5_input);
	device_create_file(&new_client->dev, &dev_attr_temp5_max);
	device_create_file(&new_client->dev, &dev_attr_temp5_max_hyst);
	device_create_file(&new_client->dev, &dev_attr_temp6_input);
	device_create_file(&new_client->dev, &dev_attr_temp6_max);
	device_create_file(&new_client->dev, &dev_attr_temp6_max_hyst);
	device_create_file(&new_client->dev, &dev_attr_temp7_input);
	device_create_file(&new_client->dev, &dev_attr_temp7_max);
	device_create_file(&new_client->dev, &dev_attr_temp7_max_hyst);

	device_create_file(&new_client->dev, &dev_attr_fan1_input);
	device_create_file(&new_client->dev, &dev_attr_fan2_input);
	device_create_file(&new_client->dev, &dev_attr_fan1_div);
	device_create_file(&new_client->dev, &dev_attr_fan2_div);
	device_create_file(&new_client->dev, &dev_attr_fan1_min);
	device_create_file(&new_client->dev, &dev_attr_fan2_min);
	device_create_file(&new_client->dev, &dev_attr_fan1_pwm);
	device_create_file(&new_client->dev, &dev_attr_fan2_pwm);
	device_create_file(&new_client->dev, &dev_attr_fan1_pwm_enable);
	device_create_file(&new_client->dev, &dev_attr_fan2_pwm_enable);

	device_create_file(&new_client->dev, &dev_attr_in0_ref);
	device_create_file(&new_client->dev, &dev_attr_alarms);
	device_create_file(&new_client->dev, &dev_attr_vrm);
	device_create_file(&new_client->dev, &dev_attr_in0_input);
	device_create_file(&new_client->dev, &dev_attr_in0_min);
	device_create_file(&new_client->dev, &dev_attr_in0_max);
	device_create_file(&new_client->dev, &dev_attr_in1_input);
	device_create_file(&new_client->dev, &dev_attr_in1_min);
	device_create_file(&new_client->dev, &dev_attr_in1_max);
	device_create_file(&new_client->dev, &dev_attr_in2_input);
	device_create_file(&new_client->dev, &dev_attr_in2_min);
	device_create_file(&new_client->dev, &dev_attr_in2_max);
	device_create_file(&new_client->dev, &dev_attr_in3_input);
	device_create_file(&new_client->dev, &dev_attr_in3_min);
	device_create_file(&new_client->dev, &dev_attr_in3_max);
	device_create_file(&new_client->dev, &dev_attr_in4_input);
	device_create_file(&new_client->dev, &dev_attr_in4_min);
	device_create_file(&new_client->dev, &dev_attr_in4_max);
	device_create_file(&new_client->dev, &dev_attr_in5_input);
	device_create_file(&new_client->dev, &dev_attr_in5_min);
	device_create_file(&new_client->dev, &dev_attr_in5_max);
	device_create_file(&new_client->dev, &dev_attr_in6_input);
	device_create_file(&new_client->dev, &dev_attr_in6_min);
	device_create_file(&new_client->dev, &dev_attr_in6_max);
	return 0;

 ERROR2:
	i2c_detach_client(new_client);
 ERROR1:
	kfree(new_client);
	release_region(address, VT1211_EXTENT);
	return err;
}


static int vt1211_attach_adapter(struct i2c_adapter *adapter)
{
	if (!(adapter->class & I2C_CLASS_HWMON))
		return 0;
	return i2c_probe(adapter, &addr_data, vt1211_detect);
}

static int vt1211_isa_attach_adapter(struct i2c_adapter *adapter)
{
	return vt1211_detect(adapter, isa_address, -1);
}


static int vt1211_detach_client(struct i2c_client *client)
{
	struct vt1211_data *data = i2c_get_clientdata(client);
	int err;

	/* release ISA region first */
	release_region(client->addr, VT1211_EXTENT);

	hwmon_device_unregister(data->class_dev);

	/* now it's safe to scrap the rest */
	if ((err = i2c_detach_client(client))) {
		return err;
	}

	kfree(data);
	return 0;
}

static int vt1211_find(int *address)
{
	u16 val;

	superio_enter();
	val= superio_inb(DEVID);
	if(VT1211_DEVID != val) {
		superio_exit();
		return -ENODEV;
	}

	superio_select();
	val = (superio_inb(VT1211_BASE_REG) << 8) |
	       superio_inb(VT1211_BASE_REG + 1);
	*address = val & ~(VT1211_EXTENT - 1);
	if (*address == 0 && force_addr == 0) {
		printk("vt1211: base address not set-use force_addr=0xaddr\n");
		superio_exit();
		return -ENODEV;
	}
	if (force_addr)
		*address = force_addr;	/* so detect will get called */

	superio_exit();
	return 0;
}





static int __init sm_vt1211_init(void)
{
        int res;

        /*printk("vt1211 for 2.6: (%s %s) loaded ...\n", __DATE__, __TIME__);
        */
	if (vt1211_find(&isa_address)) {
		printk("vt1211: not detected, module not inserted.\n");
		return -ENODEV;
	}

	res = i2c_add_driver(&vt1211_driver);
	if (res) return res;

	res = i2c_isa_add_driver(&vt1211_isa_driver);
	if (res) {
		i2c_isa_del_driver(&vt1211_driver);
		return res;
	}
	return 0;
}

static void __exit sm_vt1211_exit(void)
{
	i2c_isa_del_driver(&vt1211_isa_driver);
	i2c_del_driver(&vt1211_driver);
}



MODULE_AUTHOR("Mark D. Studebaker <mdsxyz123@yahoo.com>, "
	      "Updated to 2.6 by Lars Ekman <emil71se@yahoo.com>");
MODULE_DESCRIPTION("VT1211 sensors");
MODULE_LICENSE("GPL");

module_init(sm_vt1211_init);
module_exit(sm_vt1211_exit);
