Subject: kern/32463: patch to support h/w rnd# generator, smbus1.0 in AMD-8111 and support for ADT7463
To: None <kern-bug-people@netbsd.org, gnats-admin@netbsd.org,>
From: None <anil_public@yahoo.com>
List: netbsd-bugs
Date: 01/06/2006 03:40:00
>Number:         32463
>Category:       kern
>Synopsis:       patch to support h/w rnd# generator, smbus1.0 in AMD-8111 and support for ADT7463
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Fri Jan 06 03:40:00 +0000 2006
>Originator:     Anil Gopinath
>Release:        current
>Organization:
>Environment:
NetBSD r2t7 3.99.15 NetBSD 3.99.15 (GENERIC) #2: Thu Jan  5 19:00:33 PST 2006
>Description:

The following patch provides support for:

-  hardware random # generator found in AMD-8111
-  smbus1.0 host controller found in AMD-8111
-  ADT 7463 remote thermal controller and voltage monitor

This functionality has been tested on an TYAN S2881 board.

1. Modifications to existing files:

sys/dev/pci/amdpm.c
sys/dev/pci/amdpmreg.h
sys/dev/pci/files.pci
sys/dev/i2c/files.i2c
sys/dev/DEVNAMES

2. New files added:

sys/dev/pci/amdpm_smbus.c
sys/dev/pci/amdpm_smbusreg.h
sys/dev/i2c/adt7463.c
sys/dev/i2c/adt7463reg.h

3. How to use:

Add the following to your config file (eg. GENERIC)

# hardware random # generator found in AMD-8111
options  AMDPM_MAXCALLOUTS=1            # max callouts for rnd generator
                                                                  # set  AMDPM_MAXCALLOUTS=2  if
                                                                  # higher rate of random # generation
                                                                  # is required
amdpm*   at pci? dev? function?

# enable smbus 1.0 for AMD-8111
iic*    at amdpm?

# ADT7463 remote thermal controller and voltage monitor
adt7463c* at iic? addr 0x2D             # modify address to suit your board
                                                        # 0x2D used in TYAN S2281
                                                        # possible addr 0x2C 0x2D 0x2E



>How-To-Repeat:

How to test:

1. check boot messages (dmesg) to see if AMD-8111 hardware random # and ADT7463 were detected

iic0 at amdpm0: I2C bus
adt7463c0 at iic0 addr 0x2d

amdpm0 at pci0 dev 7 function 3
amdpm0: random number generator enabled (apprx. 57ms)

2. Run envstat from the command line. The new sensors should show up in the list.

>Fix:

