/* $NetBSD: adt7462.c,v 1.1 2026/06/03 11:11:18 jdc Exp $ */

/*-
 * Copyright (c) 2026 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Julian Coleman.
 *
 * 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.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: adt7462.c,v 1.1 2026/06/03 11:11:18 jdc Exp $");

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

#include <dev/sysmon/sysmonvar.h>

#include <dev/i2c/i2cvar.h>
#include <dev/i2c/adt7462reg.h>

/* Temperature sensors descriptions (envstat) */
static const char* temp_descs[] =
    { "local", "remote 1", "remote 2", "remote 3" };

/* PWM control channel names (sysctl) */
static const char* pwm_ctrl_names[] =
    { "local", "remote1", "remote2", "remote3",
    "off", "local_remote3", "all", "manual" };
/* Temperature sensors names (sysctl) */
static const char* temp_names[] =
    { "local", "remote1", "remote2", "remote3" };

#define ADT7462_DESC_LEN	SYSCTL_NAMELEN

/* Voltage/analog sensors registers */
struct adt7462_volts_regs {
	uint8_t v_reg, h_reg, l_reg;
};
static struct adt7462_volts_regs adt7462_volts_table[] = {
	{ ADT7462_PIN23V_VAL, ADT7462_PIN23V_HIGH, ADT7462_PIN23V_LOW },
	{ ADT7462_PIN24V_VAL, ADT7462_PIN24V_HIGH, ADT7462_PIN24V_LOW },
	{ ADT7462_PIN25V_VAL, ADT7462_PIN25V_HIGH, ADT7462_PIN25V_LOW },
	{ ADT7462_PIN26V_VAL, ADT7462_PIN26V_HIGH, ADT7462_PIN26V_LOW },
	{ ADT7462_15V1_VAL, ADT7462_15V1_HIGH, ADT7462_15V1_LOW }, /* Pin 28 */
	{ ADT7462_15V2_VAL, ADT7462_15V2_HIGH, ADT7462_15V2_LOW }, /* Pin 29 */
	{ ADT7462_33V_VAL, ADT7462_33V_HIGH, ADT7462_33V_LOW }, /* Pin 13 */
	{ ADT7462_12V1_VAL, ADT7462_12V1_HIGH, ADT7462_12V1_LOW }, /* Pin 7 */
	{ ADT7462_12V2_VAL, ADT7462_12V2_HIGH, ADT7462_12V2_LOW }, /* Pin 8 */
	{ ADT7462_12V3_VAL, ADT7462_12V3_HIGH, ADT7462_12V3_LOW }, /* Pin 22 */
	{ ADT7462_PIN19V_VAL, ADT7462_PIN19V_HIGH, ADT7462_PIN19V_LOW },
	{ ADT7462_PIN15V_VAL, ADT7462_PIN15V_HIGH, ADT7462_PIN15V_LOW },
	{ ADT7462_5V_VAL, ADT7462_5V_HIGH, ADT7462_5V_LOW }, /* Pin 21 */
};

/* 1 LSB values for voltages (datasheet table 16) in mV */
#define ADT7462_SCALE_12V	62500
#define ADT7462_SCALE_5V	26000
#define ADT7462_SCALE_VCCP	6250
#define ADT7462_SCALE_VVID	12500	/* Vccp when VID's enabled */
#define ADT7462_SCALE_3_3V	17200
#define ADT7462_SCALE_VBAT	15600
#define ADT7462_SCALE_2_5V	13000
#define ADT7462_SCALE_1_8V	9400
#define ADT7462_SCALE_1_5V	7800
#define ADT7462_SCALE_1_25V	6500
#define ADT7462_SCALE_1_2V	6250
#define ADT7462_SCALE_0_9V	4690

/* Trange values (16 entries) rounded to the nearest integer */
static const int trange_vals[] =
    { 2, 2, 3, 4, 5, 7, 8, 10,
    13, 16, 20, 27, 32, 40, 53, 80 };
#define ADT7462_TRANGE_LEN	(sizeof(trange_vals) / sizeof(trange_vals[0]))

/* Maximum number of each type of sensor */
#define ADT7462_MAX_FANS	8
#define ADT7462_MAX_TEMPS	4
#define ADT7462_MAX_VOLTS	13
#define ADT7462_MAX_FAULTS	1
#define ADT7462_MAX_SENSORS	\
    (ADT7462_MAX_FANS + ADT7462_MAX_TEMPS + \
    ADT7462_MAX_VOLTS + ADT7462_MAX_FAULTS)
#define  ADT7462_NUM_PWM	4

/* Fan/temp/volt/fault sensor offsets */
#define ADT7462_FAN_NUM(x)	(x)
#define ADT7462_TEMP_NUM(x)	(x + ADT7462_MAX_FANS)
#define ADT7462_VOLT_NUM(x)	(x + ADT7462_MAX_FANS + ADT7462_MAX_TEMPS)
#define ADT7462_FAULT_NUM(x)	\
    (x + ADT7462_MAX_FANS + ADT7462_MAX_TEMPS + ADT7462_MAX_VOLTS)

/* Fan conversions */
#define VAL_TO_SPEED(msb, lsb)  \
    (ADT7462_TACH_PERIOD / ((msb << 8) + lsb))
#define SPEED_TO_MSB(spd)	\
    (((ADT7462_TACH_PERIOD / spd) >> 8) & 0xff)

/* Temperature conversions */
#define VAL_TO_TEMP(msb, lsb)	\
    (ADT7462_TEMP_BASE + msb * 1000000 + (lsb >> 6) * 250000)
#define TEMP_TO_MSB(temp)	\
    (((temp - ADT7462_TEMP_BASE) / 1000000) & 0xff)

/* Voltage conversions */
#define VAL_TO_VOLT(val, scale)	\
    (val * scale)
#define VOLT_TO_MSB(volt, scale)	\
    ((volt / scale) & 0xff)

struct adt7462_softc {
	device_t sc_dev;
	i2c_tag_t sc_tag;
	int sc_address;
	bool sc_monitor;

	int sc_nfans, sc_ntemps, sc_nvolts;
	int sc_env_map[ADT7462_MAX_SENSORS];  /* Envsys numbers to sensors */
	int sc_vscale[ADT7462_MAX_VOLTS];  /* Scale for voltages */
	struct sysmon_envsys *sc_sme;
	envsys_data_t sc_sensor[ADT7462_MAX_SENSORS];
	uint8_t sc_therm1[ADT7462_MAX_SENSORS];
	uint8_t sc_therm2[ADT7462_MAX_SENSORS];
	int sc_crit_therm[ADT7462_MAX_SENSORS];
	uint8_t sc_highlim[ADT7462_MAX_SENSORS];
	uint8_t sc_lowlim[ADT7462_MAX_SENSORS];
	uint8_t sc_fan_conf;
	char sc_sys_ctrl[ADT7462_NUM_PWM][ADT7462_DESC_LEN]; /* sysctl str */
	struct sysctllog *sc_sysctl_log;
};

static int adt7462_match(device_t, cfdata_t, void *);
static int adt7462_ident(i2c_tag_t, i2c_addr_t, int, uint8_t*);
static void adt7462_attach(device_t, device_t, void *);
static int adt7462_detach(device_t, int);
bool adt7462_pmf_suspend(device_t, const pmf_qual_t *);
bool adt7462_pmf_resume(device_t, const pmf_qual_t *);

