/*      $NetBSD: dbcool.c,v 1.65 2024/05/18 00:02:04 thorpej 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
*      http://www.onsemi.com/pub/Collateral/ADT7490-D.PDF
*      http://www.smsc.com/media/Downloads_Public/Data_Sheets/6d103s.pdf
*
* (URLs are correct as of October 5, 2008)
*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: dbcool.c,v 1.65 2024/05/18 00:02:04 thorpej Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/sysctl.h>
#include <sys/module.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 bool 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, int, bool);

/* Sensor get/set limit functions */
static void dbcool_get_limits(struct sysmon_envsys *, envsys_data_t *,
                             sysmon_envsys_lim_t *, uint32_t *);
static void dbcool_get_temp_limits(struct dbcool_softc *, int,
                                  sysmon_envsys_lim_t *, uint32_t *);
static void dbcool_get_volt_limits(struct dbcool_softc *, int,
                                  sysmon_envsys_lim_t *, uint32_t *);
static void dbcool_get_fan_limits(struct dbcool_softc *, int,
                                 sysmon_envsys_lim_t *, uint32_t *);

static void dbcool_set_limits(struct sysmon_envsys *, envsys_data_t *,
                             sysmon_envsys_lim_t *, uint32_t *);
static void dbcool_set_temp_limits(struct dbcool_softc *, int,
                                  sysmon_envsys_lim_t *, uint32_t *);
static void dbcool_set_volt_limits(struct dbcool_softc *, int,
                                  sysmon_envsys_lim_t *, uint32_t *);
static void dbcool_set_fan_limits(struct dbcool_softc *, int,
                                 sysmon_envsys_lim_t *, uint32_t *);

/* SYSCTL Helpers */
SYSCTL_SETUP_PROTO(sysctl_dbcoolsetup);
static int sysctl_dbcool_temp(SYSCTLFN_PROTO);
static int sysctl_adm1030_temp(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_slope(SYSCTLFN_PROTO);
static int sysctl_dbcool_thyst(SYSCTLFN_PROTO);

/* Set-up subroutines */
static void dbcool_setup_controllers(struct dbcool_softc *);
static int  dbcool_setup_sensors(struct dbcool_softc *);
static int  dbcool_attach_sensor(struct dbcool_softc *, int);
static int  dbcool_attach_temp_control(struct dbcool_softc *, int,
       struct chip_id *);

#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_sysctl_info {
       const char *name;
       const char *desc;
       bool lockable;
       int (*helper)(SYSCTLFN_PROTO);
};

static struct dbc_sysctl_info dbc_sysctl_table[] = {
       /*
        * The first several entries must remain in the same order as the
        * corresponding entries in enum dbc_pwm_params
        */
       { "behavior",           "operating behavior and temp selector",
               true, sysctl_dbcool_behavior },
       { "min_duty",           "minimum fan controller PWM duty cycle",
               true, sysctl_dbcool_duty },
       { "max_duty",           "maximum fan controller PWM duty cycle",
               true, sysctl_dbcool_duty },
       { "cur_duty",           "current fan controller PWM duty cycle",
               false, sysctl_dbcool_duty },

       /*
        * The rest of these should be in the order in which they
        * are to be stored in the sysctl tree;  the table index is
        * used as the high-order bits of the sysctl_num to maintain
        * the sequence.
        *
        * If you rearrange the order of these items, be sure to
        * update the sysctl_index in the XXX_sensor_table[] for
        * the various chips!
        */
       { "Trange",             "temp slope/range to reach 100% duty cycle",
               true, sysctl_dbcool_slope },
       { "Tmin",               "temp at which to start fan controller",
               true, sysctl_dbcool_temp },
       { "Ttherm",             "temp at which THERM is asserted",
               true, sysctl_dbcool_temp },
       { "Thyst",              "temp hysteresis for stopping fan controller",
               true, sysctl_dbcool_thyst },
       { "Tmin",               "temp at which to start fan controller",
               true, sysctl_adm1030_temp },
       { "Trange",             "temp slope/range to reach 100% duty cycle",
               true, sysctl_adm1030_trange },
};

static const char *dbc_sensor_names[] = {
       "l_temp",  "r1_temp", "r2_temp", "Vccp",   "Vcc",    "fan1",
       "fan2",    "fan3",    "fan4",    "AIN1",   "AIN2",   "V2dot5",
       "V5",      "V12",     "Vtt",     "Imon",   "VID"
};

/*
* Following table derived from product data-sheets
*/
static int64_t nominal_voltages[] = {
       -1,             /* Vcc can be either 3.3 or 5.0V
                          at 3/4 scale                  */
        2249939,       /* Vccp         2.25V 3/4 scale  */
        2497436,       /* 2.5VIN       2.5V  3/4 scale  */
        5002466,       /* 5VIN         5V    3/4 scale  */
       12000000,       /* 12VIN       12V    3/4 scale  */
        1690809,       /* Vtt, Imon    2.25V full scale */
        1689600,       /* AIN1, AIN2   2.25V full scale */
              0
};