Index: amdpm.c
===================================================================
RCS file: /cvsroot/src/sys/dev/pci/amdpm.c,v
retrieving revision 1.9
diff -r1.9 amdpm.c
38a39,44
> /* The following functionality was added by Anil Gopinath (anil_public@yahoo.com)
>  * - support for AMD-8111
>  * - support for multiple callbacks. This is useful if you have
>  * applications that read from /dev/random very often.
>  */
> 
49a56
> #include <sys/malloc.h>
54c61
< 
---
> #include <dev/i2c/i2cvar.h>
55a63
> #include <dev/pci/amdpm_smbusreg.h>
57,72c65,68
< struct amdpm_softc {
< 	struct device sc_dev;
< 
< 	pci_chipset_tag_t sc_pc;
< 	pcitag_t sc_tag;
< 
< 	bus_space_tag_t sc_iot;
< 	bus_space_handle_t sc_ioh;		/* PMxx space */
< 
< 	struct callout sc_rnd_ch;
< 	rndsource_element_t sc_rnd_source;
< #ifdef AMDPM_RND_COUNTERS
< 	struct evcnt sc_rnd_hits;
< 	struct evcnt sc_rnd_miss;
< 	struct evcnt sc_rnd_data[256];
< #endif
---
> struct amdpm_callout_handle {
>         int    id;                             
>         struct callout rnd_ch;  
>         struct amdpm_softc *sc;  
87,90c83,90
< 
< 	if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_AMD &&
< 	    PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_AMD_PBC768_PMC)
< 		return (1);
---
> 	
> 	/* test for both AMD_PBC768 and AMD-8111 */
> 	if ( (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_AMD) &&
> 	     ( ( (PCI_PRODUCT(pa->pa_id)  == PCI_PRODUCT_AMD_PBC8111_ACPI) &&
> 		 (pa->pa_function == AMDPM_8111_FUNCTION) ) ||
> 	       ( PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_AMD_PBC768_PMC) ) )
> 	  return (1);
> 	
114,115c114,123
< 
< 	reg = pci_conf_read(pa->pa_pc, pa->pa_tag, AMDPM_CONFREG);
---
> 	
> 	/* enable random # generation and pm i/o space for AMD-8111 */
> 	if (PCI_PRODUCT(pa->pa_id)  == PCI_PRODUCT_AMD_PBC8111_ACPI) {
> 	
> 	  reg = pci_conf_read(pa->pa_pc, pa->pa_tag, AMDPM_CONFREG);
> 	  pci_conf_write(pa->pa_pc, pa->pa_tag, AMDPM_CONFREG, reg|
> 			 AMDPM_RNGEN|AMDPM_PMIOEN);
> 	}	  
> 	
> 	reg = pci_conf_read(pa->pa_pc, pa->pa_tag, AMDPM_CONFREG);	
117,119c125,127
< 		aprint_error("%s: PMxx space isn't enabled\n",
< 		    sc->sc_dev.dv_xname);
< 		return;
---
> 	  aprint_error("%s: PMxx space isn't enabled\n",
> 		       sc->sc_dev.dv_xname);
> 	  return;
120a129,130
> 
> 	
130,131c140
< 	pci_conf_write(pa->pa_pc, pa->pa_tag, AMDPM_CONFREG, reg | AMDPM_RNGEN);
< 	reg = pci_conf_read(pa->pa_pc, pa->pa_tag, AMDPM_CONFREG);
---
> 
147c156
< 			callout_init(&sc->sc_rnd_ch);
---
> 
173c182,191
< 			amdpm_rnd_callout(sc);
---
> 
> 			/* initialize all the callouts */
> 			for(i = 0; i < AMDPM_MAXCALLOUTS; i++) {
> 			  struct amdpm_callout_handle* handle = (struct amdpm_callout_handle*)
> 			    malloc(sizeof(struct amdpm_callout_handle), M_DEVBUF, M_WAITOK);
> 			  handle->sc = sc;
> 			  handle->id = i + 1; /* start at 1 */
> 			  callout_init(&(handle->rnd_ch));
> 			  amdpm_rnd_callout((void*)handle);
> 			}
175a194,198
> 
> 	/* try to attach to devices on the smbus */
> 	if (PCI_PRODUCT(pa->pa_id)  == PCI_PRODUCT_AMD_PBC8111_ACPI) {
> 	  amdpm_smbus_attach(sc);
> 	}
184c207,208
< 	struct amdpm_softc *sc = v;
---
>         struct amdpm_callout_handle* handle = (struct amdpm_callout_handle*)v;  
>         struct amdpm_softc *sc = handle->sc;
185a210
>   
189,194c214,222
< 
< 	if ((bus_space_read_4(sc->sc_iot, sc->sc_ioh, AMDPM_RNGSTAT) &
< 	    AMDPM_RNGDONE) != 0) {
< 		reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh, AMDPM_RNGDATA);
< 		rnd_add_data(&sc->sc_rnd_source, &reg,
< 		    sizeof(reg), sizeof(reg) * NBBY);
---
> 	
> 	/* if random # collection is disabled, then just return. */
> 	if ( RND_ENABLED(&sc->sc_rnd_source) ) {
> 	  
> 	  if ((bus_space_read_4(sc->sc_iot, sc->sc_ioh, AMDPM_RNGSTAT) &
> 	       AMDPM_RNGDONE) != 0) {
> 	    reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh, AMDPM_RNGDATA);
> 	    rnd_add_data(&sc->sc_rnd_source, &reg,
> 			 sizeof(reg), sizeof(reg) * NBBY);
196,202c224,238
< 		AMDPM_RNDCNT_INCR(&sc->sc_rnd_hits);
< 		for (i = 0; i < sizeof(reg); i++, reg >>= NBBY)
< 			AMDPM_RNDCNT_INCR(&sc->sc_rnd_data[reg & 0xff]);
< #endif
< 	} else
< 		AMDPM_RNDCNT_INCR(&sc->sc_rnd_miss);
< 	callout_reset(&sc->sc_rnd_ch, 1, amdpm_rnd_callout, sc);
---
> 	    AMDPM_RNDCNT_INCR(&sc->sc_rnd_hits);
> 	    for (i = 0; i < sizeof(reg); i++, reg >>= NBBY)
> 	      AMDPM_RNDCNT_INCR(&sc->sc_rnd_data[reg & 0xff]);
> #endif
> 	    /* dont delay the last callout */
> 	    if ( (handle->id != AMDPM_MAXCALLOUTS) && (AMDPM_MAXCALLOUTS > 1) )
> 	      delay(AMDPM_8111_RND_GENTIME); 
> 	    
> 	  } else
> 	    AMDPM_RNDCNT_INCR(&sc->sc_rnd_miss);
> 	
> 	}
> 
> 	callout_reset(&(handle->rnd_ch), 1, amdpm_rnd_callout, v);
> 	
203a240
> 


Index: amdpmreg.h
===================================================================
RCS file: /cvsroot/src/sys/dev/pci/amdpmreg.h,v
retrieving revision 1.1
diff -r1.1 amdpmreg.h
38a39,41
> #ifndef _DEV_PCI_AMDPMREG_H_
> #define _DEV_PCI_AMDPMREG_H_
> 
58a62,90
> 
> #ifndef AMDPM_MAXCALLOUTS
> #define AMDPM_MAXCALLOUTS 1             /* set the default to 1 */
> #endif
> 
> #define AMDPM_8111_FUNCTION 3
> #define AMDPM_8111_RND_GENTIME 128      /* time(usec) taken to generate a random # */
> 
> struct amdpm_softc {
> 	struct device sc_dev;
> 
> 	pci_chipset_tag_t sc_pc;
> 	pcitag_t sc_tag;
> 
> 	bus_space_tag_t sc_iot;
> 	bus_space_handle_t sc_ioh;		/* PMxx space */
>   
> 	rndsource_element_t sc_rnd_source;
>         i2c_addr_t sc_smbus_slaveaddr;           /* address of slave thats connected to smbus */
>   
>         struct i2c_controller sc_i2c;	       /* i2c controller info */
> #ifdef AMDPM_RND_COUNTERS
> 	struct evcnt sc_rnd_hits;
> 	struct evcnt sc_rnd_miss;
> 	struct evcnt sc_rnd_data[256];
> #endif
> };
> 
> #endif  /* _DEV_PCI_AMDPMREG_H_ */

