Thu Oct 2 00:47:51 2008 UTC ()
Add new driver for dbCool(tm) family of Thermal Monitor and Fan Controller.
Supported chips: ADM1027, ADM1030, ADT7463, ADT7466, ADT7467, ADT7468,
ADT7473, ADT7475, and ADT7476.  Notably missing is the ADT7490, and fan
controller support on the ADT7466 is still on the to-do list.

Tested by myself and njoly@
Reviewed by garbled@
Commit approved by christos@, bouyer@, cube@, and matt@


(pgoyette)
diff -r1.475 -r1.476 src/share/man/man4/Makefile
diff -r0 -r1.1 src/share/man/man4/dbcool.4
diff -r0 -r1.1 src/sys/dev/i2c/dbcool.c
diff -r0 -r1.1 src/sys/dev/i2c/dbcool_reg.h
diff -r0 -r1.1 src/sys/dev/i2c/dbcool_var.h
diff -r1.18 -r1.19 src/sys/dev/i2c/files.i2c

cvs diff -r1.475 -r1.476 src/share/man/man4/Makefile (expand / switch to unified diff)

--- src/share/man/man4/Makefile 2008/09/30 16:48:39 1.475
+++ src/share/man/man4/Makefile 2008/10/02 00:47:51 1.476
@@ -1,14 +1,14 @@ @@ -1,14 +1,14 @@
1# $NetBSD: Makefile,v 1.475 2008/09/30 16:48:39 jmcneill Exp $ 1# $NetBSD: Makefile,v 1.476 2008/10/02 00:47:51 pgoyette Exp $
2# @(#)Makefile 8.1 (Berkeley) 6/18/93 2# @(#)Makefile 8.1 (Berkeley) 6/18/93
3 3
4MAN= aac.4 ac97.4 acardide.4 aceride.4 acphy.4 acpidalb.4 \ 4MAN= aac.4 ac97.4 acardide.4 aceride.4 acphy.4 acpidalb.4 \
5 adbbt.4 adbkbd.4 adbms.4 \ 5 adbbt.4 adbkbd.4 adbms.4 \
6 adc.4 adt7467c.4 adv.4 adw.4 agp.4 agr.4 ahb.4 ahc.4 ahcisata.4 ahd.4 \ 6 adc.4 adt7467c.4 adv.4 adw.4 agp.4 agr.4 ahb.4 ahc.4 ahcisata.4 ahd.4 \
7 aiboost.4 amdpm.4 amdtemp.4 amhphy.4 amr.4 aps.4 \ 7 aiboost.4 amdpm.4 amdtemp.4 amhphy.4 amr.4 aps.4 \
8 an.4 arcmsr.4 aria.4 artsata.4 ata.4 atalk.4 ataraid.4 \ 8 an.4 arcmsr.4 aria.4 artsata.4 ata.4 atalk.4 ataraid.4 \
9 ath.4 atppc.4 attimer.4 atw.4 \ 9 ath.4 atppc.4 attimer.4 atw.4 \
10 auacer.4 audio.4 audiocs.4 auich.4 \ 10 auacer.4 audio.4 audiocs.4 auich.4 \
11 auixp.4 autri.4 auvia.4 awi.4 azalia.4 \ 11 auixp.4 autri.4 auvia.4 awi.4 azalia.4 \
12 battery_pmu.4 bba.4 bce.4 bcsp.4 be.4 bge.4 bnx.4 bha.4 \ 12 battery_pmu.4 bba.4 bce.4 bcsp.4 be.4 bge.4 bnx.4 bha.4 \
13 bio.4 bktr.4 bluetooth.4 bmtphy.4 bpf.4 \ 13 bio.4 bktr.4 bluetooth.4 bmtphy.4 bpf.4 \
14 brgphy.4 bridge.4 bthidev.4 bthub.4 btkbd.4 btms.4 btsco.4 btuart.4 \ 14 brgphy.4 bridge.4 bthidev.4 bthub.4 btkbd.4 btms.4 btsco.4 btuart.4 \
@@ -100,27 +100,27 @@ MAN+= bt3c.4 btbc.4 pcic.4 tcic.4 pcmcom @@ -100,27 +100,27 @@ MAN+= bt3c.4 btbc.4 pcic.4 tcic.4 pcmcom
100# machine-independent obio (mac68k and macppc) devices 100# machine-independent obio (mac68k and macppc) devices
101MAN+= adb.4 akbd.4 ams.4 mc.4 101MAN+= adb.4 akbd.4 ams.4 mc.4
102 102
103# machine-independent podulebus devices 103# machine-independent podulebus devices
104MAN+= dtide.4 ea.4 eb.4 ei.4 hcide.4 sec.4 104MAN+= dtide.4 ea.4 eb.4 ei.4 hcide.4 sec.4
105 105
106# machine-independent VME devices 106# machine-independent VME devices
107MAN+= sc.4 si.4 107MAN+= sc.4 si.4
108 108
109# machine-independent IEEE1394 devices 109# machine-independent IEEE1394 devices
110MAN+= fwohci.4 fwip.4 sbp.4 110MAN+= fwohci.4 fwip.4 sbp.4
111 111
112# machine-independent I2C devices 112# machine-independent I2C devices
113MAN+= lmtemp.4 spdmem.4 113MAN+= dbcool.4 lmtemp.4 sdtemp.4 spdmem.4
114 114
115# machine-independent SPI devices 115# machine-independent SPI devices
116MAN += m25p.4 tm121temp.4 116MAN += m25p.4 tm121temp.4
117 117
118# IPv6/IPsec 118# IPv6/IPsec
119MAN+= faith.4 gif.4 inet6.4 icmp6.4 ip6.4 ipsec.4 stf.4 119MAN+= faith.4 gif.4 inet6.4 icmp6.4 ip6.4 ipsec.4 stf.4
120 120
121# ISDN devices 121# ISDN devices
122MAN+= daic.4 isdntrc.4 isdntel.4 isdnbchan.4 ippp.4 irip.4 isdnctl.4 isdn.4 \ 122MAN+= daic.4 isdntrc.4 isdntel.4 isdnbchan.4 ippp.4 irip.4 isdnctl.4 isdn.4 \
123 ifpci.4 isic.4 iwic.4 isdncapi.4 iavc.4 123 ifpci.4 isic.4 iwic.4 isdncapi.4 iavc.4
124 124
125# onewire bus and devices 125# onewire bus and devices
126MAN+= gpioow.4 onewire.4 owtemp.4 126MAN+= gpioow.4 onewire.4 owtemp.4
@@ -136,26 +136,36 @@ MLINKS+=drm.4 sisdrm.4 @@ -136,26 +136,36 @@ MLINKS+=drm.4 sisdrm.4
136MLINKS+=drm.4 tdfxdrm.4 136MLINKS+=drm.4 tdfxdrm.4
137MLINKS+=drm.4 viadrm.4 137MLINKS+=drm.4 viadrm.4
138 138
139MLINKS+=ata.4 atabus.4 139MLINKS+=ata.4 atabus.4
140MLINKS+=audio.4 audioctl.4 140MLINKS+=audio.4 audioctl.4
141MLINKS+=audio.4 mixer.4 141MLINKS+=audio.4 mixer.4
142MLINKS+=audio.4 sound.4 142MLINKS+=audio.4 sound.4
143MLINKS+=bha.4 bt.4 143MLINKS+=bha.4 bt.4
144MLINKS+=bktr.4 tuner.4 144MLINKS+=bktr.4 tuner.4
145MLINKS+=bktr.4 vbi.4 145MLINKS+=bktr.4 vbi.4
146MLINKS+=cardbus.4 cardslot.4 146MLINKS+=cardbus.4 cardslot.4
147MLINKS+=cardbus.4 cbb.4 147MLINKS+=cardbus.4 cbb.4
148MLINKS+=crypto.4 swcrypto.4 148MLINKS+=crypto.4 swcrypto.4
 149MLINKS+=dbcool.4 dbCool.4
 150MLINKS+=dbcool.4 adm1027.4
 151MLINKS+=dbcool.4 adm1030.4
 152MLINKS+=dbcool.4 adt7463.4
 153MLINKS+=dbcool.4 adt7466.4
 154MLINKS+=dbcool.4 adt7467.4
 155MLINKS+=dbcool.4 adt7468.4
 156MLINKS+=dbcool.4 adt7473.4
 157MLINKS+=dbcool.4 adt7475.4
 158MLINKS+=dbcool.4 adt7476.4
149MLINKS+=fd.4 stderr.4 fd.4 stdin.4 fd.4 stdout.4 159MLINKS+=fd.4 stderr.4 fd.4 stdin.4 fd.4 stdout.4
150MLINKS+=fpa.4 fea.4 fpa.4 fta.4 160MLINKS+=fpa.4 fea.4 fpa.4 fta.4
151MLINKS+=icp.4 icpsp.4 161MLINKS+=icp.4 icpsp.4
152MLINKS+=irframe.4 irda.4 162MLINKS+=irframe.4 irda.4
153MLINKS+=le.4 bicc.4 le.4 nele.4 le.4 depca.4 163MLINKS+=le.4 bicc.4 le.4 nele.4 le.4 depca.4
154MLINKS+=midi.4 music.4 164MLINKS+=midi.4 music.4
155MLINKS+=midi.4 rmidi.4 165MLINKS+=midi.4 rmidi.4
156MLINKS+=midi.4 sequencer.4 166MLINKS+=midi.4 sequencer.4
157MLINKS+=mii.4 phy.4 167MLINKS+=mii.4 phy.4
158MLINKS+=netintro.4 networking.4 168MLINKS+=netintro.4 networking.4
159MLINKS+=nfsmb.4 nfsmbc.4 169MLINKS+=nfsmb.4 nfsmbc.4
160MLINKS+=nsmb.4 netsmb.4 170MLINKS+=nsmb.4 netsmb.4
161MLINKS+=ntwoc.4 ntwo.4 171MLINKS+=ntwoc.4 ntwo.4

File Added: src/share/man/man4/dbcool.4
.\"	$NetBSD: dbcool.4,v 1.1 2008/10/02 00:47:51 pgoyette Exp $
.\"
.\" Copyright (c) 2008 The NetBSD Foundation, Inc.
.\" All rights reserved.
.\"
.\" This code is derived from software contributed to The NetBSD Foundation
.\" by Paul Goyette.
.\"
.\" 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.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
.\" ``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 FOUNDATION OR CONTRIBUTORS
.\" 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.
.\"
.Dd September 28, 2008
.Dt dbCool 4
.Os
.Sh NAME
.Nm dbcool ,
.Nm adm1027 ,
.Nm adt7463 ,
.Nm adt7466 ,
.Nm adt7467 ,
.Nm adt7468 ,
.Nm adt7473 ,
.Nm adt7475 ,
.Nm adt7476
.Nd dbCool(tm) family of environmental monitors and fan controllers
.Sh SYNOPSIS
.Cd "dbcool* at ki2c?"
.Cd "dbcool* at iic? addr 0x2e"
.Sh DESCRIPTION
The
.Nm
driver provides support for the
.Tn Analog Devices
dbCool environmental monitor chips to be used with the
.Xr envsys 4
API.
.Pp
These chips support up to twelve sensors. Not all of the following sensors
are supported on all chips.
.Bl -column "Sensor" "Units" "Typical" -offset indent
.It Sy "Sensor" Ta Sy "Units" Ta Sy "Typical Use"
.It Li "l_temp" Ta "uK" Ta "local chip temperature"
.It Li "r1_temp" Ta "uK" Ta "CPU temperature"
.It Li "r2_temp" Ta "uK" Ta "GPU temperature"
.It Li "Vccp" Ta "uV DC" Ta "CPU Vcore"
.It Li "Vcc" Ta "uV DC" Ta "Chip's supply voltage"
.It Li "2.5V" Ta "uV DC" Ta "2.5V supply"
.It Li "5V" Ta "uV DC" Ta "5V supply"
.It Li "12V" Ta "uV DC" Ta "12V supply"
.It Li "AIN1" Ta "uV DC" Ta "Analog In (2.25V ref, ADT7466 only)"
.It Li "AIN2" Ta "uV DC" Ta "Analog In (2.25V ref, ADT7466 only)"
.It Li "fan1" Ta "RPM" Ta "Chassis Fan"
.It Li "fan2" Ta "RPM" Ta "Chassis Fan"
.It Li "fan3" Ta "RPM" Ta "Chassis Fan"
.It Li "fan4" Ta "RPM" Ta "Chassis Fan"
.El
.Pp
Except on the ADT7466, each temperature and voltage sensor has a
programmable high- and low-limit; fan sensors have only a low-limit.  The
user can set the threshold values using
.Xr sysctl 8
.Bd -literal -offset indent
hw.dbcool0.l_temp.low_lim = 35         degrees C
hw.dbcool0.l_temp.hi_lim = 75          degrees C
hw.dbcool0.fan1.low_lim = 300          RPM
hw.dbcool0.Vcc.low_lim = 2250          milliVolts
hw.dbcool0.Vcc.hi_lim = 2750           milliVolts
.Ed
.Pp
Temperature sensors also have
.Em Tmin ,
.Em Thyst ,
and
.Em Ttherm
.Xr sysctl 8
variables;  these values are used by the fan speed controllers.
.Pp
All
.Xr sysctl 8
variables associated with temperature sensors are in units of degC, since
this is the unit which is programmed into the device registers.  Limit
values for voltage sensors are in millivolts.  The low limit value for
fan sensors is measured in RPM;  due to the manner in which fan speed is
measured, the lowest possible value for a fan limit is 83 RPM.
.Pp
All members of the dbCool family support Pulse-Width Modulated (PWM)
fan speed control based on temperature thresholds - the fan will spin up
when one or more thermal sensors exceeds its configured 
.Em Tmin
value.  The fan will go faster as the temperature rises, and will slow
down as the temperature falls.  If the temperature exceeds the sensor's
.Em Ttherm
value, the THERM signal will be asserted, and if enabled the fan will
run at full speed.  The fan will be turned
off when the sensor(s) that triggered it reports a temperature which is
at least
.Em Thyst
degrees below its
.Em Tmin
threshold.
.Pp
Each fan controller is programmable using the following
.Xr sysctl 8
variables.
.Bd -literal -offset indent
hw.dbcool0.fan_ctl_0.behavior
hw.dbcool0.fan_ctl_0.range
hw.dbcool0.fan_ctl_0.min_duty
hw.dbcool0.fan_ctl_0.max_duty
hw.dbcool0.fan_ctl_0.cur_duty
.Ed
(On the ADM1030, the
.Em range
variable is associated with each individual temperature sensor rather 
than with the fan controller.)
.Pp
The 
.Em behavior
variable controls the selection of temperature sensors associated with
the fan controller.  When the associated temperature sensor reaches its
.Em Tmin
value, the fan controller starts the fan at its minimum duty cycle;
when the associated temperature sensor reaches its
.Em Ttherm
value and asserts the THERM signal (or if an external THERM signal is
asserted), the fan controller sets the fan speed to a 100% duty cycle.
Between these two settings, each temperature sensor is used to calculate
a duty cycle linearly based on the slope defined by the temperature sensor's
.Em range
variable.  When the associated temperature falls at least
.Em Thyst
degress below its
.Em Tmin
value, the fan controller will turn off the fan.  (On the ADM1030, the
value for
.Em Thyst
is fixed at 5 degrees C.)
.Pp
Valid values for the 
.Em behavior
variable are:
.Bd -literal -offset indent
local           (not available on ADM1030)
remote1
remote2         (not available on ADM1030)
local+remote2   (not available on ADM1030)
all-temps
full-speed      (not available on ADM1030)
manual
disabled
.Ed
.Pp
When the
.Em behavior
variable is set to "manual", the
.Em cur-duty
variable becomes user-writeable and can be set to any value between 0 and
100 inclusive to control the fan's duty cycle manually.  In all other
.Em behavior
modes, the
.Em cur-duty
variable is read-only and updates are ignored.
.Pp
The
.Em min-duty
and
.Em max-duty
variables define the range over which the fan controller will manage the
fan's duty cycle.  On the ADM1030, these values are not separately
controllable.  The
.Em max-duty
is fixed at 100%, and the
.Em cur-duty
variable is used to specify the minimum duty cycle when the fan
controller is running in automatic mode.
.Pp
Note that the duty-cycle value does not directly correspond to the fan's
speed.  That is, a 33% duty cycle does not mean that the fan runs at 33%
of its maximum speed;  in actuality, a 33% duty cycle drives the fan at
a speed close to 50% of its maximum.  Fan speed correlates approximately
to the square root of the duty cycle.
.Sh EXAMPLES
The
.Xr envstat 8
utility can be used to determine the sensors supported:
.Bd -literal -offset indent
            Current  CritMax  CritMin  CritCap     Unit
 l_temp:     44.250                                degC