static int adt7462_start_monitor(struct adt7462_softc *, int);
static int adt7462_stop_monitor(struct adt7462_softc *);
static int adt7462_setup_fans(struct adt7462_softc *, uint8_t *);
static int adt7462_setup_temps(struct adt7462_softc *, uint8_t *);
static int adt7462_setup_volts(struct adt7462_softc *, uint8_t *);
static int adt7462_setup_faults(struct adt7462_softc *);
static int adt7462_setup_sysctl(struct adt7462_softc *, uint8_t *);

void adt7462_refresh(struct sysmon_envsys *, envsys_data_t *);
static void adt7462_read_fan_val(struct adt7462_softc *, envsys_data_t *);
static void adt7462_read_temp_val(struct adt7462_softc *, envsys_data_t *);
static void adt7462_read_volt_val(struct adt7462_softc *, envsys_data_t *);
static void adt7462_read_fault_val(struct adt7462_softc *, envsys_data_t *);

void adt7462_get_limits(struct sysmon_envsys *, envsys_data_t *,
    sysmon_envsys_lim_t *, uint32_t *);
void adt7462_set_limits(struct sysmon_envsys *, envsys_data_t *,
    sysmon_envsys_lim_t *, uint32_t *);
static void adt7462_get_fan_limits(struct adt7462_softc *,
    envsys_data_t *, sysmon_envsys_lim_t *, uint32_t *);
static void adt7462_get_temp_limits(struct adt7462_softc *,
    envsys_data_t *, sysmon_envsys_lim_t *, uint32_t *);
static void adt7462_get_volt_limits(struct adt7462_softc *,
    envsys_data_t *, sysmon_envsys_lim_t *, uint32_t *);
static void adt7462_set_fan_limits(struct adt7462_softc *,
    envsys_data_t *, sysmon_envsys_lim_t *, uint32_t *);
static void adt7462_set_temp_limits(struct adt7462_softc *,
    envsys_data_t *, sysmon_envsys_lim_t *, uint32_t *);
static void adt7462_set_volt_limits(struct adt7462_softc *,
    envsys_data_t *, sysmon_envsys_lim_t *, uint32_t *);

static int adt7462_pwm_duty(SYSCTLFN_ARGS);
static int adt7462_trange(SYSCTLFN_ARGS);
static int adt7462_tmin(SYSCTLFN_ARGS);
static int adt7462_op_point(SYSCTLFN_ARGS);

static int adt7462_read_reg(i2c_tag_t, i2c_addr_t, uint8_t, uint8_t *);
static int adt7462_write_reg(i2c_tag_t, i2c_addr_t, uint8_t, uint8_t);

CFATTACH_DECL_NEW(adt7462sm, sizeof(struct adt7462_softc),
	adt7462_match, adt7462_attach, adt7462_detach, NULL);

static const struct device_compatible_entry compat_data[] = {
	{ .compat = "i2c-adt7462" },
	DEVICE_COMPAT_EOL
};

static int
adt7462_match(device_t parent, cfdata_t cf, void *aux)
{
	struct i2c_attach_args *ia = aux;
	int match_result;
	uint8_t rev;

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

	if ((ia->ia_addr == ADT7462_ADDR1 || ia->ia_addr == ADT7462_ADDR2)
	    && adt7462_ident(ia->ia_tag, ia->ia_addr, 1, &rev))
		return I2C_MATCH_ADDRESS_AND_PROBE;

	return 0;
}

static int
adt7462_ident(i2c_tag_t tag, i2c_addr_t addr, int probe_only, uint8_t *rev)
{
	uint8_t reg, val;
	int err;

	/* Device, company and revision ID */
	reg = ADT7462_DEV_ID;
	err = adt7462_read_reg(tag, addr, reg, &val);
	if (err || val != ADT7462_DEV_ID_VAL) {
		if (!probe_only)
			aprint_verbose("adt7462_ident: "
			    "device ID invalid or missing\n");
		return 0;
	}
	reg = ADT7462_COMP_ID;
	err = adt7462_read_reg(tag, addr, reg, &val);
	if (err || val != ADT7462_COMP_ID_VAL) {
		if (!probe_only)
			aprint_verbose("adt7462_ident: "
			    "company ID invalid or missing\n");
		return 0;
	}
	reg = ADT7462_REV_ID;
	err = adt7462_read_reg(tag, addr, reg, rev);
	if (err || *rev != ADT7462_REV_ID_VAL) {
		if (!probe_only)
			aprint_verbose("adt7462_ident: "
			    "revision invalid or missing\n");
		return 0;
	}
	return 1;
}

static void
adt7462_attach(device_t parent, device_t self, void *aux)
{
	struct adt7462_softc *sc = device_private(self);
	struct i2c_attach_args *ia = aux;
	prop_dictionary_t props = device_properties(self);
	uint8_t reg, rev, val, pin_cfg[4];

	sc->sc_tag = ia->ia_tag;
	sc->sc_address = ia->ia_addr;
	sc->sc_dev = self;

	/* Property override for the number of fans */
	if (prop_dictionary_get_uint8(props, "fan_conf", &sc->sc_fan_conf) == 0)
		sc->sc_fan_conf = 0xff;	/* 4 + 4 fans */

	(void) adt7462_ident(sc->sc_tag, sc->sc_address, 0, &rev);
	aprint_normal(": ADT7462 system monitor: rev. 0x%x\n", rev);

	if (adt7462_start_monitor(sc, 1))
		return;

	/* Read the pin config registers for fan/temp/volt setup. */
	reg = ADT7462_PIN_CONF1;
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
		aprint_error_dev(sc->sc_dev, ": unable to read pin conf1\n");
		return;
	}
	pin_cfg[0] = val;
	reg = ADT7462_PIN_CONF2;
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
		aprint_error_dev(sc->sc_dev, ": unable to read pin conf2\n");
		return;
	}
	pin_cfg[1] = val;
	reg = ADT7462_PIN_CONF3;
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
		aprint_error_dev(sc->sc_dev, ": unable to read pin conf3\n");
		return;
	}
	pin_cfg[2] = val;
	reg = ADT7462_PIN_CONF4;
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
		aprint_error_dev(sc->sc_dev, ": unable to read pin conf4\n");
		return;
	}
	pin_cfg[3] = val;

	sc->sc_sme = sysmon_envsys_create();

	sc->sc_nfans = 0;
	sc->sc_ntemps = 0;
	sc->sc_nvolts = 0;
	if (adt7462_setup_fans(sc, pin_cfg))
		goto bad;
	if (adt7462_setup_temps(sc, pin_cfg))
		goto bad;
	if (adt7462_setup_volts(sc, pin_cfg))
		goto bad;
	if (adt7462_setup_faults(sc))
		goto bad;
	aprint_normal_dev(self, "%d fans, %d temperatures, %d voltages\n",
	    sc->sc_nfans, sc->sc_ntemps, sc->sc_nvolts);

	sc->sc_sme->sme_name = device_xname(self);
	sc->sc_sme->sme_cookie = sc;
	sc->sc_sme->sme_refresh = adt7462_refresh;
	sc->sc_sme->sme_get_limits = adt7462_get_limits;
	sc->sc_sme->sme_set_limits = adt7462_set_limits;
	if (sysmon_envsys_register(sc->sc_sme)) {
		aprint_error_dev(self,
		    "unable to register with sysmon\n");
		goto bad;
	}

	if (!pmf_device_register(self,
	    adt7462_pmf_suspend, adt7462_pmf_resume)) {
		aprint_error_dev(self, "couldn't establish power handler\n");
		goto bad2;
	}

	if (adt7462_setup_sysctl(sc, pin_cfg))
		goto bad2;

	return;

