/*
 * National Semiconductor LM85 and compatible hardware monitor chips
 *
 ***************************************************************
 * Before calling these routines, one must call method->Open() *
 * After calling these routines, one must call method->Close() *
 ***************************************************************
 *

National Semiconductor
         Chip         Temp    Volt    Fan     SMBus   IOport
        lm85           3       5       4       yes     no

Analog Devices
         Chip         Temp    Volt    Fan     SMBus   IOport
        adm1024        2       5       2       yes     no
        adm1025        2       5       -       yes     no
        adm1027        3       5       4       yes     no
        adt7463        3       5       4       yes     no

Standard Microsystem
         Chip         Temp    Volt    Fan     SMBus   IOport
        emc6d10x       3       5       4       yes     no

 *
 * by YRS
 */


#include	<stdio.h>
#include	<string.h>
#include	"sensors.h"

/* external (global) data */
extern int smb_slave;
extern LM_METHODS method_isa, method_smb;
extern int numSMBSlave, canSMBSlave[128];


#define	LM85_ADDR_START		0x58	/*0x58-0x5A*/
#define	LM85_ADDR_END		0x5C

#define	LM85_DEVID		0x3D
#define	LM85_VENDID		0x3E
#define	LM85_VERSTEP	0x3F
#define	LM85_CONF		0x40

#define	LM85_TEMP(nr)	(0x25 + (nr))
#define	LM85_VOLT(nr)	(0x20 + (nr))
#define	LM85_FANLSB(nr)	(0x28 + (nr) * 2)
#define	LM85_FANMSB(nr)	(0x29 + (nr) * 2)
#define	LM85_VID		0x43
#define ADM_24FAN(nr)	(0x28 + (nr))
#define ADM_24FANDIV	0x47
#define ADM_24MODE		0x16
#define ADM_TEMPOFF(nr)	(0x70 + (nr))
#define ADM_EXTRES1		0x76
#define ADM_EXTRES2		0x77
#define ADM_FANPPR		0x7B

static int lm85chipid = 0;

static float Vfac;

static	int		lm85_probe(LM_METHODS *);
static	int		lm85_chk(LM_METHODS *);
static	float	lm85_temp(LM_METHODS *, int);
static	int		lm85_fanrpm(LM_METHODS *, int);
static	float	lm85_volt(LM_METHODS *, int);
#if 0
static	int		lm85_fan_ppr(LM_METHODS *, int);
static	void	lm85_fan_ppr_set(LM_METHODS *, int, int);
#endif

#define BUFF_LEN 128
static char buff[BUFF_LEN];

static int adm_24fan[2] = {0, 0};

SENSOR lm85 = {
	buff,
	lm85_probe,
	lm85_temp,
	lm85_volt,
	lm85_fanrpm
};


enum lm85_chips {
	NOSENSER,
	LM85,
	EMC6D10X,
	ADM1024,
	ADM1025,
	ADM1027,
	ADT7463,
};

static char *lm85chip[] = {
	"No Sensor",
	"Nat.Semi.Con. Chip LM85",
	"SMSC Chip EMC6D10X",
	"Analog Dev. Chip ADM1024",
	"Analog Dev. Chip ADM1025",
	"Analog Dev. Chip ADM1027",
	"Analog Dev. Chip ADT7463",
	NULL };


#define LM85_chkRegNum 20

/* Register checked for probing */
static int chkReg[] = {
	0x20, 0x21, 0x22, 0x23,
	0x24, 0x25, 0x26, 0x27,
	0x28, 0x29, 0x2A, 0x2B,
	0x2C, 0x2D, 0x2E, 0x2F,
	0x30, 0x31, 0x32, 0x33,
	0x34, 0x35, 0x36, 0x37,
	0x38, 0x39, 0x3A, 0x3B,
	0x3C, 0x3D, 0x3E, 0x3F,
	0x40, 0x41, 0x42, 0x43,
	0x70, 0x71, 0x72, 0x73,
	0x76, 0x77, 0x78, 0x7B,
	-1 };


/*
 *  return 0 if not probed
 */
static	int     lm85_probe(LM_METHODS *method)
{
	int n, save;

	if (method != &method_smb)
		return 0;

	save = smb_slave;

	for (n = LM85_ADDR_START; n <= LM85_ADDR_END;) {
		if (!(smb_slave = get_smb_slave(n, LM85_ADDR_END)))
			goto ret0;
		else {
			if (lm85_chk(method)
				&& chkReg_Probe(smb_slave,
					"Probing LM85/compatible chip:\n", chkReg, method)
						>= LM85_chkRegNum)
				goto ret1;
			else
				n = smb_slave + 2;
		}
	}

ret0:
	smb_slave = save;
	return 0;
ret1:
	kill_smb_slave(smb_slave);
	if (lm85chipid == ADM1024) {
		n = method->Read(ADM_24MODE);
		adm_24fan[0] = n & 0x01;	/* fan1 present if n & 0x01 = 0 */
		adm_24fan[1] = n & 0x02;	/* fan2 present if n & 0x02 = 0 */
	}
	if (lm85chipid < ADM1027)
		Vfac = 1. / (float) 0xFF;
	else
		Vfac = 1. / (float) 0x3FF;
	return lm85chipid;
}