Index: files.pci
===================================================================
RCS file: /cvsroot/src/sys/dev/pci/files.pci,v
retrieving revision 1.241
diff -r1.241 files.pci
644,648c644,652
< # AMD 768MPX power management controller
< defflag	opt_amdpm.h			AMDPM_RND_COUNTERS
< device	amdpm {}
< attach	amdpm at pci
< file	dev/pci/amdpm.c			amdpm
---
> # AMD 768MPX power management controller / AMD 8111 HyperTransport I/O
> defflag     opt_amdpm.h                 AMDPM_RND_COUNTERS
> defparam    opt_amdpm.h                 AMDPM_MAXCALLOUTS
> define  amdpm {}
> device  amdpm : i2cbus, amdpm
> attach  amdpm at pci
> file    dev/pci/amdpm.c                 amdpm
> file    dev/pci/amdpm_smbus.c           amdpm
> 

Index: files.i2c
===================================================================
RCS file: /cvsroot/src/sys/dev/i2c/files.i2c,v
retrieving revision 1.6
diff -r1.6 files.i2c
60a61,66
> # Analog Devices ADT 7463 thermal monitor / fan controller
> define adt7463c {}
> device adt7463c: sysmon_envsys
> attach adt7463c at iic
> file dev/i2c/adt7463c.c                        adt7463c
> 

Index: DEVNAMES
===================================================================
RCS file: /cvsroot/src/sys/dev/DEVNAMES,v
retrieving revision 1.191
diff -r1.191 DEVNAMES
38a39
> adt7463c                MI

=========

New files:

1. sys/dev/pci/amdpm_smbus.c

/*
 * Copyright (c) 2005 Anil Gopinath (anil_public@yahoo.com)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* driver for SMBUS 1.0 host controller found in the
 * AMD-8111 HyperTransport I/O Hub
 */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/rnd.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcidevs.h>

#include <dev/i2c/i2cvar.h>
#include <dev/i2c/i2c_bitbang.h>
#include <dev/pci/amdpmreg.h>
#include <dev/pci/amdpm_smbusreg.h>

static int       amdpm_smbus_acquire_bus(void *cookie, int flags);
static void      amdpm_smbus_release_bus(void *cookie, int flags);
static int       amdpm_smbus_exec(void *cookie, i2c_op_t op, i2c_addr_t addr,
				  const void *cmd, size_t cmdlen, void *vbuf,
				  size_t buflen, int flags);
static int       amdpm_smbus_check_done(struct amdpm_softc *sc);
static void      amdpm_smbus_clear_gsr(struct amdpm_softc *sc);
static u_int16_t amdpm_smbus_get_gsr(struct amdpm_softc *sc);
static int       amdpm_smbus_send_1(struct amdpm_softc *sc, u_int8_t val);
static int       amdpm_smbus_write_1(struct amdpm_softc *sc, u_int8_t cmd, u_int8_t data);
static int       amdpm_smbus_receive_1(struct amdpm_softc *sc);
static int       amdpm_smbus_read_1(struct amdpm_softc *sc, u_int8_t cmd);


void
amdpm_smbus_attach(struct amdpm_softc *sc)
{
        struct i2cbus_attach_args iba;
	
	// register with iic
	sc->sc_i2c.ic_cookie = sc; 
	sc->sc_i2c.ic_acquire_bus = amdpm_smbus_acquire_bus; 
	sc->sc_i2c.ic_release_bus = amdpm_smbus_release_bus;
	sc->sc_i2c.ic_send_start = NULL;
	sc->sc_i2c.ic_send_stop = NULL;
	sc->sc_i2c.ic_initiate_xfer = NULL;
	sc->sc_i2c.ic_read_byte = NULL;
	sc->sc_i2c.ic_write_byte = NULL;
	sc->sc_i2c.ic_exec = amdpm_smbus_exec;
      
	iba.iba_name = "iic";
	iba.iba_tag = &sc->sc_i2c;
	(void) config_found(&sc->sc_dev, &iba, iicbus_print);      
}

static int
amdpm_smbus_acquire_bus(void *cookie, int flags)
{
	return (0);
}

static void
amdpm_smbus_release_bus(void *cookie, int flags)
{
}

static int
amdpm_smbus_exec(void *cookie, i2c_op_t op, i2c_addr_t addr, const void *cmd,
    size_t cmdlen, void *vbuf, size_t buflen, int flags)
{
        struct amdpm_softc *sc  = (struct amdpm_softc *) cookie;
	sc->sc_smbus_slaveaddr  = addr;
	
	if (I2C_OP_READ_P(op) && (cmdlen == 0) && (buflen == 1)) {
	  return (amdpm_smbus_receive_1(sc));    
	}
	
	if ( (I2C_OP_READ_P(op)) && (cmdlen == 1) && (buflen == 1)) {
	  return (amdpm_smbus_read_1(sc, *(const uint8_t*)cmd));
	}
	
	if ( (I2C_OP_WRITE_P(op)) && (cmdlen == 0) && (buflen == 1)) {
	  return (amdpm_smbus_send_1(sc, *(uint8_t*)vbuf));
	}
	
	if ( (I2C_OP_WRITE_P(op)) && (cmdlen == 1) && (buflen == 1)) {
	  return (amdpm_smbus_write_1(sc,  *(const uint8_t*)cmd, *(uint8_t*)vbuf));
	}
	
	return (-1);  
}

static int 
amdpm_smbus_check_done(struct amdpm_softc *sc)
{  
        int i = 0;    
	for (i = 0; i < 1000; i++) {
	  /* check gsr and wait till cycle is done */
	  u_int16_t data = amdpm_smbus_get_gsr(sc);
	  if (data & AMDPM_8111_GSR_CYCLE_DONE) {
	    return (0);	
	  }
	  delay(1);      
	}
	return (-1);    
}