r1_temp:     41.250                                degC
r2_temp:        N/A
   Vccp:      0.002                                   V
    Vcc:      3.351                                   V
   fan1:        N/A
   fan2:        N/A
   fan3:        N/A
   fan4:        N/A
.Ed
.Pp
Using this information, the following commands in /etc/envsys.conf will
set appropriate limits for CPU temperature and chip supply voltage, and
powerd will be notified if the limits are exceeded:
.Bd -literal -offset indent
dbcool0 {
        sensor0 {
                warning-max  = 60C;
                critical-max = 65C;
        }
        sensor4 {
                critical-min = 3.1;
                warning-min =  3.2;
                critical-max = 3.5;
        }
}
.Ed
.Pp
Alternatively, set the following commands in /etc/sysctl.conf to perform
limit checking in the hardware:
.Bd -literal -offset indent
hw.dbcool0.l_temp.hi_lim = 65
hw.dbcool0.Vcc.low_lim = 3200
hw.dbcool0.Vcc.hi_lim = 3500
.Ed
.Sh SEE ALSO
.Xr envsys 4 ,
.Xr envstat 8 ,
.Xr powerd 8 ,
.Xr sysctl 8
.Sh HISTORY
The
.Nm
device appeared in
.Nx 5.0 .
.Sh BUGS
Although the sensor limit registers can be programmed, there is currently
no use of the dbCool chips' ability to generate an SMBus interrupt when the
limits are exceeded.  Limit checking is only performed when the sensor
values are polled and refreshed.
.Pp
The ADT7466 chip, although officially a member of the dbCool family, is
programmed quite differently.  The fan controllers and sensor limits on
this chip are not currently implemented.

File Added: src/sys/dev/i2c/dbcool.c
/*	$NetBSD: dbcool.c,v 1.1 2008/10/02 00:47:51 pgoyette Exp $ */

/*-
 * Copyright (c) 2008 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Paul Goyette
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``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 FOUNDATION OR CONTRIBUTORS
 * 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.
 */

/* 
 * a driver for the dbCool(tm) family of environmental controllers
 *
 * Data sheets for the various supported chips are available at
 *
 *	http://www.onsemi.com/pub/Collateral/ADM1027-D.PDF
 *	http://www.onsemi.com/pub/Collateral/ADM1030-D.PDF
 *	http://www.onsemi.com/pub/Collateral/ADT7463-D.PDF
 *	http://www.onsemi.com/pub/Collateral/ADT7466.PDF
 *	http://www.onsemi.com/pub/Collateral/ADT7467-D.PDF
 *	http://www.onsemi.com/pub/Collateral/ADT7468-D.PDF
 *	http://www.onsemi.com/pub/Collateral/ADT7473-D.PDF
 *	http://www.onsemi.com/pub/Collateral/ADT7475-D.PDF
 *	http://www.onsemi.com/pub/Collateral/ADT7476-D.PDF
 *
 * (URLs are correct as of September 27, 2008)
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: dbcool.c,v 1.1 2008/10/02 00:47:51 pgoyette Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>

#include <uvm/uvm_extern.h>

#include <dev/i2c/dbcool_var.h>
#include <dev/i2c/dbcool_reg.h>

/* Config interface */
static int dbcool_match(device_t, cfdata_t, void *);
static void dbcool_attach(device_t, device_t, void *);
static int dbcool_detach(device_t, int);

/* Device attributes */
static int dbcool_supply_voltage(struct dbcool_softc *);
static uint8_t dbcool_islocked(struct dbcool_softc *);

/* Sensor read functions */
static void dbcool_refresh(struct sysmon_envsys *, envsys_data_t *);
static int dbcool_read_rpm(struct dbcool_softc *, uint8_t);
static int dbcool_read_temp(struct dbcool_softc *, uint8_t, bool);
static int dbcool_read_volt(struct dbcool_softc *, uint8_t, bool);

/* SYSCTL Helpers */
static uint8_t sysctl_dbcool_chipreg(struct dbcool_softc *, int);
static int sysctl_dbcool_tmin(SYSCTLFN_PROTO);
static int sysctl_adm1030_tmin(SYSCTLFN_PROTO);
static int sysctl_adm1030_trange(SYSCTLFN_PROTO);
static int sysctl_dbcool_duty(SYSCTLFN_PROTO);
static int sysctl_dbcool_behavior(SYSCTLFN_PROTO);
static int sysctl_dbcool_range(SYSCTLFN_PROTO);
static int sysctl_dbcool_volt_limit(SYSCTLFN_PROTO);
static int sysctl_dbcool_temp_limit(SYSCTLFN_PROTO);
static int sysctl_dbcool_fan_limit(SYSCTLFN_PROTO);
static int sysctl_dbcool_thyst(SYSCTLFN_PROTO);
static int sysctl_dbcool_vid(SYSCTLFN_PROTO);

#ifdef DBCOOL_DEBUG
static int sysctl_dbcool_reg_select(SYSCTLFN_PROTO);
static int sysctl_dbcool_reg_access(SYSCTLFN_PROTO);
#endif /* DBCOOL_DEBUG */

/*
 * Descriptions for SYSCTL entries
 */
struct dbc_names {
	const char *name;
	const char *desc;
	int (*helper)(SYSCTLFN_PROTO);
};

/*
 * The first several entries must remain in the same order as the 
 * corresponding entries in enum dbc_pwm_params
 */
static struct dbc_names dbc_sysctl_table[] = {
	{ "behavior",		"operating behavior and temp selector",
		sysctl_dbcool_behavior },
	{ "range",		"fan controller PWM slope or temp range",
		sysctl_dbcool_range },
	{ "min_duty",		"minimum fan controller PWM duty cycle",
		sysctl_dbcool_duty },
	{ "max_duty",		"maximum fan controller PWM duty cycle",
		sysctl_dbcool_duty },
	{ "cur_duty",		"current fan controller PWM duty cycle",
		sysctl_dbcool_duty },
	{ "Tmin",		"temp at which to start fan controller",
		sysctl_dbcool_tmin },
	{ "Ttherm",		"temp at which THERM is asserted",
		sysctl_dbcool_tmin },
	{ "Thyst",		"temp hysteresis for stopping fan controller",
		sysctl_dbcool_thyst },
	{ "Tmin",		"temp at which to start fan controller",
		sysctl_adm1030_tmin },
	{ "Trange",		"temp range to reach 100% duty cycle",
		sysctl_adm1030_trange },
};

static const char *dbc_sensor_names[] = {
	"l_temp",	"r1_temp",	"r2_temp",	"Vccp",		"Vcc",
	"fan1",		"fan2",		"fan3",		"fan4",		"AIN1",
	"AIN2",		"2.5V",		"5V",		"12V"
};