static	int		lm85_chk(LM_METHODS *method)
{
	int vendor, verstep;

	vendor  = method->Read(LM85_VENDID);
	verstep = method->Read(LM85_VERSTEP);

	if (vendor == 0x01) {	/* National Semicon. */
		if ((verstep & 0xf0) == 0x60)
			lm85chipid = LM85;
		else
			goto ret0;
	} else if (vendor == 0x41) {	/* Analog Devices */
		if ((verstep & 0xF0) == 0x10)
			lm85chipid = ADM1024;
		else if ((verstep & 0xF0) == 0x20)
			lm85chipid = ADM1025;
		else if (verstep == 0x60)
			lm85chipid = ADM1027;
		else if (verstep == 0x62)
			lm85chipid = ADT7463;
		else
			goto ret0;
	} else if (vendor == 0x5C) {	/* Standard Microsystem Corp. */
		if ((verstep & 0xF0) == 0x60)
			lm85chipid = EMC6D10X;
		else
			goto ret0;
	} else
			goto ret0;
		
	strcpy(buff, lm85chip[lm85chipid]);
	return lm85chipid;
ret0:
	return 0;
}

/*
 *	\retval	0xFFFF	no sensor
 *	\retval	other	temperature
 *  no = 0,1,...
 */
static	float	lm85_temp( LM_METHODS *method, int no )
{
	int n, ne;
	float ext = 0.0, offset = 0.0;

	if (no < 0 || 2 < no)
		return 0xFFFF;

	if (lm85chipid == ADM1024 || lm85chipid == ADM1025) {
		if (no == 2)
			return 0xFFFF;
		else
			no++;
	} else if (lm85chipid >= ADM1027) {
		ne = method->Read(ADM_EXTRES2);	
		ext = 0.25 * ((ne >> ((no + 1) * 2)) & 0x03);
		n = method->Read(ADM_TEMPOFF(no));
		if (n > 0x80)
			n -= 0xFF;
		offset = (float) n;
	}
	n = method->Read(LM85_TEMP(no));
	if (n == 0x80)
		return 0xFFFF;
	else if (n > 0x80)
		return (float) (n - 0xFF) + ext - offset;
	else
		return (float) n + ext - offset;
}


/*
 *	\retval	0x0000FFFF	no sensor
 *  no = 0,1,2,...
 */

static float Vtab[5] = {
	2.5  * 4./3.,	/* 2.5V */
	2.25 * 4./3.,	/* Vcore (2.25) */
	3.3  * 4./3.,	/* 3.3V */
	5.0  * 4./3.,	/* 5.0V */
	12.0 * 4./3.	/* 12.0V */
};

static	float	lm85_volt(LM_METHODS *method, int no)
{
	int n, ne = 0;

	if (no < 0 || 4 < no)
		return 0xFFFF;

	if (lm85chipid >= ADM1027) {
		if (no == 4)
			ne = method->Read(ADM_EXTRES2) & 0x03;
		else
			ne = (method->Read(ADM_EXTRES1) >> (no * 2)) & 0x03;
	}
	n = method->Read(LM85_VOLT(no));
	if (lm85chipid >= ADM1027)
		n = (n << 2) | ne;

	return Vtab[no] * Vfac * (float) n;
}


/*
 *	\retval	0x0000FFFF	no sensor
 *  no = 0,1,...
 *
 *  Clock is 90kHz (90,000 x 60 = 5400000 counts/minute)
 */
static	int		lm85_fanrpm(LM_METHODS *method, int no)
{
	int lsb, msb, r, n;
	int p = 2;	/* default number of pulse per revolution */
	static int ppr[4] = {2,2,2,2}, div[2] = {1,1};

	if (no < 0 || 3 < no || lm85chipid == ADM1025)
		return 0xFFFF;

	if (lm85chipid == ADM1024) {
		if (no > 1 || adm_24fan[no])
			return 0xFFFF;
		n = method->Read(ADM_24FANDIV);
		div[0] = (n >> 4) & 0x03;
		div[1] =  n >> 6;
		r = method->Read(ADM_24FAN(no));
		if (r == 0)
			return 0xFFFF;
		else if (r == 0xFF) {
			/* change divisor for the sake of next call ! */
			if (div[no] < 3)
				++(div[no]);
			else
				div[no] = 0;
			r = (n & 0x0F) | (div[0] << 4) | (div[1] << 6);
			method->Write(ADM_24FANDIV, r);
			return 0xFFFF;
		} else
			return 1350000 / (r * (1 << div[no]));
	}

	if (lm85chipid >= ADM1027)
		p = ((method->Read(ADM_FANPPR) >> (no * 2)) & 0x03) + 1;
	ppr[no] = p;

	lsb = method->Read(LM85_FANLSB(no));
	msb = method->Read(LM85_FANMSB(no));
	if ((lsb == 0xFF && msb == 0xFF) || (lsb == 0 && msb == 0))
		return 0xFFFF;
	else
		return 5400000 / (p * ((msb << 8) | lsb));
}

#if 0

static int lm85_fan_ppr(LM_METHODS *method, int no)
{
	int p = 2;

	if (lm85chipid >= ADM1027)
		p = ((method->Read(ADM_FANPPR) >> (no * 2)) & 0x03) + 1;

	return p;
}

static void lm85_fan_ppr_set(LM_METHODS *method, int no, int p)
{
	int n;

	if (lm85chipid >= ADM1027) {
		n = method->Read(ADM_FANPPR);
		n &= ((p - 1) & 0x03) << (no * 2);
		method->Write(ADM_FANPPR, n);
	}
}

#endif