static void
amdpm_smbus_clear_gsr(struct amdpm_softc *sc)
{
        /* clear register */
        u_int16_t data = 0xFFFF;     
	bus_space_write_2(sc->sc_iot, sc->sc_ioh, AMDPM_8111_SMBUS_STAT, data);
}

static u_int16_t
amdpm_smbus_get_gsr(struct amdpm_softc *sc)
{
        return (bus_space_read_2(sc->sc_iot, sc->sc_ioh, AMDPM_8111_SMBUS_STAT));  
}

static int
amdpm_smbus_send_1(struct amdpm_softc *sc,  u_int8_t val)
{
        /* first clear gsr */
        amdpm_smbus_clear_gsr(sc);

	/* write smbus slave address to register */
	u_int16_t data = 0;
	data = sc->sc_smbus_slaveaddr;
	data <<= 1;
	data |= AMDPM_8111_SMBUS_SEND;    
	bus_space_write_1(sc->sc_iot, sc->sc_ioh, AMDPM_8111_SMBUS_HOSTADDR, data);
	
	data = val;    
	/* store data */
	bus_space_write_2(sc->sc_iot, sc->sc_ioh, AMDPM_8111_SMBUS_HOSTDATA, data);
	/* host start */
	bus_space_write_2(sc->sc_iot, sc->sc_ioh, AMDPM_8111_SMBUS_CTRL,
			  AMDPM_8111_SMBUS_GSR_SB);	
	return(amdpm_smbus_check_done(sc));    
}

  
static int
amdpm_smbus_write_1(struct amdpm_softc *sc, u_int8_t cmd, u_int8_t val)
{
        /* first clear gsr */
        amdpm_smbus_clear_gsr(sc);  
  
	u_int16_t data = 0;
	data = sc->sc_smbus_slaveaddr;
	data <<= 1;
	data |= AMDPM_8111_SMBUS_WRITE;    
	bus_space_write_1(sc->sc_iot, sc->sc_ioh, AMDPM_8111_SMBUS_HOSTADDR, data);
	
	data = val;    
	/* store cmd */
	bus_space_write_1(sc->sc_iot, sc->sc_ioh, AMDPM_8111_SMBUS_HOSTCMD, cmd);
	/* store data */    
	bus_space_write_2(sc->sc_iot, sc->sc_ioh, AMDPM_8111_SMBUS_HOSTDATA, data);    
	/* host start */
	bus_space_write_2(sc->sc_iot, sc->sc_ioh, AMDPM_8111_SMBUS_CTRL, AMDPM_8111_SMBUS_GSR_WB);
	
	return (amdpm_smbus_check_done(sc));    
}

static int
amdpm_smbus_receive_1(struct amdpm_softc *sc)
{
        /* first clear gsr */
        amdpm_smbus_clear_gsr(sc);  

	/* write smbus slave address to register */
	u_int16_t data = 0;
	data = sc->sc_smbus_slaveaddr;
	data <<= 1;
	data |= AMDPM_8111_SMBUS_RX;    
	bus_space_write_1(sc->sc_iot, sc->sc_ioh, AMDPM_8111_SMBUS_HOSTADDR, data);
	
	/* start smbus cycle */
	bus_space_write_2(sc->sc_iot, sc->sc_ioh, AMDPM_8111_SMBUS_CTRL, AMDPM_8111_SMBUS_GSR_RXB);
	
	/* check for errors */
	if (amdpm_smbus_check_done(sc) < 0)
	  return (-1);
	
	/* read data */
	data = bus_space_read_2(sc->sc_iot, sc->sc_ioh, AMDPM_8111_SMBUS_HOSTDATA);    
	u_int8_t ret = (u_int8_t)(data & 0x00FF);
	return (ret);
}

static int
amdpm_smbus_read_1(struct amdpm_softc *sc, u_int8_t cmd)
{  
        /* first clear gsr */
        amdpm_smbus_clear_gsr(sc);  

	/* write smbus slave address to register */
	u_int16_t data = 0;
	data = sc->sc_smbus_slaveaddr;
	data <<= 1;
	data |= AMDPM_8111_SMBUS_READ;    
	bus_space_write_1(sc->sc_iot, sc->sc_ioh, AMDPM_8111_SMBUS_HOSTADDR, data);
	
	/* store cmd */
	bus_space_write_1(sc->sc_iot, sc->sc_ioh, AMDPM_8111_SMBUS_HOSTCMD, cmd);
	/* host start */
	bus_space_write_2(sc->sc_iot, sc->sc_ioh, AMDPM_8111_SMBUS_CTRL, AMDPM_8111_SMBUS_GSR_RB);
	
	/* check for errors */
	if (amdpm_smbus_check_done(sc) < 0)
	  return (-1);
	
	/* store data */    
	data = bus_space_read_2(sc->sc_iot, sc->sc_ioh, AMDPM_8111_SMBUS_HOSTDATA);
	u_int8_t ret = (u_int8_t)(data & 0x00FF);
	return (ret);
}

================

2.  sys/dev/pci/amdpm_smbusreg.h

/*
 * Copyright (c) 2005 Anil Gopinath (anil_public@yahoo.com)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* driver for SMBUS 1.0 host controller found in the
 * AMD-8111 HyperTransport I/O Hub
 */

#ifndef _DEV_PCI_AMDPMSMBUSREG_H_
#define _DEV_PCI_AMDPMSMBUSREG_H_