struct dbcool_sensor ADT7475_sensor_table[] = {
	{ DBC_TEMP, {	DBCOOL_LOCAL_TEMP,
			DBCOOL_LOCAL_HIGHLIM,
			DBCOOL_LOCAL_LOWLIM },		0, 0 },
	{ DBC_TEMP, {	DBCOOL_REMOTE1_TEMP,
			DBCOOL_REMOTE1_HIGHLIM,
			DBCOOL_REMOTE1_LOWLIM },	1, 0 },
	{ DBC_TEMP, {	DBCOOL_REMOTE2_TEMP,
			DBCOOL_REMOTE2_HIGHLIM,
			DBCOOL_REMOTE2_LOWLIM },	2, 0 },
	{ DBC_VOLT, {	DBCOOL_CPU_VOLTAGE,
			DBCOOL_VCCP_HIGHLIM,
			DBCOOL_VCCP_LOWLIM },		3, 0 },
	{ DBC_VOLT, {	DBCOOL_SUPPLY_VOLTAGE,
			DBCOOL_VCC_HIGHLIM,
			DBCOOL_VCC_LOWLIM },		4, 0 },
	{ DBC_FAN,  {	DBCOOL_FAN1_TACH_LSB,
			DBCOOL_NO_REG,
			DBCOOL_TACH1_MIN_LSB },		5, 0 },
	{ DBC_FAN,  {	DBCOOL_FAN2_TACH_LSB,
			DBCOOL_NO_REG,
			DBCOOL_TACH2_MIN_LSB },		6, 0 },
	{ DBC_FAN,  {	DBCOOL_FAN3_TACH_LSB,
			DBCOOL_NO_REG,
			DBCOOL_TACH3_MIN_LSB },		7, 0 },
	{ DBC_FAN,  {	DBCOOL_FAN4_TACH_LSB,
			DBCOOL_NO_REG,
			DBCOOL_TACH4_MIN_LSB },		8, 0 },
	{ DBC_CTL,  {	DBCOOL_LOCAL_TMIN,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		0, 5 },
	{ DBC_CTL,  {	DBCOOL_LOCAL_TTHRESH,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		0, 6 },
	{ DBC_CTL,  {	DBCOOL_R1_LCL_TMIN_HYST | 0x80,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		0, 7 },
	{ DBC_CTL,  {	DBCOOL_REMOTE1_TMIN,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		1, 5 },
	{ DBC_CTL,  {	DBCOOL_REMOTE1_TTHRESH,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		1, 6 },
	{ DBC_CTL,  {	DBCOOL_R1_LCL_TMIN_HYST,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		1, 7 },
	{ DBC_CTL,  {	DBCOOL_REMOTE2_TMIN,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		2, 5 },
	{ DBC_CTL,  {	DBCOOL_REMOTE2_TTHRESH,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		2, 6 },
	{ DBC_CTL,  {	DBCOOL_R2_TMIN_HYST,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		2, 7 },
	{ DBC_EOF,  { 0, 0, 0 }, 0, 0 }
};

/*
 * The members of dbcool_power_control must be in the same order as
 * in enum dbc_pwm_params
 */
struct dbcool_power_control ADT7475_power_table[] = {
	{ DBCOOL_PWM1_CTL, DBCOOL_PWM1_TRANGE, DBCOOL_PWM1_MINDUTY,
		DBCOOL_PWM1_MAXDUTY, DBCOOL_PWM1_CURDUTY,
		"fan_control_1" },
	{ DBCOOL_PWM2_CTL, DBCOOL_PWM2_TRANGE, DBCOOL_PWM2_MINDUTY,
		DBCOOL_PWM2_MAXDUTY, DBCOOL_PWM2_CURDUTY,
		"fan_control_2" },
	{ DBCOOL_PWM3_CTL, DBCOOL_PWM3_TRANGE, DBCOOL_PWM3_MINDUTY, 
		DBCOOL_PWM3_MAXDUTY, DBCOOL_PWM3_CURDUTY,
		"fan_control_3" },
	{ 0, 0, 0, 0, 0, NULL }
};

struct dbcool_sensor ADT7466_sensor_table[] = {
	{ DBC_TEMP, {	DBCOOL_ADT7466_LCL_TEMP_MSB,
			DBCOOL_ADT7466_LCL_TEMP_HILIM,
			DBCOOL_ADT7466_LCL_TEMP_LOLIM }, 0,  0 },
	{ DBC_TEMP, {	DBCOOL_ADT7466_REM_TEMP_MSB,
			DBCOOL_ADT7466_REM_TEMP_HILIM,
			DBCOOL_ADT7466_REM_TEMP_LOLIM }, 1,  0 },
	{ DBC_VOLT, {	DBCOOL_ADT7466_VCC,
			DBCOOL_ADT7466_VCC_HILIM,
			DBCOOL_ADT7466_VCC_LOLIM },	4,  0 },
	{ DBC_VOLT, {	DBCOOL_ADT7466_AIN1,
			DBCOOL_ADT7466_AIN1_HILIM,
			DBCOOL_ADT7466_AIN1_LOLIM },	9,  0 },
	{ DBC_VOLT, {	DBCOOL_ADT7466_AIN2,
			DBCOOL_ADT7466_AIN2_HILIM,
			DBCOOL_ADT7466_AIN2_LOLIM },	10, 0 },
	{ DBC_FAN,  {	DBCOOL_ADT7466_FANA_LSB,
			DBCOOL_NO_REG,
			DBCOOL_ADT7466_FANA_LOLIM_LSB }, 5,  0 },
	{ DBC_FAN,  {	DBCOOL_ADT7466_FANB_LSB,
			DBCOOL_NO_REG,
			DBCOOL_ADT7466_FANB_LOLIM_LSB }, 6,  0 },
	{ DBC_EOF,  { 0, 0, 0 }, 0, 0 }
};

struct dbcool_sensor ADM1027_sensor_table[] = {
	{ DBC_TEMP, {	DBCOOL_LOCAL_TEMP,
			DBCOOL_LOCAL_HIGHLIM,
			DBCOOL_LOCAL_LOWLIM },		0, 0 },
	{ DBC_TEMP, {	DBCOOL_REMOTE1_TEMP,
			DBCOOL_REMOTE1_HIGHLIM,
			DBCOOL_REMOTE1_LOWLIM },	1, 0 },
	{ DBC_TEMP, {	DBCOOL_REMOTE2_TEMP,
			DBCOOL_REMOTE2_HIGHLIM,
			DBCOOL_REMOTE2_LOWLIM },	2, 0 },
	{ DBC_VOLT, {	DBCOOL_CPU_VOLTAGE,
			DBCOOL_VCCP_HIGHLIM,
			DBCOOL_VCCP_LOWLIM },		3, 0 },
	{ DBC_VOLT, {	DBCOOL_SUPPLY_VOLTAGE,
			DBCOOL_VCC_HIGHLIM,
			DBCOOL_VCC_LOWLIM },		4, 0 },
	{ DBC_VOLT, {	DBCOOL_25VIN,
			DBCOOL_25VIN_HIGHLIM,
			DBCOOL_25VIN_LOWLIM },		4, 0 },
	{ DBC_VOLT, {	DBCOOL_5VIN,
			DBCOOL_5VIN_HIGHLIM,
			DBCOOL_5VIN_LOWLIM },		4, 0 },
	{ DBC_VOLT, {	DBCOOL_12VIN,
			DBCOOL_12VIN_HIGHLIM,
			DBCOOL_12VIN_LOWLIM },		4, 0 },
	{ DBC_FAN,  {	DBCOOL_FAN1_TACH_LSB,
			DBCOOL_NO_REG,
			DBCOOL_TACH1_MIN_LSB },		5, 0 },
	{ DBC_FAN,  {	DBCOOL_FAN2_TACH_LSB,
			DBCOOL_NO_REG,
			DBCOOL_TACH2_MIN_LSB },		6, 0 },
	{ DBC_FAN,  {	DBCOOL_FAN3_TACH_LSB,
			DBCOOL_NO_REG,
			DBCOOL_TACH3_MIN_LSB },		7, 0 },
	{ DBC_FAN,  {	DBCOOL_FAN4_TACH_LSB,
			DBCOOL_NO_REG,
			DBCOOL_TACH4_MIN_LSB },		8, 0 },
	{ DBC_CTL,  {	DBCOOL_LOCAL_TMIN,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		0, 5 },
	{ DBC_CTL,  {	DBCOOL_LOCAL_TTHRESH,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		0, 6 },
	{ DBC_CTL,  {	DBCOOL_R1_LCL_TMIN_HYST | 0x80,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		0, 7 },
	{ DBC_CTL,  {	DBCOOL_REMOTE1_TMIN,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		1, 5 },
	{ DBC_CTL,  {	DBCOOL_REMOTE1_TTHRESH,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		1, 6 },
	{ DBC_CTL,  {	DBCOOL_R1_LCL_TMIN_HYST,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		1, 7 },
	{ DBC_CTL,  {	DBCOOL_REMOTE2_TMIN,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		2, 5 },
	{ DBC_CTL,  {	DBCOOL_REMOTE2_TTHRESH,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		2, 6 },
	{ DBC_CTL,  {	DBCOOL_R2_TMIN_HYST,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		2, 7 },
	{ DBC_EOF,  { 0, 0, 0 }, 0, 0 }
};

struct dbcool_sensor ADM1030_sensor_table[] = {
	{ DBC_TEMP, {	DBCOOL_ADM1030_L_TEMP,
			DBCOOL_ADM1030_L_HI_LIM,
			DBCOOL_ADM1030_L_LO_LIM },	0,  0 },
	{ DBC_TEMP, {	DBCOOL_ADM1030_R_TEMP,
			DBCOOL_ADM1030_R_HI_LIM,
			DBCOOL_ADM1030_R_LO_LIM },	1,  0 },
	{ DBC_FAN,  {	DBCOOL_ADM1030_FAN_TACH,
			DBCOOL_NO_REG,
			DBCOOL_ADM1030_FAN_LO_LIM },	5,  0 },
	{ DBC_CTL,  {	DBCOOL_ADM1030_L_TMIN,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		0,  8 },
	{ DBC_CTL,  {	DBCOOL_ADM1030_L_TTHRESH,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		0,  9 },
	{ DBC_CTL,  {	DBCOOL_ADM1030_L_TTHRESH,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		0,  6 },
	{ DBC_CTL,  {	DBCOOL_ADM1030_R_TMIN,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		1,  8 },
	{ DBC_CTL,  {	DBCOOL_ADM1030_L_TTHRESH,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		1,  9 },
	{ DBC_CTL,  {	DBCOOL_ADM1030_R_TTHRESH,
			DBCOOL_NO_REG,
			DBCOOL_NO_REG },		1,  6 },
	{ DBC_EOF,  {0, 0, 0 }, 0, 0 }
};

struct dbcool_power_control ADM1030_power_table[] = {   
	{ DBCOOL_ADM1030_CFG1, 0, 0, 0, DBCOOL_ADM1030_FAN_SPEED_CFG,
		"fan_control_1" },
	{ 0, 0, 0, 0, 0, NULL }
};

struct chip_id chip_table[] = {
	{ DBCOOL_COMPANYID, ADT7476_DEVICEID, 0xff,
		ADT7475_sensor_table, ADT7475_power_table,
		DBCFLAG_TEMPOFFSET | DBCFLAG_HAS_MAXDUTY | DBCFLAG_HAS_VID,
		90000 * 60, "ADT7476" },
	{ DBCOOL_COMPANYID, ADT7475_DEVICEID, 0xff,
		ADT7475_sensor_table, ADT7475_power_table,
		DBCFLAG_TEMPOFFSET | DBCFLAG_HAS_MAXDUTY | DBCFLAG_HAS_SHDN,
		90000 * 60, "ADT7475" },
	{ DBCOOL_COMPANYID, ADT7473_DEVICEID, ADT7473_REV_ID,
		ADT7475_sensor_table, ADT7475_power_table,
		DBCFLAG_TEMPOFFSET | DBCFLAG_HAS_MAXDUTY | DBCFLAG_HAS_SHDN,
		90000 * 60, "ADT7463" },
	{ DBCOOL_COMPANYID, ADT7473_DEVICEID, ADT7473_1_REV_ID,
		ADT7475_sensor_table, ADT7475_power_table,
		DBCFLAG_TEMPOFFSET | DBCFLAG_HAS_MAXDUTY | DBCFLAG_HAS_SHDN,
		90000 * 60, "ADT7463-1" },
	{ DBCOOL_COMPANYID, ADT7468_DEVICEID, 0xff,
		ADT7475_sensor_table, ADT7475_power_table,
		DBCFLAG_TEMPOFFSET  | DBCFLAG_MULTI_VCC | DBCFLAG_HAS_MAXDUTY |
		    DBCFLAG_4BIT_VER | DBCFLAG_HAS_SHDN | DBCFLAG_HAS_VID,
		90000 * 60, "ADT7468" },
	{ DBCOOL_COMPANYID, ADT7467_DEVICEID, 0xff,
		ADT7475_sensor_table, ADT7475_power_table,
		DBCFLAG_TEMPOFFSET | DBCFLAG_MULTI_VCC | DBCFLAG_HAS_MAXDUTY |
		    DBCFLAG_4BIT_VER | DBCFLAG_HAS_SHDN,
		90000 * 60, "ADT7467" },
	{ DBCOOL_COMPANYID, ADT7466_DEVICEID, 0xff,
		ADT7466_sensor_table, NULL,
		DBCFLAG_ADT7466 | DBCFLAG_TEMPOFFSET | DBCFLAG_HAS_SHDN,
		82000 * 60, "ADT7466" },
	{ DBCOOL_COMPANYID, ADT7463_DEVICEID, ADT7463_REV_ID1,
		ADM1027_sensor_table, ADT7475_power_table,
		DBCFLAG_MULTI_VCC | DBCFLAG_4BIT_VER | DBCFLAG_HAS_SHDN |
		    DBCFLAG_ADM1027 | DBCFLAG_HAS_VID,
		90000 * 60, "ADT7463" },
	{ DBCOOL_COMPANYID, ADT7463_DEVICEID, ADT7463_REV_ID2,
		ADM1027_sensor_table, ADT7475_power_table,
		DBCFLAG_MULTI_VCC | DBCFLAG_4BIT_VER | DBCFLAG_HAS_SHDN |
		    DBCFLAG_HAS_VID,
		90000 * 60, "ADT7463" },
	{ DBCOOL_COMPANYID, ADM1027_DEVICEID, ADM1027_REV_ID,
		ADM1027_sensor_table, ADT7475_power_table,
		DBCFLAG_MULTI_VCC | DBCFLAG_4BIT_VER,
		90000 * 60, "ADM1027" },
	{ DBCOOL_COMPANYID, ADM1030_DEVICEID, 0xff,
		ADM1030_sensor_table, ADM1030_power_table,
		DBCFLAG_ADM1030,
		11250 * 60, "ADM1030" },
	{ 0, 0, 0, NULL, NULL, 0, 0, NULL }
};

static const char *behavior[] = {
	"remote1",	"local",	"remote2",	"full-speed",
	"disabled",	"local+remote2","all-temps",	"manual"
};

static char dbcool_cur_behav[16];

CFATTACH_DECL_NEW(dbcool, sizeof(struct dbcool_softc),
    dbcool_match, dbcool_attach, dbcool_detach, NULL);

int
dbcool_match(device_t parent, cfdata_t cf, void *aux)
{
	struct i2c_attach_args *ia = aux;
	struct dbcool_softc sc;
	sc.sc_tag = ia->ia_tag;
	sc.sc_addr = ia->ia_addr;

	/* no probing if we attach to iic, but verify chip id */
	if (dbcool_chip_ident(&sc) >= 0)
		return 1;

	return 0;
}

void
dbcool_attach(device_t parent, device_t self, void *aux)
{
	struct dbcool_softc *sc = device_private(self);
	struct i2c_attach_args *args = aux;
	uint8_t ver;

	sc->sc_addr = args->ia_addr;
	sc->sc_tag = args->ia_tag;
	(void)dbcool_chip_ident(sc);

	aprint_naive("\n");
	aprint_normal("\n");

	ver = dbcool_readreg(sc, DBCOOL_REVISION_REG);
	if (sc->sc_chip->flags & DBCFLAG_4BIT_VER)
		aprint_normal_dev(self, "%s dBCool(tm) Controller "
			"(rev 0x%02x, stepping 0x%02x)\n", sc->sc_chip->name,
			ver >> 4, ver & 0x0f);
	else
		aprint_normal_dev(self, "%s dBCool(tm) Controller "
			"(rev 0x%04x)\n", sc->sc_chip->name, ver);

	dbcool_setup(self);

	if (!pmf_device_register(self, dbcool_pmf_suspend, dbcool_pmf_resume))
		aprint_error_dev(self, "couldn't establish power handler\n");
}

static int
dbcool_detach(device_t self, int flags)
{
	struct dbcool_softc *sc = device_private(self);

	sysmon_envsys_unregister(sc->sc_sme);
	sysmon_envsys_destroy(sc->sc_sme);
	sc->sc_sme = NULL;
	return 0;
}

/* On suspend, we save the state of the SHDN bit, then set it */
bool dbcool_pmf_suspend(device_t dev PMF_FN_ARGS)
{
	struct dbcool_softc *sc = device_private(dev);
	uint8_t reg, bit, cfg;

	if ((sc->sc_chip->flags && DBCFLAG_HAS_SHDN) == 0)
		return true;
 
	if (sc->sc_chip->flags && DBCFLAG_ADT7466) {
		reg = DBCOOL_ADT7466_CONFIG2;
		bit = DBCOOL_ADT7466_CFG2_SHDN;
	} else {
		reg = DBCOOL_CONFIG2_REG;
		bit = DBCOOL_CFG2_SHDN;
	}
	cfg = dbcool_readreg(sc, reg);
	sc->sc_suspend = cfg & bit;
	cfg |= bit;
	dbcool_writereg(sc, reg, cfg);

	return true;
}

/* On resume, we restore the previous state of the SHDN bit */
bool dbcool_pmf_resume(device_t dev PMF_FN_ARGS)
{
	struct dbcool_softc *sc = device_private(dev);
	uint8_t reg, bit, cfg;

	if ((sc->sc_chip->flags && DBCFLAG_HAS_SHDN) == 0)
		return true;
 
	if (sc->sc_chip->flags && DBCFLAG_ADT7466) {
		reg = DBCOOL_ADT7466_CONFIG2;
		bit = DBCOOL_ADT7466_CFG2_SHDN;
	} else {
		reg = DBCOOL_CONFIG2_REG;
		bit = DBCOOL_CFG2_SHDN;
	}
	cfg = dbcool_readreg(sc, reg);
	cfg &= ~sc->sc_suspend;
	dbcool_writereg(sc, reg, cfg);

	return true;

}

uint8_t
dbcool_readreg(struct dbcool_softc *sc, uint8_t reg)
{
	uint8_t data = 0;

	if (iic_acquire_bus(sc->sc_tag, 0) != 0)
		goto bad;

	if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
		     sc->sc_addr, NULL, 0, &reg, 1, 0) != 0)
		goto bad;

	iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
		 sc->sc_addr, NULL, 0, &data, 1, 0);
bad:
	iic_release_bus(sc->sc_tag, 0);
	return data;
}

void 
dbcool_writereg(struct dbcool_softc *sc, uint8_t reg, uint8_t val)
{
        if (iic_acquire_bus(sc->sc_tag, 0) != 0)
                return;
        
        iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
                 sc->sc_addr, &reg, 1, &val, 1, 0);

        iic_release_bus(sc->sc_tag, 0);
        return;
}       

static uint8_t
dbcool_islocked(struct dbcool_softc *sc)
{
	uint8_t cfg_reg;

	if (sc->sc_chip->flags & DBCFLAG_ADM1030)
		return 0;

	if (sc->sc_chip->flags & DBCFLAG_ADT7466)
		cfg_reg = DBCOOL_ADT7466_CONFIG1;
	else
		cfg_reg = DBCOOL_CONFIG1_REG;

	if (dbcool_readreg(sc, cfg_reg) & DBCOOL_CFG1_LOCK)
		return 1;
	else
		return 0;
}

static int
dbcool_read_temp(struct dbcool_softc *sc, uint8_t reg, bool extres)
{
	uint8_t	t1, t2, t3, val, ext = 0;
	uint8_t offset;
	int temp;

	offset = sc->sc_temp_offset;
	if (sc->sc_chip->flags & DBCFLAG_ADT7466) {
		/*
		 * ADT7466 temps are in strange location
		 */
		ext = dbcool_readreg(sc, DBCOOL_ADT7466_CONFIG1);
		val = dbcool_readreg(sc, reg);
		if (extres)
			ext = dbcool_readreg(sc, reg + 1);
	} else if (sc->sc_chip->flags & DBCFLAG_ADM1030) {
		/*
		 * ADM1030 temps are in their own special place, too
		 */
		if (extres) {
			ext = dbcool_readreg(sc, DBCOOL_ADM1030_TEMP_EXTRES);
			if (reg == DBCOOL_ADM1030_L_TEMP)
				ext >>= 6;
			else
				ext >>= 1;
			ext &= 0x03;
		}
		val = dbcool_readreg(sc, reg);
	} else {
		if (extres) {
			ext = dbcool_readreg(sc, DBCOOL_EXTRES2_REG);

			/* Read all msb regs to unlatch them */
			t1 = dbcool_readreg(sc, DBCOOL_12VIN);
			t1 = dbcool_readreg(sc, DBCOOL_REMOTE1_TEMP);
			t2 = dbcool_readreg(sc, DBCOOL_REMOTE2_TEMP);
			t3 = dbcool_readreg(sc, DBCOOL_LOCAL_TEMP);
			switch (reg) {
			case DBCOOL_REMOTE1_TEMP:
				val = t1;
				ext >>= 2;
				break;
			case DBCOOL_LOCAL_TEMP:
				val = t3;
				ext >>= 4;
				break;
			case DBCOOL_REMOTE2_TEMP:
				val = t2;
				ext >>= 6;
				break;
			default:
				val = 0;
				break;
			}
			ext &= 0x03;
		}
		else
			val = dbcool_readreg(sc, reg);
	}

	/* Check for invalid temp values */
	if ((offset == 0 && val == 0x80) || (offset != 0 && val == 0))
		return 0;

	/* If using offset mode, adjust, else treat as signed */
	if (offset) {
		temp = val;
		temp -= offset;
	} else
		temp = (int8_t)val;

	/* Convert degC to uK and include extended precision bits */
	temp *= 1000000;
	temp +=  250000 * (int)ext;
	temp += 273150000U;

	return temp;
}

static int
dbcool_read_rpm(struct dbcool_softc *sc, uint8_t reg)
{
	int rpm;
	uint8_t rpm_lo, rpm_hi;

	rpm_lo = dbcool_readreg(sc, reg);
	if (sc->sc_chip->flags & DBCFLAG_ADM1030)
		rpm_hi = (rpm_lo == 0xff)?0xff:0x0;
	else
		rpm_hi = dbcool_readreg(sc, reg + 1);

	rpm = (rpm_hi << 8) | rpm_lo;
	if (rpm == 0xffff)
		return 0;	/* 0xffff indicates stalled/failed fan */

	return (sc->sc_chip->rpm_dividend / rpm);
}

/* Provide chip's supply voltage, in millivolts */
static int
dbcool_supply_voltage(struct dbcool_softc *sc)
{
	if (sc->sc_chip->flags & DBCFLAG_MULTI_VCC) {
		if (dbcool_readreg(sc, DBCOOL_CONFIG1_REG) & DBCOOL_CFG1_Vcc)
			return 5000;
		else
			return 3300;
	} else if (sc->sc_chip->flags & DBCFLAG_ADT7466) {
		if (dbcool_readreg(sc, DBCOOL_ADT7466_CONFIG1) &
			    DBCOOL_ADT7466_CFG1_Vcc)
			return 5000;
		else
			return 3300;
	} else
		return 3300;
}

static int
dbcool_read_volt(struct dbcool_softc *sc, uint8_t reg, bool extres)
{
	uint8_t ext = 0, v1, v2, v3, v4, val;
	int ret, nom;

	/* ADT7466 voltages are in strange locations with only 8-bits */
	if (sc->sc_chip->flags & DBCFLAG_ADT7466) {
		val = dbcool_readreg(sc, reg);
		if (reg == DBCOOL_ADT7466_VCC)
			nom = 3300;
		else
			nom = 1687;	/* Full-scale is 2.25V */
	} else if (sc->sc_chip->flags & DBCFLAG_ADM1030) {
		/*
		 * There are no voltage sensors on the ADM1030
		 */
		return 0;
	} else if (reg == DBCOOL_12VIN || reg == DBCOOL_12VIN_LOWLIM ||
		   reg == DBCOOL_12VIN_HIGHLIM) {
		/* It's a "normal" dbCool chip */
		if (extres)
			ext = dbcool_readreg(sc, DBCOOL_EXTRES2_REG) && 0x03;
		val = dbcool_readreg(sc, reg);
		nom = 12000;
		/*
		 * Must read the temps associated with the same extres
		 * register in order to unlatch it!
		 */
		if (extres)
			(void)dbcool_read_temp(sc, DBCOOL_LOCAL_TEMP, true);
	} else {
		if (extres) {
			ext = dbcool_readreg(sc, DBCOOL_EXTRES1_REG);
			v1 = dbcool_readreg(sc, DBCOOL_25VIN);
			v2 = dbcool_readreg(sc, DBCOOL_CPU_VOLTAGE);
			v3 = dbcool_readreg(sc, DBCOOL_SUPPLY_VOLTAGE);
			v4 = dbcool_readreg(sc, DBCOOL_5VIN);
		} else
			v1 = v2 = v3 = v4 = dbcool_readreg(sc, reg);

		switch (reg) {
		case DBCOOL_25VIN:
		case DBCOOL_25VIN_LOWLIM:
		case DBCOOL_25VIN_HIGHLIM:
			val = v1;
			nom = 2500;
			break;
		case DBCOOL_CPU_VOLTAGE:
		case DBCOOL_VCCP_LOWLIM:
		case DBCOOL_VCCP_HIGHLIM:
			/* All known chips use a 2.25V reference */
			val = v2;
			nom = 2250;
			ext >>= 2;
			break;
		case DBCOOL_SUPPLY_VOLTAGE:
		case DBCOOL_VCC_LOWLIM:
		case DBCOOL_VCC_HIGHLIM:
			val = v3;
			nom = dbcool_supply_voltage(sc);
			ext >>= 4;
			break;
		case DBCOOL_5VIN:
		case DBCOOL_5VIN_LOWLIM:
		case DBCOOL_5VIN_HIGHLIM:
			val = v4;
			nom = 5000;
			ext >>= 6;
			break;
		default:
			val = nom = 0;
		}
		ext &= 0x03;
	}

	/* 
	 * Scale the nominal value by the 10-bit fraction
	 * To avoid overflows, the nominal value is specified in millivolts!
	 * Returned value is in microvolts.
	 */
	ret = (uint16_t)val << 2 | ext;
	ret = (ret * nom) / 0x300;
	ret *= 1000;

	return ret;
}

SYSCTL_SETUP(sysctl_dbcoolsetup, "sysctl dBCool subtree setup")
{
	sysctl_createv(NULL, 0, NULL, NULL,
		       CTLFLAG_PERMANENT,
		       CTLTYPE_NODE, "hw", NULL,
		       NULL, 0, NULL, 0,
		       CTL_HW, CTL_EOL);
}

/*
 * Select the appropriate register based on fan controller index
 * and attribute
 */
static uint8_t
sysctl_dbcool_chipreg(struct dbcool_softc *sc, int num)
{
	uint8_t reg;
	int idx;

	if (num & 0x10000) {
		idx =(num >> 8) & 0xff;
		switch (num & 0xff) {
		case DBC_PWM_BEHAVIOR:
			reg = sc->sc_chip->power[idx].behavior;
			break;
		case DBC_PWM_RANGE:
			reg = sc->sc_chip->power[idx].range;
			break;
		case DBC_PWM_MIN_DUTY:
			reg = sc->sc_chip->power[idx].min;
			break;
		case DBC_PWM_MAX_DUTY:
			reg = sc->sc_chip->power[idx].max;
			break;
		case DBC_PWM_CUR_DUTY:
			reg = sc->sc_chip->power[idx].cur;
			break;
		default:
			reg = 0xff;
			break;
		}
	}
	else
		reg = sc->sc_chip->table[num].reg.val_reg;

	return reg;
}

static int
sysctl_dbcool_tmin(SYSCTLFN_ARGS)
{
	struct sysctlnode node;
	struct dbcool_softc *sc;
	int reg, error;
	uint8_t chipreg;
	uint8_t newreg;

	node = *rnode;
	sc = (struct dbcool_softc *)node.sysctl_data;
	chipreg = sysctl_dbcool_chipreg(sc, node.sysctl_num);

	if (sc->sc_temp_offset) {
		reg = dbcool_readreg(sc, chipreg);
		reg -= sc->sc_temp_offset;
	} else
		reg = (int8_t)dbcool_readreg(sc, chipreg);

	node.sysctl_data = &reg;
	error = sysctl_lookup(SYSCTLFN_CALL(&node));

	if (error || newp == NULL)
		return error;

	/* We were asked to update the value - sanity check before writing */	
	if (*(int *)node.sysctl_data < -64 ||
	    *(int *)node.sysctl_data > 127 + sc->sc_temp_offset)
		return EINVAL;

	newreg = *(int *)node.sysctl_data;
	newreg += sc->sc_temp_offset;
	dbcool_writereg(sc, chipreg, newreg);
	return 0;
}

static int
sysctl_adm1030_tmin(SYSCTLFN_ARGS)
{
	struct sysctlnode node;
	struct dbcool_softc *sc;
	int reg, error;
	uint8_t chipreg, oldreg, newreg;

	node = *rnode;
	sc = (struct dbcool_softc *)node.sysctl_data;
	chipreg = sysctl_dbcool_chipreg(sc, node.sysctl_num);

	oldreg = (int8_t)dbcool_readreg(sc, chipreg);
	reg = (oldreg >> 1) & ~0x03;

	node.sysctl_data = &reg;
	error = sysctl_lookup(SYSCTLFN_CALL(&node));

	if (error || newp == NULL)
		return error;

	/* We were asked to update the value - sanity check before writing */	
	if (*(int *)node.sysctl_data < 0 || *(int *)node.sysctl_data > 127)
		return EINVAL;

	newreg = *(int *)node.sysctl_data;
	newreg &= ~0x03;
	newreg <<= 1;
	newreg |= (oldreg & 0x07);
	dbcool_writereg(sc, chipreg, newreg);
	return 0;
}

static int
sysctl_adm1030_trange(SYSCTLFN_ARGS)
{
	struct sysctlnode node;
	struct dbcool_softc *sc;
	int reg, error, newval;
	uint8_t chipreg, oldreg, newreg;

	node = *rnode;
	sc = (struct dbcool_softc *)node.sysctl_data;
	chipreg = sysctl_dbcool_chipreg(sc, node.sysctl_num);

	oldreg = (int8_t)dbcool_readreg(sc, chipreg);
	reg = oldreg & 0x07;

	node.sysctl_data = &reg;
	error = sysctl_lookup(SYSCTLFN_CALL(&node));

	if (error || newp == NULL)
		return error;

	/* We were asked to update the value - sanity check before writing */	
	newval = *(int *)node.sysctl_data;

	if (newval == 5)
		newreg = 0;
	else if (newval == 10)
		newreg = 1;
	else if (newval == 20)
		newreg = 2;
	else if (newval == 40)
		newreg = 3;
	else if (newval == 80)
		newreg = 4;
	else
		return EINVAL;

	newreg |= (oldreg & ~0x07);
	dbcool_writereg(sc, chipreg, newreg);
	return 0;
}

static int
sysctl_dbcool_duty(SYSCTLFN_ARGS)
{
	struct sysctlnode node;
	struct dbcool_softc *sc;
	int reg, error;
	uint8_t chipreg, oldreg, newreg;

	node = *rnode;
	sc = (struct dbcool_softc *)node.sysctl_data;
	chipreg = sysctl_dbcool_chipreg(sc, node.sysctl_num);

	oldreg = dbcool_readreg(sc, chipreg);
	reg = (uint32_t)oldreg;
	if (sc->sc_chip->flags & DBCFLAG_ADM1030)
		reg = ((reg & 0x0f) * 100) / 15;
	else
		reg = (reg * 100) / 255;
	node.sysctl_data = &reg;
	error = sysctl_lookup(SYSCTLFN_CALL(&node));

	if (error || newp == NULL)
		return error;

	/* We were asked to update the value - sanity check before writing */	
	if (*(int *)node.sysctl_data < 0 || *(int *)node.sysctl_data > 100)
		return EINVAL;

	if (sc->sc_chip->flags & DBCFLAG_ADM1030) {
		newreg = *(uint8_t *)(node.sysctl_data) * 15 / 100;
		newreg |= oldreg & 0xf0;
	} else
		newreg = *(uint8_t *)(node.sysctl_data) * 255 / 100;
	dbcool_writereg(sc, chipreg, newreg);
	return 0;
}

static int
sysctl_dbcool_behavior(SYSCTLFN_ARGS)
{
	struct sysctlnode node;
	struct dbcool_softc *sc;
	int i, reg, error;
	uint8_t chipreg, oldreg, newreg;

	node = *rnode;
	sc = (struct dbcool_softc *)node.sysctl_data;
	chipreg = sysctl_dbcool_chipreg(sc, node.sysctl_num);
	
	oldreg = dbcool_readreg(sc, chipreg);
	if (sc->sc_chip->flags & DBCFLAG_ADM1030) {
		if ((dbcool_readreg(sc, DBCOOL_ADM1030_CFG2) & 1) == 0)
			reg = 4;
		else if ((oldreg & 0x80) == 0)
			reg = 7;
		else if ((oldreg & 0x60) == 0)
			reg = 4;
		else
			reg = 6;
	} else
		reg = (oldreg >> 5) & 0x07;

	strlcpy(dbcool_cur_behav, behavior[reg], sizeof(dbcool_cur_behav));
	node.sysctl_data = dbcool_cur_behav;
	error = sysctl_lookup(SYSCTLFN_CALL(&node));

	if (error || newp == NULL)
		return error;

	/* We were asked to update the value - convert string to value */
	newreg = __arraycount(behavior);
	for (i = 0; i < __arraycount(behavior); i++)
		if (strcmp(node.sysctl_data, behavior[i]) == 0)
			break;
	if (i >= __arraycount(behavior))
		return EINVAL;

	if (sc->sc_chip->flags & DBCFLAG_ADM1030) {
		/*
		 * ADM1030 splits fan controller behavior across two
		 * registers.  We also do not support Auto-Filter mode
		 * nor do we support Manual-RPM-feedback.
		 */
		if (newreg == 4) {
			oldreg = dbcool_readreg(sc, DBCOOL_ADM1030_CFG2);
			oldreg &= ~0x01;
			dbcool_writereg(sc, DBCOOL_ADM1030_CFG2, oldreg);
		} else {
			if (newreg == 0)
				newreg = 4;
			else if (newreg == 6)
				newreg = 7;
			else if (newreg == 7)
				newreg = 0;
			else
				return EINVAL;
			newreg <<= 5;
			newreg |= (oldreg & 0x1f);
			dbcool_writereg(sc, chipreg, newreg);
			oldreg = dbcool_readreg(sc, DBCOOL_ADM1030_CFG2) | 1;
			dbcool_writereg(sc, DBCOOL_ADM1030_CFG2, oldreg);
		}
	} else {
		newreg = (dbcool_readreg(sc, chipreg) & 0x1f) | (i << 5);
		dbcool_writereg(sc, chipreg, newreg);
	}
	return 0;
}

static int
sysctl_dbcool_range(SYSCTLFN_ARGS)
{
	struct sysctlnode node;
	struct dbcool_softc *sc;
	int reg, error;
	uint8_t chipreg;
	uint8_t newreg;

	node = *rnode;
	sc = (struct dbcool_softc *)node.sysctl_data;
	chipreg = sysctl_dbcool_chipreg(sc, node.sysctl_num);
	
	reg = (dbcool_readreg(sc, chipreg) >> 4) & 0x0f;
	node.sysctl_data = &reg;
	error = sysctl_lookup(SYSCTLFN_CALL(&node));

	if (error || newp == NULL)
		return error;

	/* We were asked to update the value - sanity check before writing */	
	if (*(int *)node.sysctl_data < 0 || *(int *)node.sysctl_data > 0x0f)
		return EINVAL;

	newreg = (dbcool_readreg(sc, chipreg) & 0x0f) |
		  (*(int *)node.sysctl_data << 4);
	dbcool_writereg(sc, chipreg, newreg);
	return 0;
}

static int
sysctl_dbcool_volt_limit(SYSCTLFN_ARGS)
{
	struct sysctlnode node;
	struct dbcool_softc *sc;
	int reg, error;
	int nom, newval;
	uint8_t chipreg, newreg;

	node = *rnode;
	sc = (struct dbcool_softc *)node.sysctl_data;
	chipreg = node.sysctl_num;

	/*
	 * Determine the nominal voltage for this sysctl node
	 * Values are maintained in milliVolts so we can use 
	 * integer rather than floating point arithmetic
	 */
	if (sc->sc_chip->flags & DBCFLAG_ADT7466)
		switch ((chipreg - DBCOOL_ADT7466_AIN1_LOLIM) / 2) {
		case 0:		/* AIN1 */
		case 1:		/* AIN2 */
			nom = 1687;	/* XXX Full-scale is 2.250V? */
			break;
		case 2:		/* Vcc */
			nom = 3300;
			break;
		default:
			nom = 0;
			break;
		}
	else if (sc->sc_chip->flags & DBCFLAG_ADM1030) {
		/*
		 * There are no voltage sensors on the ADM1030
		 */
		return EINVAL;
	} else 
		/*
		 * It's a "normal" dbCool chip
		 */
		switch ((chipreg - DBCOOL_25VIN_LOWLIM) / 2) {
		case 0:		/* 2.5V */
			nom = 2500;
			break;
		case 1:		/* Vccp */
			nom = 2250;
			break;
		case 2:		/* Vcc */
			nom = dbcool_supply_voltage(sc);
			break;
		case 3:		/* 5V */
			nom = 5000;
			break;
		case 4:		/* 12V */
			nom = 12000;
			break;
		default:
			nom = 0;
			break;
		}

	reg =  dbcool_readreg(sc, chipreg);
	reg *= nom;
	reg /= 0xc0;	/* values are scaled so 0xc0 == nominal voltage */
	node.sysctl_data = &reg;
	error = sysctl_lookup(SYSCTLFN_CALL(&node));

	if (error || newp == NULL)
		return error;

	/*
	 * We were asked to update the value, so scale it and sanity
	 * check before writing
	 */
	if (nom == 0)
		return EINVAL;
	newval =  *(int *)node.sysctl_data;
	newval *= 0xc0;
	newval /= nom;
	if (newval < 0 || newval > 0xff)
		return EINVAL;

	newreg = newval;
	dbcool_writereg(sc, chipreg, newreg);
	return 0;
}

static int
sysctl_dbcool_temp_limit(SYSCTLFN_ARGS)
{
	struct sysctlnode node;
	struct dbcool_softc *sc;
	int reg, error, newtemp;
	uint8_t chipreg;

	node = *rnode;
	sc = (struct dbcool_softc *)node.sysctl_data;
	chipreg = node.sysctl_num;

	/* If using offset mode, adjust, else treat as signed */
	if (sc->sc_temp_offset) {
		reg = dbcool_readreg(sc, chipreg);
		reg -= sc->sc_temp_offset;
	 } else
		reg = (int8_t)dbcool_readreg(sc, chipreg);

	node.sysctl_data = &reg;
	error = sysctl_lookup(SYSCTLFN_CALL(&node));

	if (error || newp == NULL)
		return error;

	/* We were asked to update the value - sanity check before writing */	
	newtemp = *(int *)node.sysctl_data + sc->sc_temp_offset;
	if (newtemp < 0 || newtemp > 0xff)
		return EINVAL;

	dbcool_writereg(sc, chipreg, newtemp);
	return 0;
}

static int
sysctl_dbcool_fan_limit(SYSCTLFN_ARGS)
{
	struct sysctlnode node;
	struct dbcool_softc *sc;
	int reg, error, newrpm, dividend;
	uint8_t chipreg;
	uint8_t newreg;

	node = *rnode;
	sc = (struct dbcool_softc *)node.sysctl_data;
	chipreg = node.sysctl_num;

	/* retrieve two-byte limit */
	reg = dbcool_read_rpm(sc, chipreg);

	node.sysctl_data = &reg;
	error = sysctl_lookup(SYSCTLFN_CALL(&node));

	if (error || newp == NULL)
		return error;

	/*
	 * We were asked to update the value.  Calculate the two-byte
	 * limit and validate it.  Due to the way fan RPM is calculated,
	 * the new value must be at least 83 RPM (331 RPM for ADM1030)!
	 * Allow a value of -1 or 0 to indicate no limit.
	 */
	newrpm = *(int *)node.sysctl_data;
	if (newrpm == 0 || newrpm == -1)
		newrpm = 0xffff;
	else {
		if (sc->sc_chip->flags & DBCFLAG_ADM1030)
			dividend = 11250 * 60;
		else
			dividend = 90000 * 60;
		newrpm = dividend / newrpm;
		if (newrpm & ~0xffff)
			return EINVAL;
	}

	/* Update the on-chip registers with new value */
	newreg = newrpm & 0xff;
	dbcool_writereg(sc, chipreg, newreg);
	newreg = (newrpm >> 8) & 0xff;
	dbcool_writereg(sc, chipreg + 1, newreg);
	return 0;
}

static int
sysctl_dbcool_vid(SYSCTLFN_ARGS)
{
	struct sysctlnode node;
	struct dbcool_softc *sc;
	int reg, error;
	uint8_t chipreg, newreg;

	node = *rnode;
	sc = (struct dbcool_softc *)node.sysctl_data;
	chipreg = node.sysctl_num;

	/* retrieve 5- or 6-bit value */
	newreg = dbcool_readreg(sc, chipreg);
	if ((sc->sc_chip->flags & DBCFLAG_HAS_VID_SEL) &&
	    (reg & 0x80))
		reg = newreg & 0x3f;
	else
		reg = newreg & 0x1f;

	node.sysctl_data = &reg;
	error = sysctl_lookup(SYSCTLFN_CALL(&node));

	if (error == 0 && newp != NULL)
		error = EINVAL;

	return error;
}

static int
sysctl_dbcool_thyst(SYSCTLFN_ARGS)
{
	struct sysctlnode node;
	struct dbcool_softc *sc;
	int reg, error;
	uint8_t chipreg;
	uint8_t newreg, newhyst;

	node = *rnode;
	sc = (struct dbcool_softc *)node.sysctl_data;
	chipreg = node.sysctl_num & 0x7f;

	/* retrieve 4-bit value */
	newreg = dbcool_readreg(sc, chipreg);
	if ((node.sysctl_num & 0x80) == 0)
		reg = newreg >> 4;
	else
		reg = newreg;
	reg = reg & 0x0f;

	node.sysctl_data = &reg;
	error = sysctl_lookup(SYSCTLFN_CALL(&node));

	if (error || newp == NULL)
		return error;

	/* We were asked to update the value - sanity check before writing */	
	newhyst = *(int *)node.sysctl_data;
	if (newhyst > 0x0f)
		return EINVAL;

	/* Insert new value into field and update register */
	if ((node.sysctl_num & 0x80) == 0) {
		newreg &= 0x0f;
		newreg |= (newhyst << 4);
	} else {
		newreg &= 0xf0;
		newreg |= newhyst;
	}
	dbcool_writereg(sc, chipreg, newreg);
	return 0;
}

#ifdef DBCOOL_DEBUG

/*
 * These routines can be used for debugging.  reg_select is used to
 * select any arbitrary register in the device.  reg_access is used
 * to read (and optionally update) the selected register.
 *
 * No attempt is made to validate the data passed.  If you use these
 * routines, you are assumed to know what you're doing!
 *
 * Caveat user
 */
static int
sysctl_dbcool_reg_select(SYSCTLFN_ARGS)
{
	struct sysctlnode node;
	struct dbcool_softc *sc;
	int reg, error;

	node = *rnode;
	sc = (struct dbcool_softc *)node.sysctl_data;
	
	reg = sc->sc_user_reg;
	node.sysctl_data = &reg;
	error = sysctl_lookup(SYSCTLFN_CALL(&node));

	if (error || newp == NULL)
		return error;

	sc->sc_user_reg = *(int *)node.sysctl_data;
	return 0;
}

static int
sysctl_dbcool_reg_access(SYSCTLFN_ARGS)
{
	struct sysctlnode node;
	struct dbcool_softc *sc;
	int reg, error;
	uint8_t chipreg;
	uint8_t newreg;

	node = *rnode;
	sc = (struct dbcool_softc *)node.sysctl_data;
	chipreg = sc->sc_user_reg;
	
	reg = dbcool_readreg(sc, chipreg);
	node.sysctl_data = &reg;
	error = sysctl_lookup(SYSCTLFN_CALL(&node));

	if (error || newp == NULL)
		return error;

	newreg = *(int *)node.sysctl_data;
	dbcool_writereg(sc, chipreg, newreg);
	return 0;
}
#endif /* DBCOOL_DEBUG */

/*
 * Encode the PWM controller number and attribute into the sysctl_num
 * so we can select the correct device register later.  (We could place
 * the register number itself here, but that would lose control over
 * the sequencing of the sysctl tree entries.)
 */
#define	DBC_PWM_SYSCTL(idx, seq)	( 0x10000 | (idx << 8) | seq)

void
dbcool_setup(device_t self)
{
	struct dbcool_softc *sc = device_private(self);
	const struct sysctlnode *me = NULL;
	const struct sysctlnode *me2 = NULL;
	struct sysctlnode *node = NULL;
	uint8_t cfg_val, cfg_reg;
	int (*helper)(SYSCTLFN_PROTO);
	int i, j, rw_flag;
	int name_index, sysctl_index, sysctl_num;
	int ret, error;
	char name[SYSCTL_NAMELEN];

	/*
	 * Some chips are capable of reporting an extended temperature range
	 * by default.  On these models, config register 5 bit 0 can be set
	 * to 1 for compatability with other chips that report 2s complement.
	 */
	if (sc->sc_chip->flags & DBCFLAG_ADT7466) {
		if (dbcool_readreg(sc, DBCOOL_ADT7466_CONFIG1) & 0x80)
			sc->sc_temp_offset = 64;
		else
			sc->sc_temp_offset = 0;
	} else if (sc->sc_chip->flags & DBCFLAG_TEMPOFFSET) {
		if (dbcool_readreg(sc, DBCOOL_CONFIG5_REG) &
			    DBCOOL_CFG5_TWOSCOMP)
			sc->sc_temp_offset = 0;
		else
			sc->sc_temp_offset = 64;
	} else
		sc->sc_temp_offset = 0;

	sc->sc_sme = sysmon_envsys_create();

	rw_flag = dbcool_islocked(sc)?CTLFLAG_READONLY:CTLFLAG_READWRITE;
	rw_flag |= CTLFLAG_OWNDESC;
	ret = sysctl_createv(NULL, 0, NULL, &me,
	       rw_flag,
	       CTLTYPE_NODE, device_xname(self), NULL,
	       NULL, 0, NULL, 0,
	       CTL_HW, CTL_CREATE, CTL_EOL);
	if (sc->sc_chip->flags & DBCFLAG_HAS_VID) {
		ret = sysctl_createv(NULL, 0, NULL,
			(const struct sysctlnode **)&node,
			CTLFLAG_READONLY, CTLTYPE_INT, "CPU VID bits", NULL,
			sysctl_dbcool_vid,
			0, sc, sizeof(int),
			CTL_HW, me->sysctl_num, DBCOOL_VID_REG, CTL_EOL);
		if (node != NULL)
			node->sysctl_data = sc;
	}

#ifdef DBCOOL_DEBUG
	ret = sysctl_createv(NULL, 0, NULL,
		(const struct sysctlnode **)&node,
		CTLFLAG_READWRITE, CTLTYPE_INT, "reg_select", NULL,
		sysctl_dbcool_reg_select,
		0, sc, sizeof(int),
		CTL_HW, me->sysctl_num, CTL_CREATE, CTL_EOL);
	if (node != NULL)
		node->sysctl_data = sc;

	ret = sysctl_createv(NULL, 0, NULL,
		(const struct sysctlnode **)&node,
		CTLFLAG_READWRITE, CTLTYPE_INT, "reg_access", NULL,
		sysctl_dbcool_reg_access,
		0, sc, sizeof(int),
		CTL_HW, me->sysctl_num, CTL_CREATE, CTL_EOL);
	if (node != NULL)
		node->sysctl_data = sc;
#endif /* DBCOOL_DEBUG */

	/* create sensors / controllers */
	for (i=0; sc->sc_chip->table[i].type != DBC_EOF; i++) {
		if (i >= DBCOOL_MAXSENSORS &&
		    sc->sc_chip->table[i].type != DBC_CTL) {
			aprint_normal_dev(self, "chip table too large!\n");
			break;
		}
		name_index = sc->sc_chip->table[i].name_index;
		switch (sc->sc_chip->table[i].type) {
		case DBC_TEMP:
			sc->sc_sensor[i].units = ENVSYS_STEMP;
			helper = sysctl_dbcool_temp_limit;
			break;
		case DBC_VOLT:
			sc->sc_sensor[i].units = ENVSYS_SVOLTS_DC;
			helper = sysctl_dbcool_volt_limit;
			break;
		case DBC_FAN:
			sc->sc_sensor[i].units = ENVSYS_SFANRPM;
			helper = sysctl_dbcool_fan_limit;
			break;
		case DBC_CTL:
			helper = NULL;
			/*
			 * Search for the corresponding temp sensor
			 * (temp sensors need to be created first!)
			 */
			sysctl_num = -1;
			for (j = 0; j < i; j++) {
				if (j > DBCOOL_MAXSENSORS ||
				    sc->sc_chip->table[j].type != DBC_TEMP)
					continue;
				if (sc->sc_chip->table[j].name_index !=
				    sc->sc_chip->table[i].name_index)
					continue;
				sysctl_num = sc->sc_sysctl_num[j];
				break;
			}
			if (sysctl_num == -1)
				break;
			sysctl_index = sc->sc_chip->table[i].sysctl_index;
			strlcpy(name, dbc_sysctl_table[sysctl_index].name,
			    sizeof(name));
			ret = sysctl_createv(NULL, 0, NULL,
				(const struct sysctlnode **)&node,
				rw_flag, CTLTYPE_INT, name,
				dbc_sysctl_table[sysctl_index].desc,
				dbc_sysctl_table[sysctl_index].helper,
				0, sc, sizeof(int),
				CTL_HW, me->sysctl_num, sysctl_num,
				CTL_CREATE, CTL_EOL);
			if (node != NULL)
				node->sysctl_data = sc;
			break;
		default:
			helper = NULL;
			aprint_error_dev(self, "sensor_table index %d has bad"
				" type %d\n", i, sc->sc_chip->table[i].type);
			break;
		}
		if  (sc->sc_chip->table[i].type == DBC_CTL)
			continue;

		strlcpy(sc->sc_sensor[i].desc,
		    dbc_sensor_names[name_index],
		    sizeof(sc->sc_sensor[i].desc));
		sc->sc_regs[i] = &sc->sc_chip->table[i].reg;

		sc->sc_sensor[i].flags |= ENVSYS_FMONCRITUNDER;
		if (sc->sc_chip->table[i].type != DBC_FAN)
			sc->sc_sensor[i].flags |= ENVSYS_FMONCRITOVER;

		if (sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[i]))
			goto out;

		/* create sysctl node for the sensor */
		ret = sysctl_createv(NULL, 0, NULL, &me2, CTLFLAG_READWRITE,
				CTLTYPE_NODE, sc->sc_sensor[i].desc, NULL,
				NULL, 0, NULL, 0,
				CTL_HW, me->sysctl_num, CTL_CREATE, CTL_EOL);
		if (me2 == NULL)
			continue;
		sc->sc_sysctl_num[i] = me2->sysctl_num;

		/* create sysctl node for the low limit */
		ret = sysctl_createv(NULL, 0, NULL,
				(const struct sysctlnode **)&node,
				CTLFLAG_READWRITE,
				CTLTYPE_INT, "low_lim", NULL, helper, 0, sc, 0,
				CTL_HW, me->sysctl_num, me2->sysctl_num,
				sc->sc_regs[i]->lo_lim_reg, CTL_EOL);
		if (node != NULL)
			node->sysctl_data = sc;

		/* Fans do not have a high limit */
		if (sc->sc_chip->table[i].type == DBC_FAN)
			continue;

		ret = sysctl_createv(NULL, 0, NULL,
				(const struct sysctlnode **)&node,
				CTLFLAG_READWRITE,
				CTLTYPE_INT, "hi_lim", NULL, helper, 0, sc, 0,
				CTL_HW, me->sysctl_num, me2->sysctl_num,
				sc->sc_regs[i]->hi_lim_reg, CTL_EOL);
		if (node != NULL)
			node->sysctl_data = sc;
	}

	/* If supported, create sysctl tree for fan PWM controllers */
	if (sc->sc_chip->power != NULL)
		for (i = 0; sc->sc_chip->power[i].desc != NULL; i++) {
			snprintf(name, sizeof(name), "fan_ctl_%d", i);
			ret = sysctl_createv(NULL, 0, NULL, &me2,
			       rw_flag,
			       CTLTYPE_NODE, name, NULL,
			       NULL, 0, NULL, 0,
			       CTL_HW, me->sysctl_num, CTL_CREATE, CTL_EOL);

			for (j = 0; j < DBC_PWM_LAST_PARAM; j++) {
				if (j == DBC_PWM_RANGE &&
				    (sc->sc_chip->flags & DBCFLAG_ADM1027) != 0)
					continue;
				if (j == DBC_PWM_MAX_DUTY &&
				    (sc->sc_chip->flags & DBCFLAG_HAS_MAXDUTY)
						== 0)
					continue;
				strlcpy(name, dbc_sysctl_table[j].name,
					sizeof(name));
				ret = sysctl_createv(NULL, 0, NULL,
					(const struct sysctlnode **)&node,
					rw_flag,
					(j == 0)?CTLTYPE_STRING:CTLTYPE_INT,
					name,
					dbc_sysctl_table[j].desc,
					dbc_sysctl_table[j].helper,
					0, sc, 
					( j == 0)?sizeof(dbcool_cur_behav):
						  sizeof(int),
					CTL_HW, me->sysctl_num, me2->sysctl_num,
					DBC_PWM_SYSCTL(i, j), CTL_EOL);
				if (node != NULL)
					node->sysctl_data = sc;
			}
		}
	/*
	 * Read and rewrite config register to activate device
	 */
	if (sc->sc_chip->flags & DBCFLAG_ADM1030)
		cfg_reg = DBCOOL_ADM1030_CFG1;
	else if (sc->sc_chip->flags & DBCFLAG_ADT7466)
		cfg_reg = DBCOOL_ADT7466_CONFIG1;
	else
		cfg_reg = DBCOOL_CONFIG1_REG;
	cfg_val = dbcool_readreg(sc, DBCOOL_CONFIG1_REG);
	if ((cfg_val & DBCOOL_CFG1_START) == 0) {
		cfg_val |= DBCOOL_CFG1_START;
		dbcool_writereg(sc, cfg_reg, cfg_val);
	}
	if (dbcool_islocked(sc))
		aprint_normal_dev(self, "configuration locked\n");

	sc->sc_sme->sme_name = device_xname(self);
	sc->sc_sme->sme_cookie = sc;
	sc->sc_sme->sme_refresh = dbcool_refresh;

	if ((error = sysmon_envsys_register(sc->sc_sme)) != 0) {
		aprint_error_dev(self,
		    "unable to register with sysmon (%d)\n", error);
		goto out;
	}
	
	return;

out:
	sysmon_envsys_destroy(sc->sc_sme);
}

static void
dbcool_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
{
	struct dbcool_softc *sc=sme->sme_cookie;
	int i;
	int cur, hi, low;
	struct reg_list *reg;
	
	i = edata->sensor;
	reg = sc->sc_regs[i];
	switch (edata->units)
	{
		case ENVSYS_STEMP:
			cur = dbcool_read_temp(sc, reg->val_reg, true);
			low = dbcool_read_temp(sc, reg->lo_lim_reg, false);
			hi  = dbcool_read_temp(sc, reg->hi_lim_reg, false);
			break;
		case ENVSYS_SVOLTS_DC:
			cur = dbcool_read_volt(sc, reg->val_reg, true);
			low = dbcool_read_volt(sc, reg->lo_lim_reg, false);
			hi  = dbcool_read_volt(sc, reg->hi_lim_reg, false);
			break;
		case ENVSYS_SFANRPM:
			cur = dbcool_read_rpm(sc, reg->val_reg);
			low = dbcool_read_rpm(sc, reg->lo_lim_reg);
			hi  = 1 << 16;
			break;
		default:
			edata->state = ENVSYS_SINVALID;
			return;
	}

	if (cur == 0 && edata->units != ENVSYS_SFANRPM)
		edata->state = ENVSYS_SINVALID;

	/* Make sure limits are sensible */
	else if (hi <= low)
		edata->state = ENVSYS_SVALID;

	/*
	 * If fan is "stalled" but has no low limit, treat
	 * it as though the fan is not installed.
	 */
	else if (edata->units == ENVSYS_SFANRPM && cur == 0 &&
			(low == 0 || low == -1))
		edata->state = ENVSYS_SINVALID;

	/*
	 * Compare current value against the limits
	 */
	else if (cur < low)
		edata->state = ENVSYS_SCRITUNDER;
	else if (cur > hi)
		edata->state = ENVSYS_SCRITOVER;
	else
		edata->state = ENVSYS_SVALID;

	edata->value_cur = cur;
}

int
dbcool_chip_ident(struct dbcool_softc *sc)
{
	/* verify this is a supported dbCool chip */
	uint8_t c_id, d_id, r_id;
	int i;

	c_id = dbcool_readreg(sc, DBCOOL_COMPANYID_REG);
	d_id = dbcool_readreg(sc, DBCOOL_DEVICEID_REG);
	r_id = dbcool_readreg(sc, DBCOOL_REVISION_REG);
    
	for (i = 0; chip_table[i].company != 0; i++)
		if ((c_id == chip_table[i].company) &&
		    (d_id == chip_table[i].device ||
				chip_table[i].device == 0xff) &&
		    (r_id == chip_table[i].rev ||
				chip_table[i].rev == 0xff)) {
			sc->sc_chip = &chip_table[i];
			return i;
		}

	aprint_verbose("dbcool_chip_ident: addr 0x%02x c_id 0x%02x d_id 0x%02x"
			" r_id 0x%02x: No match.\n", sc->sc_addr, c_id, d_id,
			r_id);

	return -1;
}  

File Added: src/sys/dev/i2c/dbcool_reg.h
/*	$NetBSD: dbcool_reg.h,v 1.1 2008/10/02 00:47:51 pgoyette Exp $ */

/*-
 * Copyright (c) 2008 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Paul Goyette
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``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 FOUNDATION OR CONTRIBUTORS
 * 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.
 */

/* 
 * a driver for the dbCool(tm) family of environmental controllers
 */

#ifndef DBCOOLREG_H
#define DBCOOLREG_H

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: dbcool_reg.h,v 1.1 2008/10/02 00:47:51 pgoyette Exp $");

#define DBCOOL_ADDRMASK		0x7f
#define	DBCOOL_ADDR		0x2e	/* Some chips have multiple addrs */

/* The dBCool chip family register set */

/* Not all registers are available on all chips! */
#define	DBCOOL_CONFIG5A_REG	0x04
#define	DBCOOL_CONFIG6_REG	0x10
#define	DBCOOL_CONFIG7_REG	0x11
#define	DBCOOL_INTERNAL_TRIP	0x13
#define	DBCOOL_EXTERNAL_TRIP	0x14
#define	DBCOOL_TEST		0x15
#define	DBCOOL_CHANNEL_MODE	0x16
#define	DBCOOL_INT_TRIP_FIXED	0x17
#define	DBCOOL_EXT_TRIP_FIXED	0x18
#define	DBCOOL_ANALOG_OUT	0x19
#define	DBCOOL_PECI1		0x1A
#define	DBCOOL_PECI2		0x1B
#define	DBCOOL_PECI3		0x1C
#define	DBCOOL_IMON		0x1D
#define	DBCOOL_VTT		0x1E
#define	DBCOOL_EXTRES		0x1F
#define	DBCOOL_OFFSET		0x1F
#define	DBCOOL_25VIN		0x20
#define	DBCOOL_CPU_VOLTAGE	0x21
#define	DBCOOL_SUPPLY_VOLTAGE	0x22
#define	DBCOOL_5VIN		0x23
#define	DBCOOL_12VIN		0x24
#define	DBCOOL_CPU_VOLTAGE2	0x25
#define	DBCOOL_REMOTE1_TEMP	0x25
#define	DBCOOL_LOCAL_TEMP	0x26
#define	DBCOOL_REMOTE2_TEMP	0x27
#define	DBCOOL_FAN1_TACH_LSB	0x28
#define	DBCOOL_FAN1_TACH_MSB	0x29
#define	DBCOOL_FAN2_TACH_LSB	0x2A
#define	DBCOOL_FAN2_TACH_MSB	0x2B
#define	DBCOOL_FAN3_TACH_LSB	0x2C
#define	DBCOOL_FAN3_TACH_MSB	0x2D
#define	DBCOOL_FAN4_TACH_LSB	0x2E
#define	DBCOOL_FAN4_TACH_MSB	0x2F
#define	DBCOOL_PWM1_CURDUTY	0x30
#define	DBCOOL_DAC0_START	0x30
#define	DBCOOL_PWM2_CURDUTY	0x31
#define	DBCOOL_DAC1_START	0x31
#define	DBCOOL_PWM3_CURDUTY	0x32
#define	DBCOOL_DAC0_MIN		0x32
#define	DBCOOL_PECI0		0x33
#define	DBCOOL_DAC1_MIN		0x33
#define	DBCOOL_PECI_LOWLIM	0x34
#define	DBCOOL_DAC0_MAX		0x34
#define	DBCOOL_PECI_HIGHLIM	0x35
#define	DBCOOL_DAC1_MAX		0x35
#define	DBCOOL_PECI_CFG1	0x36
#define	DBCOOL_DYNTMIN_CNTRL1	0x36
#define	DBCOOL_DYNTMIN_CNTRL2	0x37
#define	DBCOOL_PWM1_MAXDUTY	0x38
#define	DBCOOL_PWM2_MAXDUTY	0x39
#define	DBCOOL_PWM3_MAXDUTY	0x3A
/*
 * Note: ADT7490 reused the Device_ID register for PECI Tcontrol limit
 */
#define	DBCOOL_DEVICEID_REG	0x3D
#define	DBCOOL_PECI_TCRTL_LIM	0x3D
#define	DBCOOL_COMPANYID_REG	0x3E
#define	DBCOOL_REVISION_REG	0x3F
#define	DBCOOL_CONFIG1_REG	0x40
#define	DBCOOL_DAC0_OUT		0x40
#define	DBCOOL_ISR1_REG		0x41
#define	DBCOOL_DAC1_OUT		0x41
#define	DBCOOL_ISR2_REG		0x42
#define	DBCOOL_ISR3_REG		0x43
#define	DBCOOL_VID_REG		0x43
#define	DBCOOL_25VIN_LOWLIM	0x44
#define	DBCOOL_25VIN_HIGHLIM	0x45
#define	DBCOOL_VCCP_LOWLIM	0x46
#define	DBCOOL_VCCP_HIGHLIM	0x47
#define	DBCOOL_VIDB		0x47
#define	DBCOOL_VCC_LOWLIM	0x48
#define	DBCOOL_VCC_HIGHLIM	0x49
#define	DBCOOL_VID4		0x49
#define	DBCOOL_5VIN_LOWLIM	0x4A
#define	DBCOOL_5VIN_HIGHLIM	0x4B
#define	DBCOOL_12VIN_LOWLIM	0x4C
#define	DBCOOL_12VIN_HIGHLIM	0x4D
#define	DBCOOL_REMOTE1_LOWLIM	0x4E
#define	DBCOOL_REMOTE1_HIGHLIM	0x4F
#define	DBCOOL_LOCAL_LOWLIM	0x50
#define	DBCOOL_LOCAL_HIGHLIM	0x51
#define	DBCOOL_REMOTE2_LOWLIM	0x52
#define	DBCOOL_REMOTE2_HIGHLIM	0x53
#define	DBCOOL_TACH1_MIN_LSB	0x54
#define	DBCOOL_TACH1_MIN_MSB	0x55
#define	DBCOOL_TACH2_MIN_LSB	0x56
#define	DBCOOL_TACH2_MIN_MSB	0x57
#define	DBCOOL_TACH3_MIN_LSB	0x58
#define	DBCOOL_TACH3_MIN_MSB	0x59
#define	DBCOOL_TACH4_MIN_LSB	0x5A
#define	DBCOOL_TACH4_MIN_MSB	0x5B
#define	DBCOOL_PWM1_CTL		0x5C
#define	DBCOOL_PWM2_CTL		0x5D
#define	DBCOOL_PWM3_CTL		0x5E
#define	DBCOOL_PWM1_TRANGE	0x5F
#define	DBCOOL_PWM2_TRANGE	0x60
#define	DBCOOL_PWM3_TRANGE	0x61
#define	DBCOOL_ENH_ACOUST_1	0x62
#define	DBCOOL_ENH_ACOUST_2	0x63
#define	DBCOOL_PWM1_MINDUTY	0x64
#define	DBCOOL_PWM2_MINDUTY	0x65
#define	DBCOOL_PWM3_MINDUTY	0x66
#define	DBCOOL_REMOTE1_TMIN	0x67
#define	DBCOOL_LOCAL_TMIN	0x68
#define	DBCOOL_REMOTE2_TMIN	0x69
#define	DBCOOL_REMOTE1_TTHRESH	0x6A
#define	DBCOOL_LOCAL_TTHRESH	0x6B
#define	DBCOOL_REMOTE2_TTHRESH	0x6C
#define	DBCOOL_R1_LCL_TMIN_HYST	0x6D
#define	DBCOOL_R2_TMIN_HYST	0x6E
#define	DBCOOL_XNOR_ENABLE	0x6F
#define	DBCOOL_REMOTE1_TEMPOFF	0x70
#define	DBCOOL_LOCAL_TEMPOFF	0x71
#define	DBCOOL_REMOTE2_TEMPOFF	0x72
#define	DBCOOL_CONFIG2_REG	0x73
#define	DBCOOL_IMASK1_REG	0x74
#define	DBCOOL_IMASK2_REG	0x75
#define	DBCOOL_EXTRES1_REG	0x76
#define	DBCOOL_EXTRES2_REG	0x77
#define	DBCOOL_CONFIG3_REG	0x78
#define	DBCOOL_THERM_TIMERSTATUS_REG	0x79
#define	DBCOOL_THERM_TIMERLIMIT_REG	0x7A
#define	DBCOOL_TACHPULSE_REG	0x7B
#define	DBCOOL_CONFIG5_REG	0x7C
#define	DBCOOL_CONFIG4_REG	0x7D
#define	DBCOOL_TEST1_REG	0x7E
#define	DBCOOL_TEST2_REG	0x7F
#define	DBCOOL_GPIO_CONFIG	0x80
#define	DBCOOL_ISR4_REG		0x81
#define	DBCOOL_IMASK3_REG	0x82
#define	DBCOOL_IMASK4_REG	0x83
#define	DBCOOL_VTT_LOWLIM	0x84
#define	DBCOOL_IMON_LOWLIM	0x85
#define	DBCOOL_VTT_HIGHLIM	0x86
#define	DBCOOL_IMON_HIGHLIM	0x87
#define	DBCOOL_PECI_CFG2	0x88
#define	DBCOOL_TEST3_REG	0x89
#define	DBCOOL_PECI_OP_PT	0x8A
#define	DBCOOL_REMOTE1_OP_PT	0x8B
#define	DBCOOL_LOCAL_OP_PT	0x8C
#define	DBCOOL_REMOTE2_OP_PT	0x8D
#define	DBCOOL_DYNTMIN_CTL1	0x8E
#define	DBCOOL_DYNTMIN_CTL2	0x8F
#define	DBCOOL_DYNTMIN_CTL3	0x90
#define	DBCOOL_PECI0_TEMPOFF	0x94
#define	DBCOOL_PECI1_TEMPOFF	0x95
#define	DBCOOL_PECI2_TEMPOFF	0x96
#define	DBCOOL_PECI3_TEMPOFF	0x97
#define	DBCOOL_NO_REG		0xff

/* Config register bit definitions */
#define	DBCOOL_CFG1_START	0x01
#define	DBCOOL_CFG1_LOCK	0x02
#define	DBCOOL_CFG1_RDY		0x04
#define	DBCOOL_CFG1_FSPD	0x08
#define	DBCOOL_CFG1_VxI		0x10
#define	DBCOOL_CFG1_RESET	0x10
#define	DBCOOL_CFG1_FSPDIS	0x20
#define	DBCOOL_CFG1_12VVID4_SEL	0x20
#define	DBCOOL_CFG1_TODIS	0x40
#define	DBCOOL_CFG1_Vcc		0x80
#define	DBCOOL_CFG1_RESET_LATCH	0x80
#define	DBCOOL_CFG2_AIN1	0x01
#define	DBCOOL_CFG2_AIN2	0x02
#define	DBCOOL_CFG2_AIN3	0x04
#define	DBCOOL_CFG2_AIN4	0x08
#define	DBCOOL_CFG2_AVG		0x10
#define	DBCOOL_CFG2_ATTN	0x20
#define	DBCOOL_CFG2_CONV	0x40
#define	DBCOOL_CFG2_SHDN	0x80
#define	DBCOOL_CFG3_ALERT	0x01
#define	DBCOOL_CFG3_THERM	0x02
#define	DBCOOL_CFG3_BOOST	0x04
#define	DBCOOL_CFG3_FAST	0x08
#define	DBCOOL_CFG3_DC1		0x10
#define	DBCOOL_CFG3_DC2		0x20
#define	DBCOOL_CFG3_DC3		0x40
#define	DBCOOL_CFG3_DC4		0x80

#define	DBCOOL_CFG4_PIN9FUNC	0x03
#define	DBCOOL_CFG4_AINL	0x0C
#define	DBCOOL_CFG4_BYPASS_ATTN	0x20

#define	DBCOOL_CFG5_TWOSCOMP	0x01
#define	DBCOOL_CFG5_FREQ	0x02
#define	DBCOOL_CFG5_GPIOD	0x04
#define	DBCOOL_CFG5_GPIOP	0x08

#define	DBCOOL_CFG6_SLOW_REM1	0x01
#define	DBCOOL_CFG6_SLOW_LOCAL	0x02
#define	DBCOOL_CFG6_SLOW_REM2	0x04
#define	DBCOOL_CFG6_THERM_MAN	0x08
#define DBCOOL_CFG6_VCCP_LOW	0x40
#define	DBCOOL_CFG6_EXTRASLOW	0x80

#define	DBCOOL_CFG7_DIS_THERM_HYST	0x10

/*
 * The ADT7466 is an orphan stepchild in the dbCool family
 */
#define	DBCOOL_ADT7466_CONFIG1		0x00
#define	DBCOOL_ADT7466_CONFIG2		0x01
#define	DBCOOL_ADT7466_CONFIG3		0x02
#define	DBCOOL_ADT7466_CONFIG4		0x03
#define	DBCOOL_ADT7466_CONFIG5		0x04
#define	DBCOOL_ADT7466_AFC1		0x05
#define	DBCOOL_ADT7466_AFC2		0x06
#define	DBCOOL_ADT7466_REM_TEMP_LSB	0x08
#define	DBCOOL_ADT7466_LCL_TEMP_LSB	0x09
#define	DBCOOL_ADT7466_AIN1		0x0A
#define	DBCOOL_ADT7466_AIN2		0x0B
#define	DBCOOL_ADT7466_VCC		0x0C
#define	DBCOOL_ADT7466_REM_TEMP_MSB	0x0D
#define	DBCOOL_ADT7466_LCL_TEMP_MSB	0x0E
#define	DBCOOL_ADT7466_PROCHOT		0x0F
#define	DBCOOL_ADT7466_INTRPT1		0x10
#define	DBCOOL_ADT7466_INTRPT2		0x11
#define	DBCOOL_ADT7466_INTMSK1		0x12
#define	DBCOOL_ADT7466_INTMSK2		0x13
#define	DBCOOL_ADT7466_AIN1_LOLIM	0x14
#define	DBCOOL_ADT7466_AIN1_HILIM	0x15
#define	DBCOOL_ADT7466_AIN2_LOLIM	0x16
#define	DBCOOL_ADT7466_AIN2_HILIM	0x17
#define	DBCOOL_ADT7466_VCC_LOLIM	0x18
#define	DBCOOL_ADT7466_VCC_HILIM	0x19
#define	DBCOOL_ADT7466_REM_TEMP_LOLIM	0x1A
#define	DBCOOL_ADT7466_REM_TEMP_HILIM	0x1B
#define	DBCOOL_ADT7466_LCL_TEMP_LOLIM	0x1C
#define	DBCOOL_ADT7466_LCL_TEMP_HILIM	0x1D
#define	DBCOOL_ADT7466_PROCHOT_LIM	0x1E
#define	DBCOOL_ADT7466_AIN1_THERM	0x1F
#define	DBCOOL_ADT7466_AIN2_THREM	0x20
#define	DBCOOL_ADT7466_REM_THERM	0x21
#define	DBCOOL_ADT7466_LCL_THERM	0x22
#define	DBCOOL_ADT7466_AIN1_OFFSET	0x24
#define	DBCOOL_ADT7466_AIN2_OFFSET	0x25
#define	DBCOOL_ADT7466_REM_OFFSET	0x26
#define	DBCOOL_ADT7466_LCL_OFFSET	0x27
#define	DBCOOL_ADT7466_AIN1_TMIN	0x28
#define	DBCOOL_ADT7466_AIN2_TMIN	0x29
#define	DBCOOL_ADT7466_REM_TMIN		0x2A
#define	DBCOOL_ADT7466_LCL_TMIN		0x2B
#define	DBCOOL_ADT7466_AIN_RANGES	0x2C
#define	DBCOOL_ADT7466_LCL_REM_RANGES	0x2D
#define	DBCOOL_ADT7466_AIN_HYSTS	0x2E
#define	DBCOOL_ADT7466_LCL_REM_HYSTS	0x2F
#define	DBCOOL_ADT7466_FANA_STARTV	0x30
#define	DBCOOL_ADT7466_FANB_STARTV	0x31
#define	DBCOOL_ADT7466_FANA_MINV	0x32
#define	DBCOOL_ADT7466_FANB_MINV	0x33
#define	DBCOOL_ADT7466_FANA_MAXRPM_MSB	0x34
#define	DBCOOL_ADT7466_FANB_MAXRPM_MSB	0x35
#define	DBCOOL_ADT7466_ENH_ACOUSTICS	0x36
#define	DBCOOL_ADT7466_FAULT_INCR	0x37
#define	DBCOOL_ADT7466_TIMEOUT		0x38
#define	DBCOOL_ADT7466_PULSES		0x39
#define	DBCOOL_ADT7466_DRIVE1		0x40
#define	DBCOOL_ADT7466_DRIVE2		0x41
#define	DBCOOL_ADT7466_XOR_TEST		0x42
#define	DBCOOL_ADT7466_FANA_LSB		0x48
#define	DBCOOL_ADT7466_FANA_MSB		0x49
#define	DBCOOL_ADT7466_FANB_LSB		0x4A
#define	DBCOOL_ADT7466_FANB_MSB		0x4B
#define	DBCOOL_ADT7466_FANA_LOLIM_LSB	0x4C
#define	DBCOOL_ADT7466_FANA_LOLIM_MSB	0x4D
#define	DBCOOL_ADT7466_FANB_LOLIM_LSB	0x4E
#define	DBCOOL_ADT7466_FANB_LOLIM_MSB	0x4F

#define	DBCOOL_ADT7466_CFG1_Vcc		0x40
#define	DBCOOL_ADT7466_CFG2_SHDN	0x40

/*
 * Even though it's not really a member of the dbCool family, we also
 * support the ADM1030 chip.  It has a different register set.
 */
#define	DBCOOL_ADM1030_CFG1		0x00
#define	DBCOOL_ADM1030_CFG2		0x01
#define	DBCOOL_ADM1030_STATUS1		0x02
#define	DBCOOL_ADM1030_STATUS2		0x03
#define	DBCOOL_ADM1030_TEMP_EXTRES	0x06
#define	DBCOOL_ADM1030_TEST_REG		0x07
#define	DBCOOL_ADM1030_FAN_TACH		0x08
#define	DBCOOL_ADM1030_L_TEMP		0x0A
#define	DBCOOL_ADM1030_R_TEMP		0x0B
#define	DBCOOL_ADM1030_L_OFFSET		0x0D
#define	DBCOOL_ADM1030_R_OFFSET		0x0E
#define	DBCOOL_ADM1030_FAN_LO_LIM	0x10
#define	DBCOOL_ADM1030_L_HI_LIM		0x14
#define	DBCOOL_ADM1030_L_LO_LIM		0x15
#define	DBCOOL_ADM1030_L_TTHRESH	0x16
#define	DBCOOL_ADM1030_R_HI_LIM		0x18
#define	DBCOOL_ADM1030_R_LO_LIM		0x19
#define	DBCOOL_ADM1030_R_TTHRESH	0x1A
#define	DBCOOL_ADM1030_FAN_CHAR		0x20
#define	DBCOOL_ADM1030_FAN_SPEED_CFG	0x22
#define	DBCOOL_ADM1030_FAN_FILTER	0x23
#define	DBCOOL_ADM1030_L_TMIN		0x24
#define	DBCOOL_ADM1030_R_TMIN		0x25
#define	DBCOOL_ADM1030_DEVICEID		DBCOOL_DEVICEID_REG
#define	DBCOOL_ADM1030_COMPANYID	DBCOOL_COMPANYID_REG
#define	DBCOOL_ADM1030_REVISION		DBCOOL_REVISION_REG

/*      
 * Macros to locate limit registers for the various sensor types
 */     
#define DBCOOL_VOLT_LOLIM(reg) ((reg - DBCOOL_25VIN) * 2 + DBCOOL_25VIN_LOWLIM)
#define DBCOOL_VOLT_HILIM(reg) (DBCOOL_VOLT_LOLIM(reg) + 1)
#define DBCOOL_TEMP_LOLIM(reg)		\
		((reg - DBCOOL_LOCAL_TEMP) * 2 + DBCOOL_LOCAL_LOWLIM)
#define DBCOOL_TEMP_HILIM(reg) (DBCOOL_TEMP_LOLIM(reg) + 1)
#define DBCOOL_TACH_LOLIM(reg)		\
		(reg - DBCOOL_FAN1_TACH_LSB + DBCOOL_TACH1_MIN_LSB)
#define	ADM1030_TEMP_HILIM(reg)		\
		((reg - DBCOOL_ADM1030_L_TEMP) * 3 + DBCOOL_ADM1030_L_HI_LIM)
#define	ADM1030_TEMP_LOLIM(reg)		\
		((reg - DBCOOL_ADM1030_L_TEMP) * 3 + DBCOOL_ADM1030_L_LO_LIM)
#define ADT7466_LIM_OFFSET(reg)         \
		((reg - DBCOOL_AIN1) * 2 + DBCOOL_AIN1_LOWLIM)
#define ADT7466_FAN_LIM_OFFSET(reg)     \
		(reg - DBCOOL_FANA_LSB + DBCOOL_FANA_LOWLIM_LSB)


/* Company and Device ID values */
#define	DBCOOL_COMPANYID	0x41

#define	ADM1027_DEVICEID	0x27
#define	ADM1030_DEVICEID	0x30
#define	ADT7463_DEVICEID	0x27
#define	ADT7466_DEVICEID	0x66
#define	ADT7467_DEVICEID	0x67
#define	ADT7468_DEVICEID	0x68
#define	ADT7473_DEVICEID	0x73
#define	ADT7475_DEVICEID	0x75
#define	ADT7476_DEVICEID	0x76

#define	ADM1027_REV_ID		0x60
#define	ADT7463_REV_ID1		0x62
#define	ADT7463_REV_ID2		0x6A
#define	ADT7467_REV_ID1		0x71
#define	ADT7467_REV_ID2		0x72
#define	ADT7473_REV_ID		0x68
#define	ADT7473_1_REV_ID	0x69

#endif /* def DBCOOLREG_H */

File Added: src/sys/dev/i2c/dbcool_var.h
/*	$NetBSD: dbcool_var.h,v 1.1 2008/10/02 00:47:51 pgoyette Exp $ */

/*-
 * Copyright (c) 2008 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Paul Goyette
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``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 FOUNDATION OR CONTRIBUTORS
 * 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.
 */

/* 
 * A driver for dbCool(tm) family of environmental controllers
 */

#ifndef DBCOOLVAR_H
#define DBCOOLVAR_H

#define DBCOOL_DEBUG
/*
*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: dbcool_var.h,v 1.1 2008/10/02 00:47:51 pgoyette Exp $");

#include <dev/i2c/i2cvar.h>

#include <dev/sysmon/sysmonvar.h>
#include "sysmon_envsys.h"

#include <dev/i2c/dbcool_reg.h>

enum dbc_pwm_params {
	DBC_PWM_BEHAVIOR = 0,
	DBC_PWM_RANGE,
	DBC_PWM_MIN_DUTY,
	DBC_PWM_MAX_DUTY,
	DBC_PWM_CUR_DUTY,
	DBC_PWM_LAST_PARAM
};

enum dbc_sensor_type {
	DBC_CTL = 0,
	DBC_TEMP,
	DBC_VOLT,
	DBC_FAN,
	DBC_EOF
};

#define	DBCFLAG_TEMPOFFSET	0x0001
#define	DBCFLAG_HAS_MAXDUTY	0x0002
#define	DBCFLAG_HAS_SHDN	0x0004
#define	DBCFLAG_MULTI_VCC	0x0008
#define	DBCFLAG_4BIT_VER	0x0010
#define	DBCFLAG_HAS_VID		0x0020
#define	DBCFLAG_HAS_VID_SEL	0x0040
#define	DBCFLAG_ADM1027		0x1000
#define	DBCFLAG_ADM1030		0x2000
#define	DBCFLAG_ADT7466		0x4000

/* Maximum sensors for any dbCool device */
#define DBCOOL_MAXSENSORS       15

struct reg_list {
	uint8_t val_reg;
	uint8_t hi_lim_reg;
	uint8_t lo_lim_reg;
};

struct dbcool_sensor {
	enum dbc_sensor_type type;
	struct reg_list	reg;
	int name_index;
	int sysctl_index;
};

/*
 * The members of dbcool_power_control need to stay in the same order
 * as the enum dbc_pwm_params above
 */
struct dbcool_power_control {
	uint8_t behavior;
	uint8_t range;
	uint8_t min;
	uint8_t max;
	uint8_t cur;
	const char *desc;
};

struct chip_id;

struct dbcool_softc {
	struct device *parent;
	i2c_tag_t sc_tag;
	i2c_addr_t sc_addr;
	struct chip_id *sc_chip;
	struct sysmon_envsys *sc_sme;
	envsys_data_t sc_sensor[DBCOOL_MAXSENSORS];
	int sc_sysctl_num[DBCOOL_MAXSENSORS];
	struct reg_list *sc_regs[DBCOOL_MAXSENSORS];
	uint8_t sc_suspend;
	uint8_t sc_temp_offset;
#ifdef DBCOOL_DEBUG
	uint8_t sc_user_reg;
#endif
};

struct chip_id {
	uint8_t company;
	uint8_t device;
	uint8_t rev;
	struct dbcool_sensor *table;
	struct dbcool_power_control *power;
	int flags;
	int rpm_dividend;
	const char *name;
};

/*
 * Expose some routines for the macppc's ki2c match/attach routines
 */
uint8_t dbcool_readreg(struct dbcool_softc *, uint8_t);
void dbcool_writereg(struct dbcool_softc *, uint8_t, uint8_t);
void dbcool_setup(device_t); 
int dbcool_chip_ident(struct dbcool_softc *);
bool dbcool_pmf_suspend(device_t PMF_FN_PROTO);
bool dbcool_pmf_resume(device_t PMF_FN_PROTO);

#endif	/* def DBCOOLVAR_H */

cvs diff -r1.18 -r1.19 src/sys/dev/i2c/files.i2c (expand / switch to unified diff)

--- src/sys/dev/i2c/files.i2c 2008/09/11 20:48:50 1.18
+++ src/sys/dev/i2c/files.i2c 2008/10/02 00:47:51 1.19
@@ -1,14 +1,14 @@ @@ -1,14 +1,14 @@
1# $NetBSD: files.i2c,v 1.18 2008/09/11 20:48:50 pgoyette Exp $ 1# $NetBSD: files.i2c,v 1.19 2008/10/02 00:47:51 pgoyette Exp $
2 2
3defflag opt_i2cbus.h I2C_SCAN 3defflag opt_i2cbus.h I2C_SCAN
4define i2cbus { } 4define i2cbus { }
5define i2cexec 5define i2cexec
6 6
7device iic { [addr = -1], [size = -1] } 7device iic { [addr = -1], [size = -1] }
8attach iic at i2cbus 8attach iic at i2cbus
9file dev/i2c/i2c.c iic | i2cbus 9file dev/i2c/i2c.c iic | i2cbus
10file dev/i2c/i2c_exec.c iic | i2cbus | i2cexec 10file dev/i2c/i2c_exec.c iic | i2cbus | i2cexec
11 11
12# Common module for bit-bang'ing an I2C bus 12# Common module for bit-bang'ing an I2C bus
13define i2c_bitbang 13define i2c_bitbang
14file dev/i2c/i2c_bitbang.c i2c_bitbang 14file dev/i2c/i2c_bitbang.c i2c_bitbang
@@ -61,26 +61,32 @@ file dev/i2c/x1226.c xrtc @@ -61,26 +61,32 @@ file dev/i2c/x1226.c xrtc
61 61
62# Analog Devices ADT 7463 thermal monitor / fan controller 62# Analog Devices ADT 7463 thermal monitor / fan controller
63define adt7463c {} 63define adt7463c {}
64device adt7463c: sysmon_envsys 64device adt7463c: sysmon_envsys
65attach adt7463c at iic 65attach adt7463c at iic
66file dev/i2c/adt7463.c adt7463c 66file dev/i2c/adt7463.c adt7463c
67 67
68# Analog Devices ADT 7467 thermal monitor / fan controller 68# Analog Devices ADT 7467 thermal monitor / fan controller
69define adt7467c {} 69define adt7467c {}
70device adt7467c: sysmon_envsys 70device adt7467c: sysmon_envsys
71attach adt7467c at iic 71attach adt7467c at iic
72file dev/i2c/adt7467.c adt7467c 72file dev/i2c/adt7467.c adt7467c
73 73
 74# Analog Devices dBCool family of thermal monitors / fan controllers
 75define dbcool {}
 76device dbcool: sysmon_envsys
 77attach dbcool at iic
 78file dev/i2c/dbcool.c dbcool
 79
74# Analog Devices ADM 1030 thermal monitor / fan controller 80# Analog Devices ADM 1030 thermal monitor / fan controller
75define adm1030c {} 81define adm1030c {}
76device adm1030c: sysmon_envsys 82device adm1030c: sysmon_envsys
77attach adm1030c at iic 83attach adm1030c at iic
78file dev/i2c/adm1030.c adm1030c 84file dev/i2c/adm1030.c adm1030c
79 85
80# RICOH RS5C372[AB] Real Time Clock 86# RICOH RS5C372[AB] Real Time Clock
81device rs5c372rtc 87device rs5c372rtc
82attach rs5c372rtc at iic 88attach rs5c372rtc at iic
83file dev/i2c/rs5c372.c rs5c372rtc 89file dev/i2c/rs5c372.c rs5c372rtc
84 90
85# RICOH R2025S/D Real Time Clock 91# RICOH R2025S/D Real Time Clock
86device r2025rtc 92device r2025rtc