bad2:
	sysmon_envsys_unregister(sc->sc_sme);
	sc->sc_sme = NULL;
bad:
	if (sc->sc_sme != NULL) {
		sysmon_envsys_destroy(sc->sc_sme);
		sc->sc_sme = NULL;
	}
	return;
}

/* Stop (suspend/detach) and restart (resume) monitoring, if we started it. */
bool
adt7462_pmf_suspend(device_t dev, const pmf_qual_t *qual)
{
	struct adt7462_softc *sc = device_private(dev);

	if (sc->sc_monitor == 1) {
		if (adt7462_stop_monitor(sc))
			return false;
	}
	return true;
}

bool
adt7462_pmf_resume(device_t dev, const pmf_qual_t *qual)
{
	struct adt7462_softc *sc = device_private(dev);

	if (sc->sc_monitor == 1) {
		if (adt7462_start_monitor(sc, 0))
			return false;
	}
	return true;
}

static int
adt7462_detach(device_t self, int flags)
{
	struct adt7462_softc *sc = device_private(self);

	pmf_device_deregister(self);

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

	if (sc->sc_monitor == 1) {
		if (adt7462_stop_monitor(sc))
			return 1;
	}
	return 0;
}

static int
adt7462_start_monitor(struct adt7462_softc *sc, int print)
{
	uint8_t reg, val;

	/*
	 * Start monitoring if not already monitoring.
	 * Wait 1.0s for the fan readings to stabilise.
	 */
	reg = ADT7462_CONF1;
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
		aprint_error_dev(sc->sc_dev, ": unable to read conf1\n");
		return 1;
	}
	if (!(val & ADT7462_CONF1_MONITOR)) {
		sc->sc_monitor = 1;
		val |= ADT7462_CONF1_MONITOR;
		if (adt7462_write_reg(sc->sc_tag, sc->sc_address,
		    reg, val) != 0) {
			aprint_error_dev(sc->sc_dev,
			    ": unable to write conf1\n");
			return 1;
		}
		if (print)
			aprint_normal_dev(sc->sc_dev,
			    ": starting monitoring, "
			    "waiting 1.0s for readings\n");
		delay(1000000);
	} else
		sc->sc_monitor = 0;
	return 0;
}

static int
adt7462_stop_monitor(struct adt7462_softc *sc)
{
	uint8_t reg, val;

	reg = ADT7462_CONF1;
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
		aprint_error_dev(sc->sc_dev, ": unable to read conf1\n");
		return 1;
	}

	val &= ~ADT7462_CONF1_MONITOR;
	if (adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val) != 0) {
		aprint_error_dev(sc->sc_dev, ": unable to write conf1\n");
		return 1;
	}
	return 0;
}

static int
adt7462_setup_fans(struct adt7462_softc *sc, uint8_t *pin_cfg)
{
	int i, map, snum;
	uint8_t reg, val, fans;

	/* Check tach enable register to see which tachs are enabled. */
	reg = ADT7462_TACH_EN;
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
		aprint_error_dev(sc->sc_dev, "unable to read tach enable\n");
		return 1;
	}
	fans = val & sc->sc_fan_conf;

	/* Don't check the fan present register - it might not be set up. */

	for (i = 0; i < ADT7462_MAX_FANS; i++) {
		/* Check tach mask and pin1/pin2 configurations. */
		if (!ADT7462_FAN_TACH_EN(fans, i) ||
		    !ADT7462_PCR1_TACH(pin_cfg[0], i) ||
		    !ADT7462_PCR2_TACH(pin_cfg[1], i))
			continue;

		snum = ADT7462_FAN_NUM(i);

		/* Store initial limit */
		reg = ADT7462_TACH_LIMIT(i);
		if (adt7462_read_reg(sc->sc_tag, sc->sc_address,
		    reg, &val) != 0) {
			aprint_error_dev(sc->sc_dev,
			    "unable to read fan %d limit\n", i);
			return 1;
		}
		sc->sc_highlim[snum] = val;

		/* Set up sysmon sensor */
		sc->sc_sensor[snum].units = ENVSYS_SFANRPM;
		sc->sc_sensor[snum].state = ENVSYS_SINVALID;
		sc->sc_sensor[snum].flags = ENVSYS_FMONLIMITS;
		snprintf(sc->sc_sensor[snum].desc,
		    sizeof(sc->sc_sensor[snum].desc), "fan %d", snum);
		if (sysmon_envsys_sensor_attach(
		    sc->sc_sme, &sc->sc_sensor[snum])) {
			aprint_error_dev(sc->sc_dev,
			    "unable to attach fan %d at sysmon\n", i);
			return 1;
		}
		map = sc->sc_sensor[snum].sensor;
		sc->sc_env_map[map] = i;
		sc->sc_nfans++;
	}
	return 0;
}

static int
adt7462_setup_temps(struct adt7462_softc *sc, uint8_t *pin_cfg)
{
	int i, map, snum;
	uint8_t reg, val, val2;

	for (i = 0; i < ADT7462_MAX_TEMPS; i++) {
		/* Check pin1 configurations. */
		if (!ADT7462_PCR1_TEMP(pin_cfg[0], i))
			continue;

		snum = ADT7462_TEMP_NUM(i);

		/* Store initial limits */
		reg = ADT7462_TEMP_THERM1(i);
		if (adt7462_read_reg(sc->sc_tag, sc->sc_address,
		    reg, &val) != 0) {
			aprint_error_dev(sc->sc_dev,
			    "unable to read temp %d therm1 limit\n", i);
			return 1;
		}
		sc->sc_therm1[snum] = val;
		reg = ADT7462_TEMP_THERM2(i);
		if (adt7462_read_reg(sc->sc_tag, sc->sc_address,
		    reg, &val2) != 0) {
			aprint_error_dev(sc->sc_dev,
			    "unable to read temp %d therm2 limit\n", i);
			return 1;
		}
		sc->sc_therm2[snum] = val2;
		/* Store lowest therm value as critmax. */
		if (val <= val2)
			sc->sc_crit_therm[snum] = 1;
		else
			sc->sc_crit_therm[snum] = 2;

		reg = ADT7462_TEMP_HIGH(i);
		if (adt7462_read_reg(sc->sc_tag, sc->sc_address,
		    reg, &val) != 0) {
			aprint_error_dev(sc->sc_dev,
			    "unable to read temp %d high limit\n", i);
			return 1;
		}
		sc->sc_highlim[snum] = val;

		reg = ADT7462_TEMP_LOW(i);
		if (adt7462_read_reg(sc->sc_tag, sc->sc_address,
		    reg, &val) != 0) {
			aprint_error_dev(sc->sc_dev,
			    "unable to read temp %d low limit\n", i);
			return 1;
		}
		sc->sc_lowlim[snum] = val;

		/* Set up sysmon sensor */
		strlcpy(sc->sc_sensor[snum].desc, temp_descs[i],
			sizeof(sc->sc_sensor[snum].desc));

		sc->sc_sensor[snum].units = ENVSYS_STEMP;
		sc->sc_sensor[snum].state = ENVSYS_SINVALID;
		sc->sc_sensor[snum].flags =
		    ENVSYS_FMONLIMITS | ENVSYS_FHAS_ENTROPY;
		if (sysmon_envsys_sensor_attach(
		    sc->sc_sme, &sc->sc_sensor[snum])) {
			aprint_error_dev(sc->sc_dev,
			    "unable to attach temp %d at sysmon\n", i);
			return 1;
		}
		map = sc->sc_sensor[snum].sensor;
		sc->sc_env_map[map] = i;
		sc->sc_ntemps++;
	}
	return 0;
}