#define AMDPM_8111_SMBUS_STAT        0xE0  /* SMBus 1.x global status register */
#define AMDPM_8111_SMBUS_CTRL        0xE2  /* SMBus 1.x global control register */
#define AMDPM_8111_SMBUS_HOSTADDR    0xE4  /* SMBus 1.x Host address register */
#define AMDPM_8111_SMBUS_HOSTDATA    0xE6  /* SMBus 1.x Host data register */
#define AMDPM_8111_SMBUS_HOSTCMD     0xE8  /* SMBus 1.x Host command field register */

#define AMDPM_8111_SMBUS_GSR_SB      0x0009 /* GSR contents to send a byte */
#define AMDPM_8111_SMBUS_GSR_RXB     0x0009 /* GSR contents to receive a byte */
#define AMDPM_8111_SMBUS_GSR_RB      0x000A /* GSR contents to read a byte */
#define AMDPM_8111_SMBUS_GSR_WB      0x000A /* GSR contents to write a byte */

#define AMDPM_8111_GSR_CYCLE_DONE    0x0010 /* indicates cycle done successfuly */

#define AMDPM_8111_SMBUS_READ        0x0001 /* smbus read cycle indicator */
#define AMDPM_8111_SMBUS_RX          0x0001 /* smbus receive cycle indicator */
#define AMDPM_8111_SMBUS_WRITE       0x0000 /* smbus write cycle indicator */
#define AMDPM_8111_SMBUS_SEND        0x0000 /* smbus send cycle indicator */

void      amdpm_smbus_attach(struct amdpm_softc *sc);

#endif

========

3. sys/dev/i2c/adt7463.c

/*
 * Copyright (c) 2005 Anil Gopinath (anil_public@yahoo.com)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Analog devices AD7463 remote thermal controller and voltage monitor
 * Data sheet at:
 * http://www.analog.com/UploadedFiles/Data_Sheets/272624927ADT7463_c.pdf
 */

/* Fan speed control added by Hanns Hartman */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <dev/sysmon/sysmonvar.h>
#include <dev/i2c/i2cvar.h>

#include <dev/i2c/adt7463creg.h>


int adt7463c_gtredata __P((struct sysmon_envsys *, struct envsys_tre_data *));

static int adt7463c_send_1(struct adt7463c_softc *sc, u_int8_t val);
static int adt7463c_receive_1(struct adt7463c_softc *sc);
static int adt7463c_write_1(struct adt7463c_softc *sc,  u_int8_t cmd, u_int8_t val);

static void adt7463c_setup_volt(struct adt7463c_softc *sc, int start, int tot);
static void adt7463c_setup_temp(struct adt7463c_softc *sc,  int start, int tot);
static void adt7463c_setup_fan(struct adt7463c_softc *sc,  int start, int tot);
static void adt7463c_refresh_volt(struct adt7463c_softc *sc);
static void adt7463c_refresh_temp(struct adt7463c_softc *sc);
static void adt7463c_refresh_fan(struct adt7463c_softc *sc);
static int  adt7463c_verify(struct adt7463c_softc *sc);

static int  adt7463c_match(struct device *, struct cfdata *, void *);
static void adt7463c_attach(struct device *, struct device *, void *);


CFATTACH_DECL(adt7463c, sizeof(struct adt7463c_softc),
    adt7463c_match, adt7463c_attach, NULL, NULL);

static int
adt7463c_match(struct device *parent, struct cfdata *cf, void *aux)
{
        struct i2c_attach_args *ia = aux;
	struct adt7463c_softc sc;
	sc.sc_tag = ia->ia_tag;
        sc.sc_address = ia->ia_addr;

	if(adt7463c_verify(&sc))
	  return (1);
	
        return (0); 
}

static void
adt7463c_attach(struct device *parent, struct device *self, void *aux)
{
        struct adt7463c_softc *sc = (struct adt7463c_softc *)self;
	struct i2c_attach_args *ia = aux;
	int i = 0;
	
	sc->sc_tag = ia->ia_tag;
        sc->sc_address = ia->ia_addr;

	/* start ADT7463 */
	adt7463c_write_1(sc,  ADT7463_CONFIG_REG1, ADT7463_START);
	/* set config reg3 to enable fast TACH measurements */
	adt7463c_write_1(sc,  ADT7463_CONFIG_REG3, ADT7463_CONFIG_REG3_FAST);
	
	/* begin fan speed control addition */
	/* associate each fan with Temp zone 2 */
	adt7463c_write_1(sc,  FANZONEREG1, TEMPCHANNEL);
	adt7463c_write_1(sc,  FANZONEREG2, TEMPCHANNEL);
	adt7463c_write_1(sc,  FANZONEREG3, TEMPCHANNEL);
	
	/* set Tmin */
	adt7463c_write_1(sc,  TMINREG,     TMINTEMP);
	/* set fans to always on when below Tmin */
	adt7463c_write_1(sc,  FANONREG,    ALWAYSON);
	
	/* set min fan speed */
	adt7463c_write_1(sc,  FANMINREG1,  FANMINSPEED);
	adt7463c_write_1(sc,  FANMINREG2,  FANMINSPEED);
	adt7463c_write_1(sc,  FANMINREG3,  FANMINSPEED);
	
	/* set Trange */
	adt7463c_write_1(sc,  TRANGEREG,   TRANGEVAL);
	/* set Tterm */
	adt7463c_write_1(sc,  TTERMREG,    TTERMVAL);
	/* set operating point */
	adt7463c_write_1(sc,  OPPTREG,     OPPTTEMP);
	/* set Tlow */
	adt7463c_write_1(sc,  TLOWREG,     TLOW);
	/* set Thigh */
	adt7463c_write_1(sc,  THIGHREG,    THIGH);
	
	/* turn on dynamic control */
	adt7463c_write_1(sc,  ENABLEDYNAMICREG, REMOTE2);
	/* set a hyst value */
	adt7463c_write_1(sc,THYSTREG,      THYST);
	/* done with fan speed control additions */
	
	/* Initialize sensors */
	adt7463c_setup_volt(sc, 0, ADT7463_VOLT_SENSORS_COUNT);
	adt7463c_setup_temp(sc, ADT7463_VOLT_SENSORS_COUNT,
			    ADT7463_TEMP_SENSORS_COUNT);
	adt7463c_setup_fan(sc, ADT7463_VOLT_SENSORS_COUNT+ADT7463_TEMP_SENSORS_COUNT,
			   ADT7463_FAN_SENSORS_COUNT);
	
	for (i = 0; i < ADT7463_MAX_ENVSYS_RANGE; ++i) {
	  sc->sc_sensor[i].sensor = sc->sc_info[i].sensor = i;
	  sc->sc_sensor[i].validflags = (ENVSYS_FVALID|ENVSYS_FCURVALID);
	  sc->sc_info[i].validflags = ENVSYS_FVALID;
	  sc->sc_sensor[i].warnflags = ENVSYS_WARN_OK;
	}      
	
	/* Hook into the System Monitor. */
	sc->sc_sysmon.sme_ranges = adt7463c_ranges;
	sc->sc_sysmon.sme_sensor_info = sc->sc_info;
	sc->sc_sysmon.sme_sensor_data = sc->sc_sensor;
	sc->sc_sysmon.sme_cookie = sc;
	
	/* callback for envsys get data */
	sc->sc_sysmon.sme_gtredata = adt7463c_gtredata;
	
	sc->sc_sysmon.sme_nsensors = ADT7463_MAX_ENVSYS_RANGE;
	sc->sc_sysmon.sme_envsys_version = 1000;
	
	sc->sc_sysmon.sme_flags = 0;	
	
	if (sysmon_envsys_register(&sc->sc_sysmon))
	printf("adt7463: unable to register with sysmon\n");
}