/*
* Sensor-type, { val-reg, hilim-reg, lolim-reg}, name-idx, sysctl-table-idx,
*      nom-voltage-index
*/
struct dbcool_sensor ADT7490_sensor_table[] = {
       { DBC_TEMP, {   DBCOOL_LOCAL_TEMP,
                       DBCOOL_LOCAL_HIGHLIM,
                       DBCOOL_LOCAL_LOWLIM },          0, 0, 0 },
       { DBC_TEMP, {   DBCOOL_REMOTE1_TEMP,
                       DBCOOL_REMOTE1_HIGHLIM,
                       DBCOOL_REMOTE1_LOWLIM },        1, 0, 0 },
       { DBC_TEMP, {   DBCOOL_REMOTE2_TEMP,
                       DBCOOL_REMOTE2_HIGHLIM,
                       DBCOOL_REMOTE2_LOWLIM },        2, 0, 0 },
       { DBC_VOLT, {   DBCOOL_VCCP,
                       DBCOOL_VCCP_HIGHLIM,
                       DBCOOL_VCCP_LOWLIM },           3, 0, 1 },
       { DBC_VOLT, {   DBCOOL_VCC,
                       DBCOOL_VCC_HIGHLIM,
                       DBCOOL_VCC_LOWLIM },            4, 0, 0 },
       { DBC_VOLT, {   DBCOOL_25VIN,
                       DBCOOL_25VIN_HIGHLIM,
                       DBCOOL_25VIN_LOWLIM },          11, 0, 2 },
       { DBC_VOLT, {   DBCOOL_5VIN,
                       DBCOOL_5VIN_HIGHLIM,
                       DBCOOL_5VIN_LOWLIM },           12, 0, 3 },
       { DBC_VOLT, {   DBCOOL_12VIN,
                       DBCOOL_12VIN_HIGHLIM,
                       DBCOOL_12VIN_LOWLIM },          13, 0, 4 },
       { DBC_VOLT, {   DBCOOL_VTT,
                       DBCOOL_VTT_HIGHLIM,
                       DBCOOL_VTT_LOWLIM },            14, 0, 5 },
       { DBC_VOLT, {   DBCOOL_IMON,
                       DBCOOL_IMON_HIGHLIM,
                       DBCOOL_IMON_LOWLIM },           15, 0, 5 },
       { DBC_FAN,  {   DBCOOL_FAN1_TACH_LSB,
                       DBCOOL_NO_REG,
                       DBCOOL_TACH1_MIN_LSB },         5, 0, 0 },
       { DBC_FAN,  {   DBCOOL_FAN2_TACH_LSB,
                       DBCOOL_NO_REG,
                       DBCOOL_TACH2_MIN_LSB },         6, 0, 0 },
       { DBC_FAN,  {   DBCOOL_FAN3_TACH_LSB,
                       DBCOOL_NO_REG,
                       DBCOOL_TACH3_MIN_LSB },         7, 0, 0 },
       { DBC_FAN,  {   DBCOOL_FAN4_TACH_LSB,
                       DBCOOL_NO_REG,
                       DBCOOL_TACH4_MIN_LSB },         8, 0, 0 },
       { DBC_VID,  {   DBCOOL_VID_REG,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                16, 0, 0 },
       { DBC_CTL,  {   DBCOOL_LOCAL_TMIN,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                0, 5, 0 },
       { DBC_CTL,  {   DBCOOL_LOCAL_TTHRESH,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                0, 6, 0 },
       { DBC_CTL,  {   DBCOOL_R1_LCL_TMIN_HYST | 0x80,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                0, 7, 0 },
       { DBC_CTL,  {   DBCOOL_REMOTE1_TMIN,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                1, 5, 0 },
       { DBC_CTL,  {   DBCOOL_REMOTE1_TTHRESH,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                1, 6, 0 },
       { DBC_CTL,  {   DBCOOL_R1_LCL_TMIN_HYST,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                1, 7, 0 },
       { DBC_CTL,  {   DBCOOL_REMOTE2_TMIN,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                2, 5, 0 },
       { DBC_CTL,  {   DBCOOL_REMOTE2_TTHRESH,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                2, 6, 0 },
       { DBC_CTL,  {   DBCOOL_R2_TMIN_HYST,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                2, 7, 0 },
       { DBC_EOF,  { 0, 0, 0 }, 0, 0, 0 }
};

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

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

/*
* The registers 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_MINDUTY,
           DBCOOL_PWM1_MAXDUTY, DBCOOL_PWM1_CURDUTY },
               "fan_control_1" },
       { { DBCOOL_PWM2_CTL, DBCOOL_PWM2_MINDUTY,
           DBCOOL_PWM2_MAXDUTY, DBCOOL_PWM2_CURDUTY },
               "fan_control_2" },
       { { DBCOOL_PWM3_CTL, DBCOOL_PWM3_MINDUTY,
           DBCOOL_PWM3_MAXDUTY, DBCOOL_PWM3_CURDUTY },
               "fan_control_3" },
       { { 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, 0 },
       { DBC_TEMP, {   DBCOOL_ADT7466_REM_TEMP_MSB,
                       DBCOOL_ADT7466_REM_TEMP_HILIM,
                       DBCOOL_ADT7466_REM_TEMP_LOLIM }, 1,  0, 0 },
       { DBC_VOLT, {   DBCOOL_ADT7466_VCC,
                       DBCOOL_ADT7466_VCC_HILIM,
                       DBCOOL_ADT7466_VCC_LOLIM },     4,  0, 0 },
       { DBC_VOLT, {   DBCOOL_ADT7466_AIN1,
                       DBCOOL_ADT7466_AIN1_HILIM,
                       DBCOOL_ADT7466_AIN1_LOLIM },    9,  0, 6 },
       { DBC_VOLT, {   DBCOOL_ADT7466_AIN2,
                       DBCOOL_ADT7466_AIN2_HILIM,
                       DBCOOL_ADT7466_AIN2_LOLIM },    10, 0, 6 },
       { DBC_FAN,  {   DBCOOL_ADT7466_FANA_LSB,
                       DBCOOL_NO_REG,
                       DBCOOL_ADT7466_FANA_LOLIM_LSB }, 5,  0, 0 },
       { DBC_FAN,  {   DBCOOL_ADT7466_FANB_LSB,
                       DBCOOL_NO_REG,
                       DBCOOL_ADT7466_FANB_LOLIM_LSB }, 6,  0, 0 },
       { DBC_EOF,  { 0, 0, 0 }, 0, 0, 0 }
};

struct dbcool_sensor ADM1027_sensor_table[] = {
       { DBC_TEMP, {   DBCOOL_LOCAL_TEMP,
                       DBCOOL_LOCAL_HIGHLIM,
                       DBCOOL_LOCAL_LOWLIM },          0, 0, 0 },
       { DBC_TEMP, {   DBCOOL_REMOTE1_TEMP,
                       DBCOOL_REMOTE1_HIGHLIM,
                       DBCOOL_REMOTE1_LOWLIM },        1, 0, 0 },
       { DBC_TEMP, {   DBCOOL_REMOTE2_TEMP,
                       DBCOOL_REMOTE2_HIGHLIM,
                       DBCOOL_REMOTE2_LOWLIM },        2, 0, 0 },
       { DBC_VOLT, {   DBCOOL_VCCP,
                       DBCOOL_VCCP_HIGHLIM,
                       DBCOOL_VCCP_LOWLIM },           3, 0, 1 },
       { DBC_VOLT, {   DBCOOL_VCC,
                       DBCOOL_VCC_HIGHLIM,
                       DBCOOL_VCC_LOWLIM },            4, 0, 0 },
       { DBC_VOLT, {   DBCOOL_25VIN,
                       DBCOOL_25VIN_HIGHLIM,
                       DBCOOL_25VIN_LOWLIM },          11, 0, 2 },
       { DBC_VOLT, {   DBCOOL_5VIN,
                       DBCOOL_5VIN_HIGHLIM,
                       DBCOOL_5VIN_LOWLIM },           12, 0, 3 },
       { DBC_VOLT, {   DBCOOL_12VIN,
                       DBCOOL_12VIN_HIGHLIM,
                       DBCOOL_12VIN_LOWLIM },          13, 0, 4 },
       { DBC_FAN,  {   DBCOOL_FAN1_TACH_LSB,
                       DBCOOL_NO_REG,
                       DBCOOL_TACH1_MIN_LSB },         5, 0, 0 },
       { DBC_FAN,  {   DBCOOL_FAN2_TACH_LSB,
                       DBCOOL_NO_REG,
                       DBCOOL_TACH2_MIN_LSB },         6, 0, 0 },
       { DBC_FAN,  {   DBCOOL_FAN3_TACH_LSB,
                       DBCOOL_NO_REG,
                       DBCOOL_TACH3_MIN_LSB },         7, 0, 0 },
       { DBC_FAN,  {   DBCOOL_FAN4_TACH_LSB,
                       DBCOOL_NO_REG,
                       DBCOOL_TACH4_MIN_LSB },         8, 0, 0 },
       { DBC_VID,  {   DBCOOL_VID_REG,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                16, 0, 0 },
       { DBC_CTL,  {   DBCOOL_LOCAL_TMIN,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                0, 5, 0 },
       { DBC_CTL,  {   DBCOOL_LOCAL_TTHRESH,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                0, 6, 0 },
       { DBC_CTL,  {   DBCOOL_R1_LCL_TMIN_HYST | 0x80,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                0, 7, 0 },
       { DBC_CTL,  {   DBCOOL_REMOTE1_TMIN,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                1, 5, 0 },
       { DBC_CTL,  {   DBCOOL_REMOTE1_TTHRESH,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                1, 6, 0 },
       { DBC_CTL,  {   DBCOOL_R1_LCL_TMIN_HYST,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                1, 7, 0 },
       { DBC_CTL,  {   DBCOOL_REMOTE2_TMIN,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                2, 5, 0 },
       { DBC_CTL,  {   DBCOOL_REMOTE2_TTHRESH,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                2, 6, 0 },
       { DBC_CTL,  {   DBCOOL_R2_TMIN_HYST,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                2, 7, 0 },
       { DBC_EOF,  { 0, 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, 0 },
       { DBC_TEMP, {   DBCOOL_ADM1030_R_TEMP,
                       DBCOOL_ADM1030_R_HI_LIM,
                       DBCOOL_ADM1030_R_LO_LIM },      1,  0, 0 },
       { DBC_FAN,  {   DBCOOL_ADM1030_FAN_TACH,
                       DBCOOL_NO_REG,
                       DBCOOL_ADM1030_FAN_LO_LIM },    5,  0, 0 },
       { DBC_CTL,  {   DBCOOL_ADM1030_L_TMIN,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                0,  8, 0 },
       { DBC_CTL,  {   DBCOOL_ADM1030_L_TTHRESH,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                0,  9, 0 },
       { DBC_CTL,  {   DBCOOL_ADM1030_L_TTHRESH,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                0,  6, 0 },
       { DBC_CTL,  {   DBCOOL_ADM1030_R_TMIN,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                1,  8, 0 },
       { DBC_CTL,  {   DBCOOL_ADM1030_R_TTHRESH,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                1,  9, 0 },
       { DBC_CTL,  {   DBCOOL_ADM1030_R_TTHRESH,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                1,  6, 0 },
       { DBC_EOF,  {0, 0, 0 }, 0, 0, 0 }
};

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

struct dbcool_sensor ADM1031_sensor_table[] = {
       { DBC_TEMP, {   DBCOOL_ADM1030_L_TEMP,
                       DBCOOL_ADM1030_L_HI_LIM,
                       DBCOOL_ADM1030_L_LO_LIM },      0,  0, 0 },
       { DBC_TEMP, {   DBCOOL_ADM1030_R_TEMP,
                       DBCOOL_ADM1030_R_HI_LIM,
                       DBCOOL_ADM1030_R_LO_LIM },      1,  0, 0 },
       { DBC_TEMP, {   DBCOOL_ADM1031_R2_TEMP,
                       DBCOOL_ADM1031_R2_HI_LIM,
                       DBCOOL_ADM1031_R2_LO_LIM },     2,  0, 0 },
       { DBC_FAN,  {   DBCOOL_ADM1030_FAN_TACH,
                       DBCOOL_NO_REG,
                       DBCOOL_ADM1030_FAN_LO_LIM },    5,  0, 0 },
       { DBC_FAN,  {   DBCOOL_ADM1031_FAN2_TACH,
                       DBCOOL_NO_REG,
                       DBCOOL_ADM1031_FAN2_LO_LIM },   6,  0, 0 },
       { DBC_CTL,  {   DBCOOL_ADM1030_L_TMIN,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                0,  8, 0 },
       { DBC_CTL,  {   DBCOOL_ADM1030_L_TTHRESH,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                0,  9, 0 },
       { DBC_CTL,  {   DBCOOL_ADM1030_L_TTHRESH,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                0,  6, 0 },
       { DBC_CTL,  {   DBCOOL_ADM1030_R_TMIN,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                1,  8, 0 },
       { DBC_CTL,  {   DBCOOL_ADM1030_R_TTHRESH,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                1,  9, 0 },
       { DBC_CTL,  {   DBCOOL_ADM1030_R_TTHRESH,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                1,  6, 0 },
       { DBC_CTL,  {   DBCOOL_ADM1031_R2_TMIN,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                2,  8, 0 },
       { DBC_CTL,  {   DBCOOL_ADM1031_R2_TTHRESH,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                2,  9, 0 },
       { DBC_CTL,  {   DBCOOL_ADM1031_R2_TTHRESH,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                2,  6, 0 },
       { DBC_EOF,  {0, 0, 0 }, 0, 0, 0 }
};

struct dbcool_power_control ADM1031_power_table[] = {
       { { DBCOOL_ADM1030_CFG1,  DBCOOL_NO_REG, DBCOOL_NO_REG,
           DBCOOL_ADM1030_FAN_SPEED_CFG },
         "fan_control_1" },
       { { DBCOOL_ADM1030_CFG1,  DBCOOL_NO_REG, DBCOOL_NO_REG,
           DBCOOL_ADM1030_FAN_SPEED_CFG },
         "fan_control_2" },
       { { 0, 0, 0, 0 }, NULL }
};

struct dbcool_sensor EMC6D103S_sensor_table[] = {
       { DBC_TEMP, {   DBCOOL_LOCAL_TEMP,
                       DBCOOL_LOCAL_HIGHLIM,
                       DBCOOL_LOCAL_LOWLIM },          0, 0, 0 },
       { DBC_TEMP, {   DBCOOL_REMOTE1_TEMP,
                       DBCOOL_REMOTE1_HIGHLIM,
                       DBCOOL_REMOTE1_LOWLIM },        1, 0, 0 },
       { DBC_TEMP, {   DBCOOL_REMOTE2_TEMP,
                       DBCOOL_REMOTE2_HIGHLIM,
                       DBCOOL_REMOTE2_LOWLIM },        2, 0, 0 },
       { DBC_VOLT, {   DBCOOL_VCCP,
                       DBCOOL_VCCP_HIGHLIM,
                       DBCOOL_VCCP_LOWLIM },           3, 0, 1 },
       { DBC_VOLT, {   DBCOOL_VCC,
                       DBCOOL_VCC_HIGHLIM,
                       DBCOOL_VCC_LOWLIM },            4, 0, 0 },
       { DBC_VOLT, {   DBCOOL_25VIN,
                       DBCOOL_25VIN_HIGHLIM,
                       DBCOOL_25VIN_LOWLIM },          11, 0, 2 },
       { DBC_VOLT, {   DBCOOL_5VIN,
                       DBCOOL_5VIN_HIGHLIM,
                       DBCOOL_5VIN_LOWLIM },           12, 0, 3 },
       { DBC_VOLT, {   DBCOOL_12VIN,
                       DBCOOL_12VIN_HIGHLIM,
                       DBCOOL_12VIN_LOWLIM },          13, 0, 4 },
       { DBC_FAN,  {   DBCOOL_FAN1_TACH_LSB,
                       DBCOOL_NO_REG,
                       DBCOOL_TACH1_MIN_LSB },         5, 0, 0 },
       { DBC_FAN,  {   DBCOOL_FAN2_TACH_LSB,
                       DBCOOL_NO_REG,
                       DBCOOL_TACH2_MIN_LSB },         6, 0, 0 },
       { DBC_FAN,  {   DBCOOL_FAN3_TACH_LSB,
                       DBCOOL_NO_REG,
                       DBCOOL_TACH3_MIN_LSB },         7, 0, 0 },
       { DBC_FAN,  {   DBCOOL_FAN4_TACH_LSB,
                       DBCOOL_NO_REG,
                       DBCOOL_TACH4_MIN_LSB },         8, 0, 0 },
       { DBC_VID,  {   DBCOOL_VID_REG,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                16, 0, 0 },
       { DBC_CTL,  {   DBCOOL_LOCAL_TMIN,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                0, 5, 0 },
       { DBC_CTL,  {   DBCOOL_LOCAL_TTHRESH,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                0, 6, 0 },
       { DBC_CTL,  {   DBCOOL_REMOTE1_TMIN,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                1, 5, 0 },
       { DBC_CTL,  {   DBCOOL_REMOTE1_TTHRESH,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                1, 6, 0 },
       { DBC_CTL,  {   DBCOOL_REMOTE2_TMIN,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                2, 5, 0 },
       { DBC_CTL,  {   DBCOOL_REMOTE2_TTHRESH,
                       DBCOOL_NO_REG,
                       DBCOOL_NO_REG },                2, 6, 0 },
       { DBC_EOF,  { 0, 0, 0 }, 0, 0, 0 }
};

struct chip_id chip_table[] = {
       { DBCOOL_COMPANYID, ADT7490_DEVICEID, ADT7490_REV_ID,
               ADT7490_sensor_table, ADT7475_power_table,
               DBCFLAG_TEMPOFFSET | DBCFLAG_HAS_MAXDUTY | DBCFLAG_HAS_PECI,
               90000 * 60, "ADT7490" },
       { DBCOOL_COMPANYID, ADT7476_DEVICEID, 0xff,
               ADT7476_sensor_table, ADT7475_power_table,
               DBCFLAG_TEMPOFFSET | DBCFLAG_HAS_MAXDUTY,
               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_ID1,
               ADT7475_sensor_table, ADT7475_power_table,
               DBCFLAG_TEMPOFFSET | DBCFLAG_HAS_MAXDUTY | DBCFLAG_HAS_SHDN,
               90000 * 60, "ADT7460/ADT7463" },
       { DBCOOL_COMPANYID, ADT7473_DEVICEID, ADT7473_REV_ID2,
               ADT7475_sensor_table, ADT7475_power_table,
               DBCFLAG_TEMPOFFSET | DBCFLAG_HAS_MAXDUTY | DBCFLAG_HAS_SHDN,
               90000 * 60, "ADT7463-1" },
       { DBCOOL_COMPANYID, ADT7468_DEVICEID, 0xff,
               ADT7476_sensor_table, ADT7475_power_table,
               DBCFLAG_TEMPOFFSET  | DBCFLAG_MULTI_VCC | DBCFLAG_HAS_MAXDUTY |
                   DBCFLAG_4BIT_VER | DBCFLAG_HAS_SHDN,
               90000 * 60, "ADT7467/ADT7468" },
       { 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,
               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_SEL,
               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 | DBCFLAG_NO_READBYTE,
               11250 * 60, "ADM1030" },
       { DBCOOL_COMPANYID, ADM1031_DEVICEID, 0xff,
               ADM1031_sensor_table, ADM1030_power_table,
               DBCFLAG_ADM1030 | DBCFLAG_NO_READBYTE,
               11250 * 60, "ADM1031" },
       { SMSC_COMPANYID, EMC6D103S_DEVICEID, EMC6D103S_REV_ID,
               EMC6D103S_sensor_table, ADT7475_power_table,
               DBCFLAG_4BIT_VER,
               90000 * 60, "EMC6D103S" },
       { 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);

static const struct device_compatible_entry compat_data[] = {
       { .compat = "i2c-adm1031" },
       { .compat = "adt7467" },
       { .compat = "adt7460" },
       { .compat = "adm1030" },
       DEVICE_COMPAT_EOL
};

int
dbcool_match(device_t parent, cfdata_t cf, void *aux)
{
       struct i2c_attach_args *ia = aux;
       struct dbcool_chipset dc;
       dc.dc_tag = ia->ia_tag;
       dc.dc_addr = ia->ia_addr;
       dc.dc_chip = NULL;
       dc.dc_readreg = dbcool_readreg;
       dc.dc_writereg = dbcool_writereg;
       int match_result;

       if (iic_use_direct_match(ia, cf, compat_data, &match_result))
               return match_result;

       if ((ia->ia_addr & DBCOOL_ADDRMASK) != DBCOOL_ADDR)
               return 0;
       if (dbcool_chip_ident(&dc) >= 0)
               return I2C_MATCH_ADDRESS_AND_PROBE;

       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_dc.dc_addr = args->ia_addr;
       sc->sc_dc.dc_tag = args->ia_tag;
       sc->sc_dc.dc_chip = NULL;
       sc->sc_dc.dc_readreg = dbcool_readreg;
       sc->sc_dc.dc_writereg = dbcool_writereg;
       sc->sc_dev = self;
       sc->sc_prop = args->ia_prop;
       prop_object_retain(sc->sc_prop);

       if (dbcool_chip_ident(&sc->sc_dc) < 0 || sc->sc_dc.dc_chip == NULL)
               panic("could not identify chip at addr %d", args->ia_addr);

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

       ver = sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_REVISION_REG);
       if (sc->sc_dc.dc_chip->flags & DBCFLAG_4BIT_VER)
               if (sc->sc_dc.dc_chip->company == SMSC_COMPANYID)
               {
                       aprint_normal_dev(self, "SMSC %s Controller "
                           "(rev 0x%02x, stepping 0x%02x)\n",
                           sc->sc_dc.dc_chip->name, ver >> 4, ver & 0x0f);
               } else {
                       aprint_normal_dev(self, "%s dBCool(tm) Controller "
                           "(rev 0x%02x, stepping 0x%02x)\n",
                           sc->sc_dc.dc_chip->name, ver >> 4, ver & 0x0f);
               }
       else
               aprint_normal_dev(self, "%s dBCool(tm) Controller "
                       "(rev 0x%04x)\n", sc->sc_dc.dc_chip->name, ver);

       sc->sc_sysctl_log = NULL;

#ifdef _MODULE
       sysctl_dbcoolsetup(&sc->sc_sysctl_log);
#endif

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

       pmf_device_deregister(self);

       if (sc->sc_sme != NULL)
               sysmon_envsys_unregister(sc->sc_sme);

       sysctl_teardown(&sc->sc_sysctl_log);

       return 0;
}

/*
* On suspend, we save the state of the SHDN bit, then set it
* On resume, we restore the previous state of the SHDN bit (which
* we saved in sc_suspend)
*/
static bool
dbcool_do_pmf(device_t dev, const pmf_qual_t *qual, bool suspend)
{
       struct dbcool_softc *sc = device_private(dev);
       uint8_t reg, bit, cfg;

       if ((sc->sc_dc.dc_chip->flags & DBCFLAG_HAS_SHDN) == 0)
               return true;

       if (sc->sc_dc.dc_chip->flags & DBCFLAG_ADT7466) {
               reg = DBCOOL_ADT7466_CONFIG2;
               bit = DBCOOL_ADT7466_CFG2_SHDN;
       } else {
               reg = DBCOOL_CONFIG2_REG;
               bit = DBCOOL_CFG2_SHDN;
       }
       cfg = sc->sc_dc.dc_readreg(&sc->sc_dc, reg);
       if (suspend) {
               sc->sc_suspend = (cfg & bit) != 0;
               cfg |= bit;
       } else {
               cfg &= sc->sc_suspend ? bit : 0;
       }
       sc->sc_dc.dc_writereg(&sc->sc_dc, reg, cfg);

       return true;
}

bool
dbcool_pmf_suspend(device_t dev, const pmf_qual_t *qual)
{

       return dbcool_do_pmf(dev, qual, true);
}

bool
dbcool_pmf_resume(device_t dev, const pmf_qual_t *qual)
{

       return dbcool_do_pmf(dev, qual, false);
}

uint8_t
dbcool_readreg(struct dbcool_chipset *dc, uint8_t reg)
{
       uint8_t data = 0;

       if (iic_acquire_bus(dc->dc_tag, 0) != 0)
               return data;

       if (dc->dc_chip == NULL || dc->dc_chip->flags & DBCFLAG_NO_READBYTE) {
               /* ADM1027 doesn't support i2c read_byte protocol */
               if (iic_smbus_send_byte(dc->dc_tag, dc->dc_addr, reg, 0) != 0)
                       goto bad;
               (void)iic_smbus_receive_byte(dc->dc_tag, dc->dc_addr, &data, 0);
       } else
               (void)iic_smbus_read_byte(dc->dc_tag, dc->dc_addr, reg, &data,
                                         0);

bad:
       iic_release_bus(dc->dc_tag, 0);
       return data;
}

void
dbcool_writereg(struct dbcool_chipset *dc, uint8_t reg, uint8_t val)
{
       if (iic_acquire_bus(dc->dc_tag, 0) != 0)
               return;

       (void)iic_smbus_write_byte(dc->dc_tag, dc->dc_addr, reg, val, 0);

       iic_release_bus(dc->dc_tag, 0);
}

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

       if (sc->sc_dc.dc_chip->flags & DBCFLAG_ADM1030)
               return 0;

       if (sc->sc_dc.dc_chip->flags & DBCFLAG_ADT7466)
               cfg_reg = DBCOOL_ADT7466_CONFIG1;
       else
               cfg_reg = DBCOOL_CONFIG1_REG;

       if (sc->sc_dc.dc_readreg(&sc->sc_dc, 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;
       int temp;

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

               /* Read all msb regs to unlatch them */
               t1 = sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_12VIN);
               t1 = sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_REMOTE1_TEMP);
               t2 = sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_REMOTE2_TEMP);
               t3 = sc->sc_dc.dc_readreg(&sc->sc_dc, 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 = sc->sc_dc.dc_readreg(&sc->sc_dc, reg);

       /* Check for invalid temp values */
       if ((sc->sc_temp_offset == 0 && val == 0x80) ||
           (sc->sc_temp_offset != 0 && val == 0))
               return 0;

       /* If using offset mode, adjust, else treat as signed */
       if (sc->sc_temp_offset) {
               temp = val;
               temp -= sc->sc_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 = sc->sc_dc.dc_readreg(&sc->sc_dc, reg);
       if (sc->sc_dc.dc_chip->flags & DBCFLAG_ADM1030)
               rpm_hi = (rpm_lo == 0xff)?0xff:0x0;
       else
               rpm_hi = sc->sc_dc.dc_readreg(&sc->sc_dc, reg + 1);

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

       /* don't divide by zero */
       return (rpm == 0)? 0 : (sc->sc_dc.dc_chip->rpm_dividend / rpm);
}

/* Provide chip's supply voltage, in microvolts */
static int
dbcool_supply_voltage(struct dbcool_softc *sc)
{
       if (sc->sc_dc.dc_chip->flags & DBCFLAG_MULTI_VCC) {
               if (sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_CONFIG1_REG) & DBCOOL_CFG1_Vcc)
                       return 5002500;
               else
                       return 3300000;
       } else if (sc->sc_dc.dc_chip->flags & DBCFLAG_ADT7466) {
               if (sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_ADT7466_CONFIG1) &
                           DBCOOL_ADT7466_CFG1_Vcc)
                       return 5000000;
               else
                       return 3300000;
       } else
               return 3300000;
}

/*
* Nominal voltages are calculated in microvolts
*/
static int
dbcool_read_volt(struct dbcool_softc *sc, uint8_t reg, int nom_idx, bool extres)
{
       uint8_t ext = 0, v1, v2, v3, v4, val;
       int64_t ret;
       int64_t nom;

       nom = nominal_voltages[nom_idx];
       if (nom < 0)
               nom = sc->sc_supply_voltage;

       /* ADT7466 voltages are in strange locations with only 8-bits */
       if (sc->sc_dc.dc_chip->flags & DBCFLAG_ADT7466)
               val = sc->sc_dc.dc_readreg(&sc->sc_dc, reg);
       else
       /*
        * It's a "normal" dbCool chip - check for regs that
        * share extended resolution bits since we have to
        * read all the MSB registers to unlatch them.
        */
       if (!extres)
               val = sc->sc_dc.dc_readreg(&sc->sc_dc, reg);
       else if (reg == DBCOOL_12VIN) {
               ext = sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_EXTRES2_REG) & 0x03;
               val = sc->sc_dc.dc_readreg(&sc->sc_dc, reg);
               (void)dbcool_read_temp(sc, DBCOOL_LOCAL_TEMP, true);
       } else if (reg == DBCOOL_VTT || reg == DBCOOL_IMON) {
               ext = sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_EXTRES_VTT_IMON);
               v1 = sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_IMON);
               v2 = sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_VTT);
               if (reg == DBCOOL_IMON) {
                       val = v1;
                       ext >>= 6;
               } else {
                       val = v2;
                       ext >>= 4;
               }
               ext &= 0x0f;
       } else {
               ext = sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_EXTRES1_REG);
               v1 = sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_25VIN);
               v2 = sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_VCCP);
               v3 = sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_VCC);
               v4 = sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_5VIN);