static int
adt7462_setup_volts(struct adt7462_softc *sc, uint8_t *pin_cfg)
{
	int i, map, snum;
	uint8_t reg, val;
	char desc[ADT7462_DESC_LEN];

	for (i = 0; i < ADT7462_MAX_VOLTS; i++) {
		snum = ADT7462_VOLT_NUM(i);

		/* Invidual configuration for each sensor */
		switch (i) {
		case 0:		/* Pin 23 (always measures volts) */
			if (ADT7462_PCR2_P23_25V(pin_cfg[1])) {
				strcpy(desc, "V2.5 1");
				sc->sc_vscale[i] = ADT7462_SCALE_2_5V;
			} else if (ADT7462_PCR2_P23_18V(pin_cfg[1])) {
				strcpy(desc, "V1.8 1");
				sc->sc_vscale[i] = ADT7462_SCALE_1_8V;
			} else if (ADT7462_PCR2_P23_15V(pin_cfg[1])) {
				strcpy(desc, "V1.5 1");
				sc->sc_vscale[i] = ADT7462_SCALE_1_5V;
			} else {
				strcpy(desc, "Vccp 1");
				if (ADT7462_PCR1_VIDS(pin_cfg[0]))
					sc->sc_vscale[i] = ADT7462_SCALE_VVID;
				else
					sc->sc_vscale[i] = ADT7462_SCALE_VCCP;
			}
			break;
		case 1:		/* Pin 24 (always measures volts) */
			if (ADT7462_PCR3_P24_25V(pin_cfg[2])) {
				strcpy(desc, "V2.5 2");
				sc->sc_vscale[i] = ADT7462_SCALE_2_5V;
			} else if (ADT7462_PCR3_P24_18V(pin_cfg[2])) {
				strcpy(desc, "V1.8 2");
				sc->sc_vscale[i] = ADT7462_SCALE_1_8V;
			} else if (ADT7462_PCR3_P24_15V(pin_cfg[2])) {
				strcpy(desc, "V1.5 2");
				sc->sc_vscale[i] = ADT7462_SCALE_1_5V;
			} else {
				strcpy(desc, "Vccp 2");
				if (ADT7462_PCR1_VIDS(pin_cfg[0]))
					sc->sc_vscale[i] = ADT7462_SCALE_VVID;
				else
					sc->sc_vscale[i] = ADT7462_SCALE_VCCP;
			}
			break;
		case 2:		/* Pin 25 (check pin config 3) */
			if (ADT7462_PCR3_P25_33V(pin_cfg[2])) {
				strcpy(desc, "V3.3 1");
				sc->sc_vscale[i] = ADT7462_SCALE_3_3V;
			} else if (ADT7462_PCR3_P25_12V(pin_cfg[2])) {
				strcpy(desc, "V1.2 1");
				sc->sc_vscale[i] = ADT7462_SCALE_1_2V;
			} else
				continue;	/* Not voltage */
			break;
		case 3:		/* Pin 26 (check pin config 3) */
			if (ADT7462_PCR3_P26_VBAT(pin_cfg[2])) {
				strcpy(desc, "Vbatt");
				sc->sc_vscale[i] = ADT7462_SCALE_VBAT;
			} else if (ADT7462_PCR3_P26_12V(pin_cfg[2])) {
				strcpy(desc, "V1.2 2");
				sc->sc_vscale[i] = ADT7462_SCALE_1_2V;
			} else
				continue;	/* Not voltage */
			break;
		case 4:		/* Pin 28 (check pin config 1 and 4) */
			if (ADT7462_PCR1_VIDS(pin_cfg[0]))
				continue;
			if (!ADT7462_PCR4_P28_15V(pin_cfg[3]))
				continue;
			strcpy(desc, "V1.5 3");
			sc->sc_vscale[i] = ADT7462_SCALE_1_5V;
			break;
		case 5:		/* Pin 29 (check pin config 1 and 4) */
			if (ADT7462_PCR1_VIDS(pin_cfg[0]))
				continue;
			if (!ADT7462_PCR4_P29_15V(pin_cfg[3]))
				continue;
			strcpy(desc, "V1.5 4");
			sc->sc_vscale[i] = ADT7462_SCALE_1_5V;
			break;
		case 6:		/* Pin 13 (check pin config 2) */
			if (ADT7462_PCR2_P13_PWM4(pin_cfg[1]))
				continue;
			strcpy(desc, "V3.3 2");
			sc->sc_vscale[i] = ADT7462_SCALE_3_3V;
			break;
		case 7:		/* Pin 7 (check pin config 1) */
			if (!ADT7462_PCR1_PIN7_V(pin_cfg[0]))
				continue;
			strcpy(desc, "V12 1");
			sc->sc_vscale[i] = ADT7462_SCALE_12V;
			break;
		case 8:		/* Pin 8 (check pin config 2) */
			if (ADT7462_PCR2_P8_TACH6(pin_cfg[1]))
				continue;
			strcpy(desc, "V12 2");
			sc->sc_vscale[i] = ADT7462_SCALE_12V;
			break;
		case 9:		/* Pin 22 (check pin config 2) */
			if (ADT7462_PCR2_P22_TACH8(pin_cfg[1]))
				continue;
			strcpy(desc, "V12 3");
			sc->sc_vscale[i] = ADT7462_SCALE_12V;
			break;
		case 10:	/* Pin 19 (check pin config 1 and 2) */
			if (!ADT7462_PCR1_PIN19_V(pin_cfg[0]))
				continue;
			if (ADT7462_PCR2_P19_09V(pin_cfg[1])) {
				strcpy(desc, "V0.9 1");
				sc->sc_vscale[i] = ADT7462_SCALE_0_9V;
			} else {
				strcpy(desc, "V1.25 1");
				sc->sc_vscale[i] = ADT7462_SCALE_1_25V;
			}
			break;
		case 11:	/* Pin 15 (check pin config 1 and 2) */
			if (!ADT7462_PCR1_PIN15_V(pin_cfg[0]))
				continue;
			if (ADT7462_PCR2_P15_18V(pin_cfg[1])) {
				strcpy(desc, "V1.8 3");
				sc->sc_vscale[i] = ADT7462_SCALE_1_8V;
			} else {
				strcpy(desc, "V2.5 3");
				sc->sc_vscale[i] = ADT7462_SCALE_2_5V;
			}
			break;
		case 12:	/* Pin 21 (check pin config 2) */
			if (ADT7462_PCR2_P21_TACH7(pin_cfg[1]))
				continue;
			strcpy(desc, "V5 1");
			sc->sc_vscale[i] = ADT7462_SCALE_5V;
			break;
		}

		/* Store initial limits */
		reg = adt7462_volts_table[i].h_reg;
		if (adt7462_read_reg(sc->sc_tag, sc->sc_address,
		    reg, &val) != 0) {
			aprint_error_dev(sc->sc_dev,
			    "unable to read volt %d high limit\n", i);
			return 1;
		}
		sc->sc_highlim[snum] = val;

		reg = adt7462_volts_table[i].l_reg;
		if (adt7462_read_reg(sc->sc_tag, sc->sc_address,
		    reg, &val) != 0) {
			aprint_error_dev(sc->sc_dev,
			    "unable to read volt %d low limit\n", i);
			return 1;
		}
		sc->sc_lowlim[snum] = val;

		/* Set up sysmon sensor */
		sc->sc_sensor[snum].units = ENVSYS_SVOLTS_DC;
		sc->sc_sensor[snum].state = ENVSYS_SINVALID;
		sc->sc_sensor[snum].flags = ENVSYS_FMONLIMITS;
		strlcpy(sc->sc_sensor[snum].desc, desc,
		    sizeof(sc->sc_sensor[snum].desc));
		if (sysmon_envsys_sensor_attach(
		    sc->sc_sme, &sc->sc_sensor[snum])) {
			aprint_error_dev(sc->sc_dev,
			    "unable to attach volts %d at sysmon\n", i);
			return 1;
		}
		map = sc->sc_sensor[snum].sensor;
		sc->sc_env_map[map] = i;
		sc->sc_nvolts++;
	}
	return 0;
}