static int
adt7463c_verify(struct adt7463c_softc *sc)
{
      /* verify this is an adt7463 */
      int c_id, d_id;
      adt7463c_send_1(sc,  ADT7463_COMPANYID_REG);
      c_id = adt7463c_receive_1(sc);
      adt7463c_send_1(sc,  ADT7463_DEVICEID_REG);
      d_id = adt7463c_receive_1(sc);
      
      if ( (c_id == ADT7463_COMPANYID) &&
	   (d_id == ADT7463_DEVICEID) ) {
	return (1);
      }
      return (0);      
}

static void
adt7463c_setup_volt(struct adt7463c_softc *sc, int start, int tot)
{  
        sc->sc_sensor[start+0].units = sc->sc_info[start+0].units = ENVSYS_SVOLTS_DC;
	snprintf(sc->sc_info[start+0].desc, sizeof(sc->sc_info[start+0].desc), "2.5V");
	sc->sc_info[start+0].rfact = 10000;
	sc->sc_sensor[start+1].units = sc->sc_info[start+1].units = ENVSYS_SVOLTS_DC;	
	snprintf(sc->sc_info[start+1].desc, sizeof(sc->sc_info[start+1].desc), "VCCP");
	sc->sc_info[start+1].rfact = 10000;
	sc->sc_sensor[start+2].units = sc->sc_info[start+2].units = ENVSYS_SVOLTS_DC;
	snprintf(sc->sc_info[start+2].desc, sizeof(sc->sc_info[start+2].desc), "VCC");
	sc->sc_info[start+2].rfact = 10000;
	sc->sc_sensor[start+3].units = sc->sc_info[start+3].units = ENVSYS_SVOLTS_DC;
	snprintf(sc->sc_info[start+3].desc, sizeof(sc->sc_info[start+3].desc), "5V");
	sc->sc_info[start+3].rfact = 10000;
	sc->sc_sensor[start+4].units = sc->sc_info[start+4].units = ENVSYS_SVOLTS_DC;
	snprintf(sc->sc_info[start+4].desc, sizeof(sc->sc_info[start+4].desc), "12V");
	sc->sc_info[start+4].rfact = 10000;
}


static void
adt7463c_setup_temp(struct adt7463c_softc *sc,  int start, int tot)
{
       sc->sc_sensor[start+0].units = sc->sc_info[start+0].units = ENVSYS_STEMP;
       snprintf(sc->sc_info[start + 0].desc,
		sizeof(sc->sc_info[start + 0].desc), "Temp-1");

       sc->sc_sensor[start+1].units = sc->sc_info[start+1].units = ENVSYS_STEMP;
       snprintf(sc->sc_info[start + 1].desc,
		sizeof(sc->sc_info[start + 1].desc), "Temp-2");       

       sc->sc_sensor[start+2].units = sc->sc_info[start+2].units = ENVSYS_STEMP;
       snprintf(sc->sc_info[start + 2].desc,
		sizeof(sc->sc_info[start + 2].desc), "Temp-3");

}