               switch (reg) {
               case DBCOOL_25VIN:
                       val = v1;
                       break;
               case DBCOOL_VCCP:
                       val = v2;
                       ext >>= 2;
                       break;
               case DBCOOL_VCC:
                       val = v3;
                       ext >>= 4;
                       break;
               case DBCOOL_5VIN:
                       val = v4;
                       ext >>= 6;
                       break;
               default:
                       val = nom = 0;
               }
               ext &= 0x03;
       }

       /*
        * Scale the nominal value by the 10-bit fraction
        *
        * Returned value is in microvolts.
        */
       ret = val;
       ret <<= 2;
       ret |= ext;
       ret = (ret * nom) / 0x300;

       return ret;
}

static int
sysctl_dbcool_temp(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 = node.sysctl_num & 0xff;

       if (sc->sc_temp_offset) {
               reg = sc->sc_dc.dc_readreg(&sc->sc_dc, chipreg);
               reg -= sc->sc_temp_offset;
       } else
               reg = (int8_t)sc->sc_dc.dc_readreg(&sc->sc_dc, 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;
       sc->sc_dc.dc_writereg(&sc->sc_dc, chipreg, newreg);
       return 0;
}

static int
sysctl_adm1030_temp(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 = node.sysctl_num & 0xff;

       oldreg = (int8_t)sc->sc_dc.dc_readreg(&sc->sc_dc, 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);
       sc->sc_dc.dc_writereg(&sc->sc_dc, 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 = node.sysctl_num & 0xff;

       oldreg = (int8_t)sc->sc_dc.dc_readreg(&sc->sc_dc, 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);
       sc->sc_dc.dc_writereg(&sc->sc_dc, 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 = node.sysctl_num & 0xff;

       oldreg = sc->sc_dc.dc_readreg(&sc->sc_dc, chipreg);
       reg = (uint32_t)oldreg;
       if (sc->sc_dc.dc_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_dc.dc_chip->flags & DBCFLAG_ADM1030) {
               newreg = *(uint8_t *)(node.sysctl_data) * 15 / 100;
               newreg |= oldreg & 0xf0;
       } else
               newreg = *(uint8_t *)(node.sysctl_data) * 255 / 100;
       sc->sc_dc.dc_writereg(&sc->sc_dc, 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 = node.sysctl_num & 0xff;

       oldreg = sc->sc_dc.dc_readreg(&sc->sc_dc, chipreg);

       if (sc->sc_dc.dc_chip->flags & DBCFLAG_ADM1030) {
               if ((sc->sc_dc.dc_readreg(&sc->sc_dc, 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;
       newreg = i;
       if (sc->sc_dc.dc_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 = sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_ADM1030_CFG2);
                       oldreg &= ~0x01;
                       sc->sc_dc.dc_writereg(&sc->sc_dc, 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);
                       sc->sc_dc.dc_writereg(&sc->sc_dc, chipreg, newreg);
                       oldreg = sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_ADM1030_CFG2) | 1;
                       sc->sc_dc.dc_writereg(&sc->sc_dc, DBCOOL_ADM1030_CFG2, oldreg);
               }
       } else {
               newreg = (sc->sc_dc.dc_readreg(&sc->sc_dc, chipreg) & 0x1f) | (i << 5);
               sc->sc_dc.dc_writereg(&sc->sc_dc, chipreg, newreg);
       }
       return 0;
}

static int
sysctl_dbcool_slope(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 = node.sysctl_num & 0xff;

       reg = (sc->sc_dc.dc_readreg(&sc->sc_dc, 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 = (sc->sc_dc.dc_readreg(&sc->sc_dc, chipreg) & 0x0f) |
                 (*(int *)node.sysctl_data << 4);
       sc->sc_dc.dc_writereg(&sc->sc_dc, chipreg, newreg);
       return 0;
}

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 = sc->sc_dc.dc_readreg(&sc->sc_dc, 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;
       }
       sc->sc_dc.dc_writereg(&sc->sc_dc, 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 = sc->sc_dc.dc_readreg(&sc->sc_dc, chipreg);
       node.sysctl_data = &reg;
       error = sysctl_lookup(SYSCTLFN_CALL(&node));

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

       newreg = *(int *)node.sysctl_data;
       sc->sc_dc.dc_writereg(&sc->sc_dc, chipreg, newreg);
       return 0;
}
#endif /* DBCOOL_DEBUG */

/*
* Encode an index number and register number for use as a sysctl_num
* so we can select the correct device register later.
*/
#define DBC_PWM_SYSCTL(seq, reg)        ((seq << 8) | reg)

void
dbcool_setup(device_t self)
{
       struct dbcool_softc *sc = device_private(self);
       const struct sysctlnode *me = NULL;
#ifdef DBCOOL_DEBUG
       struct sysctlnode *node = NULL;
#endif
       uint8_t cfg_val, cfg_reg;
       int ret, error;

       /*
        * 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 compatibility with other chips that report 2s complement.
        */
       if (sc->sc_dc.dc_chip->flags & DBCFLAG_ADT7466) {
               if (sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_ADT7466_CONFIG1) & 0x80)
                       sc->sc_temp_offset = 64;
               else
                       sc->sc_temp_offset = 0;
       } else if (sc->sc_dc.dc_chip->flags & DBCFLAG_TEMPOFFSET) {
               if (sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_CONFIG5_REG) &
                           DBCOOL_CFG5_TWOSCOMP)
                       sc->sc_temp_offset = 0;
               else
                       sc->sc_temp_offset = 64;
       } else
               sc->sc_temp_offset = 0;

       /* Determine Vcc for this chip */
       sc->sc_supply_voltage = dbcool_supply_voltage(sc);

       ret = sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &me,
              CTLFLAG_READWRITE,
              CTLTYPE_NODE, device_xname(self), NULL,
              NULL, 0, NULL, 0,
              CTL_HW, CTL_CREATE, CTL_EOL);
       if (ret == 0)
               sc->sc_root_sysctl_num = me->sysctl_num;
       else
               sc->sc_root_sysctl_num = 0;

       aprint_debug_dev(self,
               "Supply voltage %"PRId64".%06"PRId64"V, %s temp range\n",
               sc->sc_supply_voltage / 1000000,
               sc->sc_supply_voltage % 1000000,
               sc->sc_temp_offset ? "extended" : "normal");

       /* Create the sensors for this device */
       sc->sc_sme = sysmon_envsys_create();
       if (dbcool_setup_sensors(sc))
               goto out;

       if (sc->sc_root_sysctl_num != 0) {
               /* If supported, create sysctl tree for fan PWM controllers */
               if (sc->sc_dc.dc_chip->power != NULL)
                       dbcool_setup_controllers(sc);

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

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

       /*
        * Read and rewrite config register to activate device
        */
       if (sc->sc_dc.dc_chip->flags & DBCFLAG_ADM1030)
               cfg_reg = DBCOOL_ADM1030_CFG1;
       else if (sc->sc_dc.dc_chip->flags & DBCFLAG_ADT7466)
               cfg_reg = DBCOOL_ADT7466_CONFIG1;
       else
               cfg_reg = DBCOOL_CONFIG1_REG;
       cfg_val = sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_CONFIG1_REG);
       if ((cfg_val & DBCOOL_CFG1_START) == 0) {
               cfg_val |= DBCOOL_CFG1_START;
               sc->sc_dc.dc_writereg(&sc->sc_dc, 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;
       sc->sc_sme->sme_set_limits = dbcool_set_limits;
       sc->sc_sme->sme_get_limits = dbcool_get_limits;

       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);
       sc->sc_sme = NULL;
}

static int
dbcool_setup_sensors(struct dbcool_softc *sc)
{
       int i;
       int error = 0;
       uint8_t vid_reg, vid_val;
       struct chip_id *chip = sc->sc_dc.dc_chip;

       for (i=0; chip->table[i].type != DBC_EOF; i++) {
               if (i < DBCOOL_MAXSENSORS)
                       sc->sc_sysctl_num[i] = -1;
               else if (chip->table[i].type != DBC_CTL) {
                       aprint_normal_dev(sc->sc_dev, "chip table too big!\n");
                       break;
               }
               switch (chip->table[i].type) {
               case DBC_TEMP:
                       sc->sc_sensor[i].units = ENVSYS_STEMP;
                       sc->sc_sensor[i].state = ENVSYS_SINVALID;
                       sc->sc_sensor[i].flags |= ENVSYS_FMONLIMITS;
                       sc->sc_sensor[i].flags |= ENVSYS_FHAS_ENTROPY;
                       error = dbcool_attach_sensor(sc, i);
                       break;
               case DBC_VOLT:
                       /*
                        * If 12V-In pin has been reconfigured as 6th bit
                        * of VID code, don't create a 12V-In sensor
                        */
                       if ((chip->flags & DBCFLAG_HAS_VID_SEL) &&
                           (chip->table[i].reg.val_reg == DBCOOL_12VIN) &&
                           (sc->sc_dc.dc_readreg(&sc->sc_dc, DBCOOL_VID_REG) &
                                       0x80))
                               break;

                       sc->sc_sensor[i].units = ENVSYS_SVOLTS_DC;
                       sc->sc_sensor[i].state = ENVSYS_SINVALID;
                       sc->sc_sensor[i].flags |= ENVSYS_FMONLIMITS;
                       sc->sc_sensor[i].flags |= ENVSYS_FHAS_ENTROPY;
                       error = dbcool_attach_sensor(sc, i);
                       break;
               case DBC_FAN:
                       sc->sc_sensor[i].units = ENVSYS_SFANRPM;
                       sc->sc_sensor[i].state = ENVSYS_SINVALID;
                       sc->sc_sensor[i].flags |= ENVSYS_FMONLIMITS;
                       sc->sc_sensor[i].flags |= ENVSYS_FHAS_ENTROPY;
                       error = dbcool_attach_sensor(sc, i);
                       break;
               case DBC_VID:
                       sc->sc_sensor[i].units = ENVSYS_INTEGER;
                       sc->sc_sensor[i].state = ENVSYS_SINVALID;
                       sc->sc_sensor[i].flags |= ENVSYS_FMONNOTSUPP;

                       /* retrieve 5- or 6-bit value */
                       vid_reg = chip->table[i].reg.val_reg;
                       vid_val = sc->sc_dc.dc_readreg(&sc->sc_dc, vid_reg);
                       if (chip->flags & DBCFLAG_HAS_VID_SEL)
                               vid_val &= 0x3f;
                       else
                               vid_val &= 0x1f;
                       sc->sc_sensor[i].value_cur = vid_val;

                       error = dbcool_attach_sensor(sc, i);
                       break;
               case DBC_CTL:
                       error = dbcool_attach_temp_control(sc, i, chip);
                       if (error) {
                               aprint_error_dev(sc->sc_dev,
                                               "attach index %d failed %d\n",
                                               i, error);
                               error = 0;
                       }
                       break;
               default:
                       aprint_error_dev(sc->sc_dev,
                               "sensor_table index %d has bad type %d\n",
                               i, chip->table[i].type);
                       break;
               }
               if (error)
                       break;
       }
       return error;
}

static int
dbcool_attach_sensor(struct dbcool_softc *sc, int idx)
{
       int name_index;
       int error = 0;
       char name[8];
       const char *desc;

       name_index = sc->sc_dc.dc_chip->table[idx].name_index;
       snprintf(name, 7, "s%02x", sc->sc_dc.dc_chip->table[idx].reg.val_reg);
       if (prop_dictionary_get_string(sc->sc_prop, name, &desc)) {
                strlcpy(sc->sc_sensor[idx].desc, desc,
                       sizeof(sc->sc_sensor[idx].desc));
       } else {
               strlcpy(sc->sc_sensor[idx].desc, dbc_sensor_names[name_index],
                       sizeof(sc->sc_sensor[idx].desc));
       }
       sc->sc_regs[idx] = &sc->sc_dc.dc_chip->table[idx].reg;
       sc->sc_nom_volt[idx] = sc->sc_dc.dc_chip->table[idx].nom_volt_index;

       error = sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[idx]);
       return error;
}

static int
dbcool_attach_temp_control(struct dbcool_softc *sc, int idx,
                          struct chip_id *chip)
{
       const struct sysctlnode *me2 = NULL, *node;
       int j, ret, sysctl_index, rw_flag;
       uint8_t sysctl_reg;
       char name[SYSCTL_NAMELEN];

       /* Search for the corresponding temp sensor */
       for (j = 0; j < idx; j++) {
               if (j >= DBCOOL_MAXSENSORS || chip->table[j].type != DBC_TEMP)
                       continue;
               if (chip->table[j].name_index == chip->table[idx].name_index)
                       break;
       }
       if (j >= idx)   /* Temp sensor not found */
               return ENOENT;

       /* create sysctl node for the sensor if not one already there */
       if (sc->sc_sysctl_num[j] == -1) {
               int name_index = sc->sc_dc.dc_chip->table[idx].name_index;

               ret = sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &me2,
                                    CTLFLAG_READWRITE,
                                    CTLTYPE_NODE, dbc_sensor_names[name_index],
                                    sc->sc_sensor[j].desc,
                                    NULL, 0, NULL, 0,
                                    CTL_HW, sc->sc_root_sysctl_num, CTL_CREATE,
                                       CTL_EOL);
               if (me2 != NULL)
                       sc->sc_sysctl_num[j] = me2->sysctl_num;
               else
                       return ret;
       }
       /* add sysctl leaf node for this control variable */
       sysctl_index = chip->table[idx].sysctl_index;
       sysctl_reg = chip->table[idx].reg.val_reg;
       strlcpy(name, dbc_sysctl_table[sysctl_index].name, sizeof(name));
       if (dbc_sysctl_table[sysctl_index].lockable && dbcool_islocked(sc))
               rw_flag = CTLFLAG_READONLY | CTLFLAG_OWNDESC;
       else
               rw_flag = CTLFLAG_READWRITE | CTLFLAG_OWNDESC;
       ret = sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &node, rw_flag,
                            CTLTYPE_INT, name,
                            SYSCTL_DESCR(dbc_sysctl_table[sysctl_index].desc),
                            dbc_sysctl_table[sysctl_index].helper,
                            0, (void *)sc, sizeof(int),
                            CTL_HW, sc->sc_root_sysctl_num,
                               sc->sc_sysctl_num[j],
                               DBC_PWM_SYSCTL(idx, sysctl_reg), CTL_EOL);

       return ret;
}

static void
dbcool_setup_controllers(struct dbcool_softc *sc)
{
       int i, j, rw_flag;
       uint8_t sysctl_reg;
       struct chip_id *chip = sc->sc_dc.dc_chip;
       const struct sysctlnode *me2 = NULL;
       const struct sysctlnode *node = NULL;
       char name[SYSCTL_NAMELEN];

       for (i = 0; chip->power[i].desc != NULL; i++) {
               snprintf(name, sizeof(name), "fan_ctl_%d", i);
               sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &me2,
                      CTLFLAG_READWRITE | CTLFLAG_OWNDESC,
                      CTLTYPE_NODE, name, NULL,
                      NULL, 0, NULL, 0,
                      CTL_HW, sc->sc_root_sysctl_num, CTL_CREATE, CTL_EOL);

               for (j = DBC_PWM_BEHAVIOR; j < DBC_PWM_LAST_PARAM; j++) {
                       if (j == DBC_PWM_MAX_DUTY &&
                           (chip->flags & DBCFLAG_HAS_MAXDUTY) == 0)
                               continue;
                       sysctl_reg = chip->power[i].power_regs[j];
                       if (sysctl_reg == DBCOOL_NO_REG)
                               continue;
                       strlcpy(name, dbc_sysctl_table[j].name, sizeof(name));
                       if (dbc_sysctl_table[j].lockable && dbcool_islocked(sc))
                               rw_flag = CTLFLAG_READONLY | CTLFLAG_OWNDESC;
                       else
                               rw_flag = CTLFLAG_READWRITE | CTLFLAG_OWNDESC;
                       (sysctl_createv)(&sc->sc_sysctl_log, 0, NULL,
                               &node, rw_flag,
                               (j == DBC_PWM_BEHAVIOR)?
                                       CTLTYPE_STRING:CTLTYPE_INT,
                               name,
                               SYSCTL_DESCR(dbc_sysctl_table[j].desc),
                               dbc_sysctl_table[j].helper,
                               0, sc,
                               ( j == DBC_PWM_BEHAVIOR)?
                                       sizeof(dbcool_cur_behav): sizeof(int),
                               CTL_HW, sc->sc_root_sysctl_num, me2->sysctl_num,
                               DBC_PWM_SYSCTL(j, sysctl_reg), CTL_EOL);
               }
       }
}

static void
dbcool_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
{
       struct dbcool_softc *sc=sme->sme_cookie;
       int i, nom_volt_idx, cur;
       struct reg_list *reg;

       i = edata->sensor;
       reg = sc->sc_regs[i];

       edata->state = ENVSYS_SVALID;
       switch (edata->units)
       {
               case ENVSYS_STEMP:
                       cur = dbcool_read_temp(sc, reg->val_reg, true);
                       break;
               case ENVSYS_SVOLTS_DC:
                       nom_volt_idx = sc->sc_nom_volt[i];
                       cur = dbcool_read_volt(sc, reg->val_reg, nom_volt_idx,
                                               true);
                       break;
               case ENVSYS_SFANRPM:
                       cur = dbcool_read_rpm(sc, reg->val_reg);
                       break;
               case ENVSYS_INTEGER:
                       return;
               default:
                       edata->state = ENVSYS_SINVALID;
                       return;
       }

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

       /*
        * 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 &&
                       !(edata->upropset & (PROP_CRITMIN | PROP_WARNMIN)))
               edata->state = ENVSYS_SINVALID;

       edata->value_cur = cur;
}

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

       c_id = dc->dc_readreg(dc, DBCOOL_COMPANYID_REG);
       d_id = dc->dc_readreg(dc, DBCOOL_DEVICEID_REG);
       r_id = dc->dc_readreg(dc, DBCOOL_REVISION_REG);

       /* The EMC6D103S only supports read_byte and since dc->dc_chip is
        * NULL when we call dc->dc_readreg above we use
        * send_byte/receive_byte which doesn't work.
        *
        * So if we only get 0's back then try again with dc->dc_chip
        * set to the EMC6D103S_DEVICEID and which doesn't have
        * DBCFLAG_NO_READBYTE set so read_byte will be used
        */
       if ((c_id == 0) && (d_id == 0) && (r_id == 0)) {
               for (i = 0; chip_table[i].company != 0; i++)
                       if ((SMSC_COMPANYID == chip_table[i].company) &&
                           (EMC6D103S_DEVICEID == chip_table[i].device)) {
                               dc->dc_chip = &chip_table[i];
                               break;
                       }
               c_id = dc->dc_readreg(dc, DBCOOL_COMPANYID_REG);
               d_id = dc->dc_readreg(dc, DBCOOL_DEVICEID_REG);
               r_id = dc->dc_readreg(dc, 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)) {
                       dc->dc_chip = &chip_table[i];
                       return i;
               }

       aprint_debug("dbcool_chip_ident: addr 0x%02x c_id 0x%02x d_id 0x%02x"
                       " r_id 0x%02x: No match.\n", dc->dc_addr, c_id, d_id,
                       r_id);

       return -1;
}

/*
* Retrieve sensor limits from the chip registers
*/
static void
dbcool_get_limits(struct sysmon_envsys *sme, envsys_data_t *edata,
                 sysmon_envsys_lim_t *limits, uint32_t *props)
{
       int index = edata->sensor;
       struct dbcool_softc *sc = sme->sme_cookie;

       *props &= ~(PROP_CRITMIN | PROP_CRITMAX);
       switch (edata->units) {
           case ENVSYS_STEMP:
               dbcool_get_temp_limits(sc, index, limits, props);
               break;
           case ENVSYS_SVOLTS_DC:
               dbcool_get_volt_limits(sc, index, limits, props);
               break;
           case ENVSYS_SFANRPM:
               dbcool_get_fan_limits(sc, index, limits, props);

           /* FALLTHROUGH */
           default:
               break;
       }
       *props &= ~PROP_DRIVER_LIMITS;

       /* If both limits provided, make sure they're sane */
       if ((*props & PROP_CRITMIN) &&
           (*props & PROP_CRITMAX) &&
           (limits->sel_critmin >= limits->sel_critmax))
               *props &= ~(PROP_CRITMIN | PROP_CRITMAX);

       /*
        * If this is the first time through, save these values
        * in case user overrides them and then requests a reset.
        */
       if (sc->sc_defprops[index] == 0) {
               sc->sc_defprops[index] = *props | PROP_DRIVER_LIMITS;
               sc->sc_deflims[index]  = *limits;
       }
}

static void
dbcool_get_temp_limits(struct dbcool_softc *sc, int idx,
                      sysmon_envsys_lim_t *lims, uint32_t *props)
{
       struct reg_list *reg = sc->sc_regs[idx];
       uint8_t lo_lim, hi_lim;

       lo_lim = sc->sc_dc.dc_readreg(&sc->sc_dc, reg->lo_lim_reg);
       hi_lim = sc->sc_dc.dc_readreg(&sc->sc_dc, reg->hi_lim_reg);

       if (sc->sc_temp_offset) {
               if (lo_lim > 0x01) {
                       lims->sel_critmin = lo_lim - sc->sc_temp_offset;
                       *props |= PROP_CRITMIN;
               }
               if (hi_lim != 0xff) {
                       lims->sel_critmax = hi_lim - sc->sc_temp_offset;
                       *props |= PROP_CRITMAX;
               }
       } else {
               if (lo_lim != 0x80 && lo_lim != 0x81) {
                       lims->sel_critmin = (int8_t)lo_lim;
                       *props |= PROP_CRITMIN;
               }

               if (hi_lim != 0x7f) {
                       lims->sel_critmax = (int8_t)hi_lim;
                       *props |= PROP_CRITMAX;
               }
       }

       /* Convert temp limits to microKelvin */
       lims->sel_critmin *= 1000000;
       lims->sel_critmin += 273150000;
       lims->sel_critmax *= 1000000;
       lims->sel_critmax += 273150000;
}

static void
dbcool_get_volt_limits(struct dbcool_softc *sc, int idx,
                      sysmon_envsys_lim_t *lims, uint32_t *props)
{
       struct reg_list *reg = sc->sc_regs[idx];
       int64_t limit;
       int nom;

       nom = nominal_voltages[sc->sc_dc.dc_chip->table[idx].nom_volt_index];
       if (nom < 0)
               nom = dbcool_supply_voltage(sc);
       nom *= 1000000;         /* scale for microvolts */

       limit = sc->sc_dc.dc_readreg(&sc->sc_dc, reg->lo_lim_reg);
       if (limit != 0x00 && limit != 0xff) {
               limit *= nom;
               limit /= 0xc0;
               lims->sel_critmin = limit;
               *props |= PROP_CRITMIN;
       }
       limit = sc->sc_dc.dc_readreg(&sc->sc_dc, reg->hi_lim_reg);
       if (limit != 0x00 && limit != 0xff) {
               limit *= nom;
               limit /= 0xc0;
               lims->sel_critmax = limit;
               *props |= PROP_CRITMAX;
       }
}

static void
dbcool_get_fan_limits(struct dbcool_softc *sc, int idx,
                     sysmon_envsys_lim_t *lims, uint32_t *props)
{
       struct reg_list *reg = sc->sc_regs[idx];
       int32_t limit;

       limit = dbcool_read_rpm(sc, reg->lo_lim_reg);
       if (limit) {
               lims->sel_critmin = limit;
               *props |= PROP_CRITMIN;
       }
}

/*
* Update sensor limits in the chip registers
*/
static void
dbcool_set_limits(struct sysmon_envsys *sme, envsys_data_t *edata,
                 sysmon_envsys_lim_t *limits, uint32_t *props)
{
       int index = edata->sensor;
       struct dbcool_softc *sc = sme->sme_cookie;

       if (limits == NULL) {
               limits = &sc->sc_deflims[index];
               props  = &sc->sc_defprops[index];
       }
       switch (edata->units) {
           case ENVSYS_STEMP:
               dbcool_set_temp_limits(sc, index, limits, props);
               break;
           case ENVSYS_SVOLTS_DC:
               dbcool_set_volt_limits(sc, index, limits, props);
               break;
           case ENVSYS_SFANRPM:
               dbcool_set_fan_limits(sc, index, limits, props);

           /* FALLTHROUGH */
           default:
               break;
       }
       *props &= ~PROP_DRIVER_LIMITS;
}

static void
dbcool_set_temp_limits(struct dbcool_softc *sc, int idx,
                      sysmon_envsys_lim_t *lims, uint32_t *props)
{
       struct reg_list *reg = sc->sc_regs[idx];
       int32_t limit;

       if (*props & PROP_CRITMIN) {
               limit = lims->sel_critmin - 273150000;
               limit /= 1000000;
               if (sc->sc_temp_offset) {
                       limit += sc->sc_temp_offset;
                       if (limit < 0)
                               limit = 0;
                       else if (limit > 255)
                               limit = 255;
               } else {
                       if (limit < -127)
                               limit = -127;
                       else if (limit > 127)
                               limit = 127;
               }
               sc->sc_dc.dc_writereg(&sc->sc_dc, reg->lo_lim_reg,
                                     (uint8_t)limit);
       } else if (*props & PROP_DRIVER_LIMITS) {
               if (sc->sc_temp_offset)
                       limit = 0x00;
               else
                       limit = 0x80;
               sc->sc_dc.dc_writereg(&sc->sc_dc, reg->lo_lim_reg,
                                     (uint8_t)limit);
       }

       if (*props & PROP_CRITMAX) {
               limit = lims->sel_critmax - 273150000;
               limit /= 1000000;
               if (sc->sc_temp_offset) {
                       limit += sc->sc_temp_offset;
                       if (limit < 0)
                               limit = 0;
                       else if (limit > 255)
                               limit = 255;
               } else {
                       if (limit < -127)
                               limit = -127;
                       else if (limit > 127)
                               limit = 127;
               }
               sc->sc_dc.dc_writereg(&sc->sc_dc, reg->hi_lim_reg,
                                     (uint8_t)limit);
       } else if (*props & PROP_DRIVER_LIMITS) {
               if (sc->sc_temp_offset)
                       limit = 0xff;
               else
                       limit = 0x7f;
               sc->sc_dc.dc_writereg(&sc->sc_dc, reg->hi_lim_reg,
                                     (uint8_t)limit);
       }
}

static void
dbcool_set_volt_limits(struct dbcool_softc *sc, int idx,
                      sysmon_envsys_lim_t *lims, uint32_t *props)
{
       struct reg_list *reg = sc->sc_regs[idx];
       int64_t limit;
       int nom;

       nom = nominal_voltages[sc->sc_dc.dc_chip->table[idx].nom_volt_index];
       if (nom < 0)
               nom = dbcool_supply_voltage(sc);
       nom *= 1000000;         /* scale for microvolts */

       if (*props & PROP_CRITMIN) {
               limit = lims->sel_critmin;
               limit *= 0xc0;
               limit /= nom;
               if (limit > 0xff)
                       limit = 0xff;
               else if (limit < 0)
                       limit = 0;
               sc->sc_dc.dc_writereg(&sc->sc_dc, reg->lo_lim_reg, limit);
       } else if (*props & PROP_DRIVER_LIMITS)
               sc->sc_dc.dc_writereg(&sc->sc_dc, reg->lo_lim_reg, 0);

       if (*props & PROP_CRITMAX) {
               limit = lims->sel_critmax;
               limit *= 0xc0;
               limit /= nom;
               if (limit > 0xff)
                       limit = 0xff;
               else if (limit < 0)
                       limit = 0;
               sc->sc_dc.dc_writereg(&sc->sc_dc, reg->hi_lim_reg, limit);
       } else if (*props & PROP_DRIVER_LIMITS)
               sc->sc_dc.dc_writereg(&sc->sc_dc, reg->hi_lim_reg, 0xff);
}

static void
dbcool_set_fan_limits(struct dbcool_softc *sc, int idx,
                     sysmon_envsys_lim_t *lims, uint32_t *props)
{
       struct reg_list *reg = sc->sc_regs[idx];
       int32_t limit, dividend;

       if (*props & PROP_CRITMIN) {
               limit = lims->sel_critmin;
               if (limit == 0)
                       limit = 0xffff;
               else {
                       if (sc->sc_dc.dc_chip->flags & DBCFLAG_ADM1030)
                               dividend = 11250 * 60;
                       else
                               dividend = 90000 * 60;
                       limit = limit / dividend;
                       if (limit > 0xffff)
                               limit = 0xffff;
               }
               sc->sc_dc.dc_writereg(&sc->sc_dc, reg->lo_lim_reg,
                                     limit & 0xff);
               limit >>= 8;
               sc->sc_dc.dc_writereg(&sc->sc_dc, reg->lo_lim_reg + 1,
                                     limit & 0xff);
       } else if (*props & PROP_DRIVER_LIMITS) {
               sc->sc_dc.dc_writereg(&sc->sc_dc, reg->lo_lim_reg, 0xff);
               sc->sc_dc.dc_writereg(&sc->sc_dc, reg->lo_lim_reg + 1, 0xff);
       }
}

MODULE(MODULE_CLASS_DRIVER, dbcool, "iic,sysmon_envsys");

#ifdef _MODULE
#include "ioconf.c"
#endif

static int
dbcool_modcmd(modcmd_t cmd, void *opaque)
{
       int error = 0;
#ifdef _MODULE
       static struct sysctllog *dbcool_sysctl_clog;
#endif

       switch (cmd) {
       case MODULE_CMD_INIT:
#ifdef _MODULE
               error = config_init_component(cfdriver_ioconf_dbcool,
                   cfattach_ioconf_dbcool, cfdata_ioconf_dbcool);
               sysctl_dbcoolsetup(&dbcool_sysctl_clog);
#endif
               return error;
       case MODULE_CMD_FINI:
#ifdef _MODULE
               error = config_fini_component(cfdriver_ioconf_dbcool,
                   cfattach_ioconf_dbcool, cfdata_ioconf_dbcool);
               sysctl_teardown(&dbcool_sysctl_clog);
#endif
               return error;
       default:
               return ENOTTY;
       }
}