static int
adt7462_setup_faults(struct adt7462_softc *sc)
{
	int map, snum;

	snum = ADT7462_FAULT_NUM(0);

	/* Set up sysmon sensor */
	strlcpy(sc->sc_sensor[snum].desc, "fan fault",
	    sizeof(sc->sc_sensor[snum].desc));
	sc->sc_sensor[snum].units = ENVSYS_INTEGER;
	sc->sc_sensor[snum].state = ENVSYS_SINVALID;
	sc->sc_sensor[snum].flags = ENVSYS_FMONCRITICAL;
	if (sysmon_envsys_sensor_attach(
	    sc->sc_sme, &sc->sc_sensor[snum])) {
		aprint_error_dev(sc->sc_dev,
		    "unable to attach fan fault at sysmon\n");
		return 1;
	}
	map = sc->sc_sensor[snum].sensor;
	sc->sc_env_map[map] = 0;
	return 0;
}

static int
adt7462_setup_sysctl(struct adt7462_softc *sc, uint8_t *pin_cfg)
{
	int i, sysnum, temp_is_pwm[ADT7462_MAX_TEMPS];
	uint8_t reg, val, val2, rw;
	char name[ADT7462_DESC_LEN], desc[ADT7462_DESC_LEN];
	const struct sysctlnode *root, *branch, *node;

	/* Default temperatures to manual */
	for (i = 0; i < ADT7462_MAX_TEMPS; i++)
		temp_is_pwm[i] = 0;

	/* Root node */
	root = NULL;
	sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &root,
	    CTLFLAG_READWRITE, CTLTYPE_NODE,
	    device_xname(sc->sc_dev), NULL,
	    NULL, 0, NULL, 0,
	    CTL_HW, CTL_CREATE, CTL_EOL);
	if (root == NULL) {
		aprint_error_dev(sc->sc_dev, "unable to add sysctl root\n");
		return 1;
	}

	/* PWM settings */
	for (i = 0; i < ADT7462_NUM_PWM; i++) {
		/* PWM root */
		snprintf(name, sizeof(name), "pwm%d", i + 1);
		snprintf(desc, sizeof(desc), "PWM output %d", i + 1);
		branch = NULL;
		sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &branch,
		    CTLFLAG_READWRITE, CTLTYPE_NODE,
		    name, SYSCTL_DESCR(desc),
		    NULL, 0, NULL, 0,
		    CTL_HW, root->sysctl_num, CTL_CREATE, CTL_EOL);
		if (branch == NULL) {
			aprint_error_dev(sc->sc_dev,
			    "unable to add sysctl pwm %d\n", i + 1);
			return 1;
		}
		/* Encode the PWM id in the sysctl_num */
		sysnum = 256;

		reg = ADT7462_PWM_CFG(i);
		if (adt7462_read_reg(sc->sc_tag, sc->sc_address,
		    reg, &val) != 0) {
			aprint_error_dev(sc->sc_dev,
			    "unable to read pwm %d configuration\n", i + 1);
			return 1;
		}
		val &= ADT7462_PWM_BHVR_MASK;

		/* PWM duty cycle (RW) if control channel is manual */
		if (val == ADT7462_PWM_BHVR_MAN) {
			node = NULL;
			sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &node,
			    CTLFLAG_READWRITE, CTLTYPE_INT,
			    "duty_cycle", SYSCTL_DESCR("PWM duty cycle"),
			    adt7462_pwm_duty, 1, (void *)sc, 0,
			    CTL_HW, root->sysctl_num, branch->sysctl_num,
			    sysnum + i, CTL_EOL);
			if (node == NULL) {
				aprint_error_dev(sc->sc_dev,
				    "unable to add sysctl pwm duty %d\n",
				    i + 1);
				return 1;
			}
		}
		sysnum *= 2;

		/* Mark temperatures as auto/dynamic */
		switch (val) {
		case ADT7462_PWM_BHVR_LOCAL:
			temp_is_pwm[0] = 1;
			break;
		case ADT7462_PWM_BHVR_REM1:
			temp_is_pwm[1] = 1;
			break;
		case ADT7462_PWM_BHVR_REM2:
			temp_is_pwm[2] = 1;
			break;
		case ADT7462_PWM_BHVR_REM3:
			temp_is_pwm[3] = 1;
			break;
		case ADT7462_PWM_BHVR_LR3:
			temp_is_pwm[0] = 1;
			temp_is_pwm[3] = 1;
			break;
		case ADT7462_PWM_BHVR_ALL:
			temp_is_pwm[0] = 1;
			temp_is_pwm[1] = 1;
			temp_is_pwm[2] = 1;
			temp_is_pwm[3] = 1;
			break;
		}

		/* Base control channel name */
		val2 = (val & ADT7462_PWM_BHVR_MASK) >> ADT7462_PWM_BHVR_SHFT;
		strlcpy(sc->sc_sys_ctrl[i], pwm_ctrl_names[val2],
		    sizeof(sc->sc_sys_ctrl[i]));

		/* Remote 1 and 2 can have dynamic Tmin */
		if (val == ADT7462_PWM_BHVR_REM1 ||
		    val == ADT7462_PWM_BHVR_REM2) {
			reg = ADT7462_TMIN_CAL1;
			if (adt7462_read_reg(sc->sc_tag, sc->sc_address,
			    reg, &val2) != 0) {
				aprint_error_dev(sc->sc_dev,
				    "unable to read tmin control\n");
				return 1;
			}
			if (val == ADT7462_PWM_BHVR_REM1 &&
			    (val2 & ADT7462_REM1_EN)) {
				temp_is_pwm[1] = 2;
				strlcat(sc->sc_sys_ctrl[i], "_dynamic",
				    sizeof(sc->sc_sys_ctrl[i]));
			}
			if (val == ADT7462_PWM_BHVR_REM2 &&
			    (val2 & ADT7462_REM2_EN)) {
				temp_is_pwm[2] = 2;
				strlcat(sc->sc_sys_ctrl[i], "_dynamic",
				    sizeof(sc->sc_sys_ctrl[i]));
			}
		}

		/* Control description (RO) */
		node = NULL;
		sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &node,
		    CTLFLAG_READONLY, CTLTYPE_STRING,
		    "channel", SYSCTL_DESCR("PWM control channel"),
		    NULL, 0, sc->sc_sys_ctrl[i], 0,
		    CTL_HW, root->sysctl_num, branch->sysctl_num,
		    sysnum + i, CTL_EOL);
		if (node == NULL) {
			aprint_error_dev(sc->sc_dev,
			    "unable to add sysctl pwm channel %d\n", i + 1);
			return 1;
		}
	}

	/* Temperature sensor settings */
	for (i = 0; i < ADT7462_MAX_TEMPS; i++) {
		/* Check pin1 configurations. */
		if (!ADT7462_PCR1_TEMP(pin_cfg[0], i))
			continue;

		/* Temperature root */
		snprintf(desc, sizeof(desc), "Temperature sensor %s",
		    temp_descs[i]);
		branch = NULL;
		sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &branch,
		    CTLFLAG_READWRITE, CTLTYPE_NODE,
		    temp_names[i], SYSCTL_DESCR(desc),
		    NULL, 0, NULL, 0,
		    CTL_HW, root->sysctl_num, CTL_CREATE, CTL_EOL);
		if (branch == NULL) {
			aprint_error_dev(sc->sc_dev,
			    "unable to add sysctl temp %s\n", temp_names[i]);
			return 1;
		}
		/* Encode the PWM id in the sysctl_num */
		sysnum = 256;

		/* Automatic = Tmin (RW) / Dynamic = Tmin (RO) */
		if (temp_is_pwm[i] > 0) {
			if (temp_is_pwm[i] == 1)
				rw = CTLFLAG_READWRITE;
			else
				rw = CTLFLAG_READONLY;
			node = NULL;
			sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &node,
			    rw, CTLTYPE_INT,
			    "tmin", SYSCTL_DESCR("Temperature minimum"),
			    adt7462_tmin, 0, (void *)sc, 0,
			    CTL_HW, root->sysctl_num, branch->sysctl_num,
			    sysnum + i, CTL_EOL);
			if (node == NULL) {
				aprint_error_dev(sc->sc_dev,
				    "unable to add sysctl %s tmin\n",
				    temp_names[i]);
				return 1;
			}
		}
		sysnum *= 2;

		/* Automatic/Dynamic = Trange (RW) */
		if (temp_is_pwm[i]) {
			node = NULL;
			sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &node,
			    CTLFLAG_READWRITE, CTLTYPE_INT,
			    "trange", SYSCTL_DESCR("Temperature range"),
			    adt7462_trange, 0, (void *)sc, 0,
			    CTL_HW, root->sysctl_num, branch->sysctl_num,
			    sysnum + i, CTL_EOL);
			if (node == NULL) {
				aprint_error_dev(sc->sc_dev,
				    "unable to add sysctl %s trange\n",
				    temp_names[i]);
				return 1;
			}
		}
		sysnum *= 2;

		/* Dynamic Tmin = operating point (RW) */
		if (temp_is_pwm[i] == 2) {
			node = NULL;
			sysctl_createv(&sc->sc_sysctl_log, 0, NULL, &node,
			    CTLFLAG_READWRITE, CTLTYPE_INT,
			    "oppoint", SYSCTL_DESCR("Operating point"),
			    adt7462_op_point, 0, (void *)sc, 0,
			    CTL_HW, root->sysctl_num, branch->sysctl_num,
			    sysnum + i, CTL_EOL);
			if (node == NULL) {
				aprint_error_dev(sc->sc_dev,
				    "unable to add sysctl %s op point\n",
				    temp_names[i]);
				return 1;
			}
		}
	}
	return 0;
}