static void
adt7463c_setup_fan(struct adt7463c_softc *sc,  int start, int tot)
{
        sc->sc_sensor[start + 0].units = ENVSYS_SFANRPM;
	sc->sc_info[start + 0].units = ENVSYS_SFANRPM;
	snprintf(sc->sc_info[start + 0].desc,
		 sizeof(sc->sc_info[start + 0].desc), "Fan-1");

	sc->sc_sensor[start + 1].units = ENVSYS_SFANRPM;
	sc->sc_info[start + 1].units = ENVSYS_SFANRPM;
	snprintf(sc->sc_info[start + 1].desc,
		 sizeof(sc->sc_info[start + 1].desc), "Fan-2");
	
	sc->sc_sensor[start + 2].units = ENVSYS_SFANRPM;
	sc->sc_info[start + 2].units = ENVSYS_SFANRPM;
	snprintf(sc->sc_info[start + 2].desc,
		 sizeof(sc->sc_info[start + 2].desc), "Fan-3");

	sc->sc_sensor[start + 3].units = ENVSYS_SFANRPM;
	sc->sc_info[start + 3].units = ENVSYS_SFANRPM;
	snprintf(sc->sc_info[start + 3].desc,
		 sizeof(sc->sc_info[start + 3].desc), "Fan-4");	
	
}

int
adt7463c_gtredata(sme, tred)
	 struct sysmon_envsys *sme;
	 struct envsys_tre_data *tred;
{  
        struct adt7463c_softc *sc = sme->sme_cookie;

	adt7463c_refresh_volt(sc);
	adt7463c_refresh_temp(sc);
	adt7463c_refresh_fan(sc);
	
	*tred = sc->sc_sensor[tred->sensor];
	return (0);
  
}

void
adt7463c_refresh_volt(struct adt7463c_softc *sc)
{
        int i;
	u_int8_t reg;
	int data;
	float mult[] = {ADT7463_2_5V_CONST,
			ADT7463_VCC_CONST,
			ADT7463_3_3V_CONST,
			ADT7463_5V_CONST,
			ADT7463_12V_CONST};
	
	reg = ADT7463_VOLT_REG_START;    
	for (i = 0; i < ADT7463_VOLT_SENSORS_COUNT; i++) {      
	  adt7463c_send_1(sc,  reg++);
	  data = adt7463c_receive_1(sc);
	  
	  /* envstat assumes that voltage is in uVDC */
	  double val = (data * 1000000.0  * mult[i]);      
	  if (data > 0) 
	    sc->sc_sensor[i].cur.data_us = (u_int32_t)val;
	  else      
	    sc->sc_sensor[i].cur.data_us = 0;
	}    
}


void
adt7463c_refresh_temp(struct adt7463c_softc *sc)
{
        int i = 0;
	u_int8_t reg;
	int data;    
	
	reg = ADT7463_TEMP_REG_START;
	for (i = 0; i < ADT7463_TEMP_SENSORS_COUNT; i++) {      
	  adt7463c_send_1(sc,  reg++);
	  data = adt7463c_receive_1(sc);
	  
	  /* envstat assumes temperature is in micro kelvin */
	  if (data > 0)
	    sc->sc_sensor[i + ADT7463_VOLT_SENSORS_COUNT].cur.data_us
	      = (data + ADT7463_CEL_TO_KELVIN)* 1000000;
	  else
	    sc->sc_sensor[i + ADT7463_VOLT_SENSORS_COUNT].cur.data_us
	      = 0;      
	}    
}


void
adt7463c_refresh_fan(struct adt7463c_softc *sc)
{
        int i, j;
	u_int8_t reg;
	int data = 0;
	u_int16_t val = 0;    
	u_char buf[2];    
	
	reg = ADT7463_FAN_REG_START;
	for (i = 0; i < ADT7463_FAN_SENSORS_COUNT; i++) {
	  
	  /* read LSB and then MSB */
	  for (j = 0; j < 2; j++) {	
	    adt7463c_send_1(sc,  reg++);
	    data = adt7463c_receive_1(sc);
	    if (data > 0)
	      buf[j] = data;
	    else
	      buf[j] = 0;	
	  }
	  
	  val = le16dec(buf);
	  
#if _BYTE_ORDER == _BIG_ENDIAN
	  val = LE16TOH(val);    
#endif      
	  
	  /* calculate RPM */
	  if (val > 0)
	    sc->sc_sensor[i + ADT7463_VOLT_SENSORS_COUNT +
			  ADT7463_TEMP_SENSORS_COUNT].cur.data_us
	      = (ADT7463_RPM_CONST)/val;
	  else
	    sc->sc_sensor[i + ADT7463_VOLT_SENSORS_COUNT +
			  ADT7463_TEMP_SENSORS_COUNT].cur.data_us = 0;
	}
}

int
adt7463c_receive_1(struct adt7463c_softc *sc)
{
        u_int8_t val = 0;
	int error = 0;
	if ((error = iic_acquire_bus(sc->sc_tag, 0)) != 0)
	  return (error);

	if ((error = iic_exec(sc->sc_tag, I2C_OP_READ,
			      sc->sc_address, NULL, 0, &val, 1, 0)) != 0)
	  return (error);
	
	iic_release_bus(sc->sc_tag, 0);
	return (val);
}

int
adt7463c_send_1(struct adt7463c_softc *sc, u_int8_t val)
{
        int error = 0;
	if ((error = iic_acquire_bus(sc->sc_tag, 0)) != 0)
	  return (error);

	if ((error = iic_exec(sc->sc_tag, I2C_OP_WRITE,
			      sc->sc_address, NULL, 0, &val, 1, 0)) != 0)
	  return (error);
	
	iic_release_bus(sc->sc_tag, 0);
	return (0);
}

int
adt7463c_write_1(struct adt7463c_softc *sc,  u_int8_t cmd, u_int8_t val)
{
        int error = 0;
	if ((error = iic_acquire_bus(sc->sc_tag, 0)) != 0)
	  return (error);

	if ((error = iic_exec(sc->sc_tag, I2C_OP_WRITE,
			      sc->sc_address, &cmd, 1, &val, 1, 0)) != 0)
	  return (error);
	
	iic_release_bus(sc->sc_tag, 0);
	return (0);
}