void
adt7462_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
{
	struct adt7462_softc *sc = sme->sme_cookie;

	if (edata->sensor < sc->sc_nfans)
		adt7462_read_fan_val(sc, edata);
	else if (edata->sensor < sc->sc_nfans + sc->sc_ntemps)
		adt7462_read_temp_val(sc, edata);
	else if (edata->sensor < sc->sc_nfans + sc->sc_ntemps + sc->sc_nvolts)
		adt7462_read_volt_val(sc, edata);
	else
		adt7462_read_fault_val(sc, edata);
}

static void
adt7462_read_fan_val(struct adt7462_softc *sc, envsys_data_t *edata)
{
	int fan = sc->sc_env_map[edata->sensor];
	uint8_t	reg, lsb, msb;

	/* Read LSB then MSB to ensure correct reading */
	reg = ADT7462_TACH_VAL_LSB(fan);
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &lsb) != 0) {
		edata->state = ENVSYS_SINVALID;
		return;
	}
	reg += 1;	/* MSB register is always LSB register + 1 */
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &msb) != 0) {
		edata->state = ENVSYS_SINVALID;
		return;
	}
	if ((msb == 0xff && lsb == 0xff) || (msb == 0x00 && lsb == 0x00))
		/* Fan missing or stopped */
		edata->value_cur = 0;
	else
		edata->value_cur = VAL_TO_SPEED(msb, lsb);
	edata->state = ENVSYS_SVALID;
}

static void
adt7462_read_temp_val(struct adt7462_softc *sc, envsys_data_t *edata)
{
	int temp = sc->sc_env_map[edata->sensor];
	uint8_t	reg, lsb, msb;

	/* Read LSB then MSB to ensure correct reading */
	reg = ADT7462_TEMP_LSB(temp);
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &lsb) != 0) {
		edata->state = ENVSYS_SINVALID;
		return;
	}
	reg += 1;	/* MSB register is always LSB register + 1 */
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &msb) != 0) {
		edata->state = ENVSYS_SINVALID;
		return;
	}

	edata->value_cur = VAL_TO_TEMP(msb, lsb);
	edata->state = ENVSYS_SVALID;
}

static void
adt7462_read_volt_val(struct adt7462_softc *sc, envsys_data_t *edata)
{
	int volt = sc->sc_env_map[edata->sensor];
	uint8_t	reg, val;

	reg = adt7462_volts_table[volt].v_reg;
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
		edata->state = ENVSYS_SINVALID;
		return;
	}
	edata->value_cur = VAL_TO_VOLT(val, sc->sc_vscale[volt]);
	edata->state = ENVSYS_SVALID;
}

static void
adt7462_read_fault_val(struct adt7462_softc *sc, envsys_data_t *edata)
{
	uint8_t	reg, val;
	int32_t which, total;
	int i, j;

	reg = ADT7462_FAN_STAT_H;
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
		edata->state = ENVSYS_SINVALID;
		return;
	}
	val &= sc->sc_fan_conf;

	/* Change our status based on zero or non-zero value */
	if (val != 0)
		edata->state = ENVSYS_SCRITICAL;
	else
		edata->state = ENVSYS_SVALID;

	/* Create an 8-digit integer for fan positions (0 OK, 1 fault) */
	total = 0;
	for (i = 0; i < 8; i++)
		if (val & (1 << i)) {
			which = 1;
			for (j = 0; j < i; j++)
				which *= 10;
			total += which;
		}
	if (total != edata->value_cur)
		aprint_normal_dev(sc->sc_dev,
		    "fan fault status change: %08d -> %08d\n",
		    edata->value_cur, total);
	edata->value_cur = total;
}

void
adt7462_get_limits(struct sysmon_envsys *sme, envsys_data_t *edata,
	sysmon_envsys_lim_t *limits, uint32_t *props)
{
	struct adt7462_softc *sc = sme->sme_cookie;

	if (edata->sensor < sc->sc_nfans)
		adt7462_get_fan_limits(sc, edata, limits, props);
	else if (edata->sensor < sc->sc_nfans + sc->sc_ntemps)
		adt7462_get_temp_limits(sc, edata, limits, props);
	else if (edata->sensor < sc->sc_nfans + sc->sc_ntemps + sc->sc_nvolts)
		adt7462_get_volt_limits(sc, edata, limits, props);
}

static void
adt7462_get_fan_limits(struct adt7462_softc *sc, envsys_data_t *edata,
	sysmon_envsys_lim_t *limits, uint32_t *props)
{
	int fan = sc->sc_env_map[edata->sensor];
	uint8_t	reg, val;

	/* The chip measures intervals, so the limit is for low speed. */
	*props &= ~PROP_WARNMIN;

	reg = ADT7462_TACH_LIMIT(fan);
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0)
		return;
	if (val == 0x00)	/* No limit */
		return;
	limits->sel_warnmin = VAL_TO_SPEED(val, 0);
	*props |= PROP_WARNMIN;
}

static void
adt7462_get_temp_limits(struct adt7462_softc *sc, envsys_data_t *edata,
	sysmon_envsys_lim_t *limits, uint32_t *props)
{
	int temp, snum;
	uint8_t	reg, val;

	temp = sc->sc_env_map[edata->sensor];
	snum = ADT7462_TEMP_NUM(temp);

	*props &= ~(PROP_CRITMAX | PROP_WARNMAX | PROP_WARNMIN);

	if (sc->sc_crit_therm[snum] == 1)
		reg = ADT7462_TEMP_THERM1(temp);
	else
		reg = ADT7462_TEMP_THERM2(temp);
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0)
		return;
	limits->sel_critmax = VAL_TO_TEMP(val, 0);
	*props |= PROP_CRITMAX;

	reg = ADT7462_TEMP_HIGH(temp);
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0)
		return;
	limits->sel_warnmax = VAL_TO_TEMP(val, 0);
	*props |= PROP_WARNMAX;

	reg = ADT7462_TEMP_LOW(temp);
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0)
		return;
	limits->sel_warnmin = VAL_TO_TEMP(val, 0);
	*props |= PROP_WARNMIN;
}

static void
adt7462_get_volt_limits(struct adt7462_softc *sc, envsys_data_t *edata,
	sysmon_envsys_lim_t *limits, uint32_t *props)
{
	int volt = sc->sc_env_map[edata->sensor];
	uint8_t	reg, val;

	*props &= ~(PROP_WARNMAX | PROP_WARNMIN);

	reg = adt7462_volts_table[volt].h_reg;
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0)
		return;
	limits->sel_warnmax = VAL_TO_VOLT(val, sc->sc_vscale[volt]);
	*props |= PROP_WARNMAX;

	reg = adt7462_volts_table[volt].l_reg;
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0)
		return;
	limits->sel_warnmin = VAL_TO_VOLT(val, sc->sc_vscale[volt]);
	*props |= PROP_WARNMIN;
}

void
adt7462_set_limits(struct sysmon_envsys *sme, envsys_data_t *edata,
	sysmon_envsys_lim_t *limits, uint32_t *props)
{
	struct adt7462_softc *sc = sme->sme_cookie;

	if (edata->sensor < sc->sc_nfans)
		adt7462_set_fan_limits(sc, edata, limits, props);
	else if (edata->sensor < sc->sc_nfans + sc->sc_ntemps)
		adt7462_set_temp_limits(sc, edata, limits, props);
	else if (edata->sensor < sc->sc_nfans + sc->sc_ntemps + sc->sc_nvolts)
		adt7462_set_volt_limits(sc, edata, limits, props);
}

static void
adt7462_set_fan_limits(struct adt7462_softc *sc, envsys_data_t *edata,
	sysmon_envsys_lim_t *limits, uint32_t *props)
{
	int fan, snum;
	uint8_t	reg, val;

	fan = sc->sc_env_map[edata->sensor];
	snum = ADT7462_FAN_NUM(fan);

	if (limits == NULL || *props & PROP_WARNMIN) {
		if (limits == NULL)	/* Restore defaults */
			val = sc->sc_highlim[snum];
		else {
			if (limits->sel_warnmin == 0)
				val = 0xff;
			else
				val = SPEED_TO_MSB(limits->sel_warnmin);
		}
		reg = ADT7462_TACH_LIMIT(fan);
		adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val);
	}
}

static void
adt7462_set_temp_limits(struct adt7462_softc *sc, envsys_data_t *edata,
	sysmon_envsys_lim_t *limits, uint32_t *props)
{
	int temp, snum;
	uint8_t	reg, val;

	temp = sc->sc_env_map[edata->sensor];
	snum = ADT7462_TEMP_NUM(temp);

	if (limits == NULL || *props & PROP_CRITMAX) {
		if (limits == NULL) {	/* Restore defaults */
			if (sc->sc_crit_therm[snum] == 1)
				val = sc->sc_therm1[snum];
			else
				val = sc->sc_therm2[snum];
		} else {
			val = TEMP_TO_MSB(limits->sel_critmax);
		}
		/* Don't change order of therm limits */
		if (sc->sc_crit_therm[snum] == 1) {
			reg = ADT7462_TEMP_THERM1(temp);
			if (val > sc->sc_therm2[snum])
				val = sc->sc_therm2[snum];
			sc->sc_therm1[snum] = val;
		} else {
			reg = ADT7462_TEMP_THERM2(temp);
			if (val > sc->sc_therm1[snum])
				val = sc->sc_therm1[snum];
			sc->sc_therm2[snum] = val;
		}
		adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val);
	}

	if (limits == NULL || *props & PROP_WARNMAX) {
		if (limits == NULL)	/* Restore defaults */
			val = sc->sc_highlim[snum];
		else {
			val = TEMP_TO_MSB(limits->sel_warnmax);
		}
		reg = ADT7462_TEMP_HIGH(temp);
		adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val);
	}

	if (limits == NULL || *props & PROP_WARNMIN) {
		if (limits == NULL)	/* Restore defaults */
			val = sc->sc_lowlim[snum];
		else {
			val = TEMP_TO_MSB(limits->sel_warnmin);
		}
		reg = ADT7462_TEMP_LOW(temp);
		adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val);
	}
}