=================

4. sys/dev/i2c/adt7463reg.h

/*
 * Copyright (c) 2005 Anil Gopinath (anil_public@yahoo.com)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Analog devices AD7463 remote thermal controller and voltage monitor
 * Data sheet at:
 * http://www.analog.com/UploadedFiles/Data_Sheets/272624927ADT7463_c.pdf
 */

/* Fan speed control added by Hanns Hartman */

#ifndef INCLUDE_ADT7463REG_H
#define INCLUDE_ADT7463REG_H

#define ADT7463_VOLT_SENSORS_COUNT 5
#define ADT7463_TEMP_SENSORS_COUNT 3
#define ADT7463_FAN_SENSORS_COUNT 4
#define ADT7463_MAX_ENVSYS_RANGE 12 /* sum of the above */

/* I2C/SMBUS address */
#define ADT7463_ADDR1                0x2C
#define ADT7463_ADDR2                0x2D
#define ADT7463_ADDR3                0x2E

#define ADT7463_CONFIG_REG1         0x40
#define ADT7463_CONFIG_REG3         0x78
#define ADT7463_START               0x01
#define ADT7463_COMPANYID_REG       0x3E
#define ADT7463_COMPANYID           0x41
#define ADT7463_DEVICEID_REG        0x3D
#define ADT7463_DEVICEID            0x27
  
#define ADT7463_VOLT_REG_START      0x20
#define ADT7463_TEMP_REG_START      0x25
#define ADT7463_FAN_REG_START       0x28

#define ADT7463_CONFIG_REG3_FAST    0x08

/* currently we use only 8 bits and hence the multiplier */
#define ADT7463_12V_CONST           (0.0625)
#define ADT7463_5V_CONST            (0.0260)
#define ADT7463_3_3V_CONST          (0.0171)
#define ADT7463_2_5V_CONST          (0.0130)
#define ADT7463_VCC_CONST           (0.0117)

#define ADT7463_CEL_TO_KELVIN       273.15
#define ADT7463_RPM_CONST           (90000 * 60)

const struct envsys_range adt7463c_ranges[] = {
  
	{ 0, 0xFF,    ENVSYS_STEMP   },
	{ 0, 0xFF,    ENVSYS_SFANRPM },
	{ 1, 0,       ENVSYS_SVOLTS_AC },	/* None */
	{ 0, 0xFF,    ENVSYS_SVOLTS_DC },
	{ 1, 0,       ENVSYS_SOHMS },	/* None */
	{ 1, 0,       ENVSYS_SWATTS },	/* None */
	{ 1, 0,       ENVSYS_SAMPS }	/* None */
};  

struct adt7463c_softc {
	struct device sc_dev;		/* generic device structures */
	i2c_tag_t sc_tag;
	i2c_addr_t sc_address;

	struct envsys_tre_data sc_sensor[ADT7463_MAX_ENVSYS_RANGE];
	struct envsys_basic_info sc_info[ADT7463_MAX_ENVSYS_RANGE];

	struct sysmon_envsys sc_sysmon;  
  
};

/* Fan speed control define(s) 
 * All below references to page numbers refer to the Automatic Fan
 * Speed Control App Note
 */
 
/* step two setting temperature zone 2
 * page 5 gives specific information about how to program the temperature 
 * channel.  Also note that the low order byte of 2 should not be changed.
 */
#define FANZONEREG1 0x5C
#define FANZONEREG2 0x5D
#define FANZONEREG3 0x5E
#define TEMPCHANNEL 0x42

/* Minimum temperature remote zone 2 (page 7) */   
#define TMINREG 0x69
#define TMINTEMP 0x2C

/* keep the fans always on
 * please see page 7 for which  bit to set to enable a 
 * pwm to be left always on.
 */
#define FANONREG 0x62
#define ALWAYSON 0xE0

/* minimum fan speed
 * computing the number for FANMINSPEED is done by converting
 * percent fan speed to a pwm number using the equation on page 8
 */
#define FANMINREG1 0x64
#define FANMINREG2 0x65
#define FANMINREG3 0x66
#define FANMINSPEED 0x45

/* give a Trange this is the slope at which the fan speed will 
 * increase based on temperature
 * please make sure not to change the low order byte of 4 if adjusting
 * this value.  In order to calculate Trange use the equation on page 9
 * note that this is the best value given the current bios situation
 */
#define TRANGEREG 0x61
#define TRANGEVAL 0x94

/* This is the hyst value.  once the operating temperature-hyst 
 * is broken the fan speed will start to increase
 * consult page 12 for what to put in what register
 */
#define THYSTREG 0x6E
#define THYST 0x20

/* this is the value when reach will cause the fans to drive at full speed
 * see page 12
 */
#define TTERMREG 0x6C
#define TTERMVAL 0x39

/* This is the desired operating temperature for the cpu
 * see page 15-16 for more detail
 */
#define OPPTREG 0x35
#define OPPTTEMP 0x34

/* Once the temperature falls below this point the 
 * fans speed will start to decrease
 * see page 17 for more detail
 */
#define TLOWREG 0x52
#define TLOW 0x2D

/* Once the temperature rises above this point the fan speed 
 * will be increased at a more rapid rate
 * see page 17 for more detail
 */
#define THIGHREG 0x53
#define THIGH 0x36

/* Enable dynamic control on remote2 given a polling interval
 * please see page 18-22 in setting values for register 0x36
 */
#define ENABLEDYNAMICREG 0x36 
#define REMOTE2 0x80

/* done with fan speed control additions */

#endif