static void
adt7462_set_volt_limits(struct adt7462_softc *sc, envsys_data_t *edata,
	sysmon_envsys_lim_t *limits, uint32_t *props)
{
	int volt, snum;
	uint8_t	reg, val;

	volt = sc->sc_env_map[edata->sensor];
	snum = ADT7462_VOLT_NUM(volt);

	if (limits == NULL || *props & PROP_WARNMAX) {
		if (limits == NULL)	/* Restore defaults */
			val = sc->sc_highlim[snum];
		else {
			val = VOLT_TO_MSB(limits->sel_warnmax,
			    sc->sc_vscale[volt]);
		}
		reg = adt7462_volts_table[volt].h_reg;
		adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val);
	}

	if (limits == NULL || *props & PROP_WARNMIN) {
		if (limits == NULL)	/* Restore defaults */
			val = sc->sc_lowlim[snum];
		else {
			val = VOLT_TO_MSB(limits->sel_warnmin,
			    sc->sc_vscale[volt]);
		}
		reg = adt7462_volts_table[volt].l_reg;
		adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val);
	}
}

static int
adt7462_pwm_duty(SYSCTLFN_ARGS)
{
	struct sysctlnode node = *rnode;
	struct adt7462_softc *sc = node.sysctl_data;
	uint8_t reg, val;
	int sysval, err;

	/* Register from sysctl number */
	val = node.sysctl_num & 0xff;
	reg = ADT7462_PWM_DUTY(val);

	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
		aprint_error_dev(sc->sc_dev,
		    "unable to read pwm duty cycle\n");
		return EAGAIN;
	}
	sysval = val * 100 / 255;
	node.sysctl_data = &sysval;
	err = sysctl_lookup(SYSCTLFN_CALL(&node));
	if (err || newp == NULL)
		return err;

	/* Write a new value */
	sysval = *(int *)node.sysctl_data;
	if (sysval < 0 || sysval > 100)
		return EINVAL;
	val = (sysval * 255 / 100) & 0xff;
	if (adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val) != 0) {
		aprint_error_dev(sc->sc_dev,
		    "unable to write pwm duty cycle\n");
		return EAGAIN;
	}
	return 0;
}

static int
adt7462_trange(SYSCTLFN_ARGS)
{
	struct sysctlnode node = *rnode;
	struct adt7462_softc *sc = node.sysctl_data;
	uint8_t reg, val, old, new;
	int sysval, err, i;

	/* Register from sysctl number */
	val = node.sysctl_num & 0xff;
	reg = ADT7462_HYST_TRANGE(val);

	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
		aprint_error_dev(sc->sc_dev, "unable to read trange\n");
		return EAGAIN;
	}
	old = (val & ADT7462_HTR_RNGE_MASK) >> ADT7462_HTR_RNGE_SHFT;
	sysval = trange_vals[old];
	node.sysctl_data = &sysval;
	err = sysctl_lookup(SYSCTLFN_CALL(&node));
	if (err || newp == NULL)
		return err;

	/* Write a new value */
	sysval = *(int *)node.sysctl_data;
	/* Convert new value to trange value (and add in lower 4 bits) */
	if (sysval < trange_vals[0] ||
	    sysval > trange_vals[ADT7462_TRANGE_LEN - 1])
		return EINVAL;
	new = 12;	/* Default Trange */
	for (i = 0; i < ADT7462_TRANGE_LEN; i++)
		if (sysval >= trange_vals[i])
			new = i;
	new <<= ADT7462_HTR_RNGE_SHFT;
	new |= (val & ADT7462_HTR_HYST_MASK);
	if (adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, new) != 0) {
		aprint_error_dev(sc->sc_dev, "unable to write trange\n");
		return EAGAIN;
	}
	return 0;
}

static int
adt7462_tmin(SYSCTLFN_ARGS)
{
	struct sysctlnode node = *rnode;
	struct adt7462_softc *sc = node.sysctl_data;
	uint8_t reg, val;
	int temp, sysval, err;

	/* Register from sysctl number */
	temp = node.sysctl_num & 0xff;
	reg = ADT7462_TMIN(temp);

	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
		aprint_error_dev(sc->sc_dev, "unable to read tmin\n");
		return EAGAIN;
	}
	sysval = val - ADT7462_TEMP_OFFSET;
	node.sysctl_data = &sysval;
	err = sysctl_lookup(SYSCTLFN_CALL(&node));
	if (err || newp == NULL)
		return err;

	/* Write a new value */
	sysval = *(int *)node.sysctl_data;
	if (sysval < 0)
		return EINVAL;
	val = (sysval + ADT7462_TEMP_OFFSET) & 0xff;
	/* Tmin needs to be below the therm limits */
	if (sc->sc_crit_therm[temp] == 1) {
		if (val > sc->sc_therm1[temp] - 1)
			return EINVAL;
	} else {
		if (val > sc->sc_therm2[temp] - 1)
			return EINVAL;
	}
	/* If we have RW tmin, we don't have op. point, so don't check it */
	if (adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val) != 0) {
		aprint_error_dev(sc->sc_dev, "unable to write tmin\n");
		return EAGAIN;
	}
	return 0;
}

static int
adt7462_op_point(SYSCTLFN_ARGS)
{
	struct sysctlnode node = *rnode;
	struct adt7462_softc *sc = node.sysctl_data;
	uint8_t reg, val, mreg, mval;
	int temp, sysval, err;

	/* Register from sysctl number */
	temp = node.sysctl_num & 0xff;
	reg = ADT7462_OP_POINT(temp);

	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, reg, &val) != 0) {
		aprint_error_dev(sc->sc_dev, "unable to read op point\n");
		return EAGAIN;
	}
	sysval = val - ADT7462_TEMP_OFFSET;
	node.sysctl_data = &sysval;
	err = sysctl_lookup(SYSCTLFN_CALL(&node));
	if (err || newp == NULL)
		return err;

	/* Write a new value */
	sysval = *(int *)node.sysctl_data;
	if (sysval < 1)
		return EINVAL;
	val = (sysval + ADT7462_TEMP_OFFSET) & 0xff;
	/* Operating point should be below the therm limits */
	if (sc->sc_crit_therm[temp] == 1) {
		if (val > sc->sc_therm1[temp] - 1)
			return EINVAL;
	} else {
		if (val > sc->sc_therm2[temp] - 1)
			return EINVAL;
	}
	/* Operating point should be above Tmin */
	mreg = ADT7462_TMIN(temp);
	if (adt7462_read_reg(sc->sc_tag, sc->sc_address, mreg, &mval) != 0) {
		aprint_error_dev(sc->sc_dev, "unable to read tmin\n");
		return EAGAIN;
	}
	if (val < mval + 1)
		return EINVAL;
	if (adt7462_write_reg(sc->sc_tag, sc->sc_address, reg, val) != 0) {
		aprint_error_dev(sc->sc_dev, "unable to write op point\n");
		return EAGAIN;
	}
	return 0;
}

static int
adt7462_read_reg(i2c_tag_t tag, i2c_addr_t addr, uint8_t reg, uint8_t *val)
{
	int err = 0;

	if ((err = iic_acquire_bus(tag, 0)) != 0)
		return err;
	err = iic_exec(tag, I2C_OP_READ_WITH_STOP, addr, &reg, 1, val, 1, 0);
	iic_release_bus(tag, 0);
	return err;
}

static int
adt7462_write_reg(i2c_tag_t tag, i2c_addr_t addr, uint8_t reg, uint8_t val)
{
	int err = 0;

	if ((err = iic_acquire_bus(tag, 0)) != 0)
		return err;
	err = iic_exec(tag, I2C_OP_WRITE_WITH_STOP, addr, &reg, 1, &val, 1, 0);
	iic_release_bus(tag, 0);
	return err;
}
