NetBSD-5.0.2/sys/arch/sparc64/dev/envctrl.c

Compare this file to the similar file:
Show the results in this format:

/*	$NetBSD: envctrl.c,v 1.11 2008/04/28 20:23:36 martin Exp $ */

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

/*
 * SUNW,envctrl Sun Ultra Enterprise 450 environmental monitoring driver
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: envctrl.c,v 1.11 2008/04/28 20:23:36 martin Exp $");

#include <sys/param.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/kthread.h>
#include <sys/condvar.h>
#include <sys/mutex.h>
#include <sys/envsys.h>

#include <machine/autoconf.h>
#include <machine/promlib.h>

#include <dev/ebus/ebusreg.h>
#include <dev/ebus/ebusvar.h>
#include <dev/i2c/i2cvar.h>
#include <sparc64/dev/envctrlreg.h>
#include <dev/sysmon/sysmonvar.h>

#include <dev/i2c/pcf8583reg.h>	/* for WDT */
#include <dev/ic/pcf8584var.h>

static int envctrlmatch(device_t, cfdata_t, void *);
static void envctrlattach(device_t, device_t, void *);

struct envctrl_softc {
	device_t sc_dev;
	bus_space_tag_t sc_iot;
	bus_space_handle_t sc_ioh;

	struct pcf8584_handle sc_pcfiic;

	lwp_t *sc_thread;
	kcondvar_t sc_sleepcond;
	kmutex_t sc_sleepmtx;

	struct sysmon_envsys *sc_sme;
	envsys_data_t sc_sensor[13];
	uint8_t sc_keyswitch;
	uint8_t sc_fanstate;
	uint8_t sc_ps_state[3];
	int sc_ps_temp_factors[256];
	int sc_cpu_temp_factors[256];
	uint8_t sc_ps_fan_speeds[112];
	uint8_t sc_cpu_fan_speeds[112];
};

CFATTACH_DECL_NEW(envctrl, sizeof(struct envctrl_softc),
    envctrlmatch, envctrlattach, NULL, NULL);

static void envctrl_thread(void *);
static void envctrl_sleep(struct envctrl_softc *, int);
static int envctrl_write_1(struct envctrl_softc *, int, uint8_t);
static int envctrl_write_2(struct envctrl_softc *, int, uint8_t, uint8_t);
static int envctrl_read(struct envctrl_softc *, int, uint8_t *, int);
static int envctrl_get_cputemp(struct envctrl_softc *, uint8_t);
static int envctrl_get_pstemp(struct envctrl_softc *, uint8_t);
static int envctrl_get_ambtemp(struct envctrl_softc *);
static int envctrl_set_fanvoltage(struct envctrl_softc *, uint8_t, uint8_t);
static uint8_t envctrl_cputemp_to_voltage(struct envctrl_softc *, int);
static uint8_t envctrl_pstemp_to_voltage(struct envctrl_softc *, int);
static void envctrl_init_components(struct envctrl_softc *);
static int envctrl_init_tables(struct envctrl_softc *, int);
static void envctrl_update_sensors(struct envctrl_softc *);
static void envctrl_interpolate_ob_table(int *, uint8_t *);

static int
envctrlmatch(device_t parent, cfdata_t cf, void *aux)
{
	struct ebus_attach_args *ea = aux;

	return (strcmp("SUNW,envctrl", ea->ea_name) == 0);
}

static void
envctrlattach(device_t parent, device_t self, void *aux)
{
	struct envctrl_softc *sc = device_private(self);
	struct ebus_attach_args *ea = aux;
	bus_addr_t devaddr;
	int i, error;

	sc->sc_dev = self;
	sc->sc_iot = ea->ea_bustag;
	devaddr = EBUS_ADDR_FROM_REG(&ea->ea_reg[0]);
	if (bus_space_map(sc->sc_iot, devaddr, ea->ea_reg[0].size,
		0, &sc->sc_ioh) != 0) {
		aprint_error(": unable to map device registers\n");
		return;
	}
	if (envctrl_init_tables(sc, ea->ea_node) != 0) {
		aprint_error(": unable to initialize tables\n");
		return;
	}
	/*
	 * initialize envctrl bus
	 */
	sc->sc_pcfiic.ha_parent = self;
	sc->sc_pcfiic.ha_iot = sc->sc_iot;
	sc->sc_pcfiic.ha_ioh = sc->sc_ioh;
	pcf8584_init(&sc->sc_pcfiic);

	if (envctrl_write_1(sc, ENVCTRL_FANFAIL_ADDR, 0xFF)) {
		aprint_error(": i2c probe failed\n");
		return;
	}
	aprint_normal("\n%s: Ultra Enterprise 450 environmental monitoring\n",
	    device_xname(self));

	envctrl_init_components(sc);

	/*
	 * fill envsys sensor structures
	 */
	sc->sc_sme = sysmon_envsys_create();

	for (i = 0; i < 8; i++)
		sc->sc_sensor[i].units = ENVSYS_STEMP;

	for (i = 0; i < 4; i++)
		sprintf(sc->sc_sensor[i].desc, "CPU%i", i);

	for (i = 4; i < 7; i++)
		sprintf(sc->sc_sensor[i].desc, "PS%i", i - 4);

	for (i = 8; i < 10; i++)
		sc->sc_sensor[i].units = ENVSYS_SVOLTS_DC;

	for (i = 10; i < 12; i++) {
		sc->sc_sensor[i].units = ENVSYS_INTEGER;
		sc->sc_sensor[i].flags = ENVSYS_FMONNOTSUPP;
	}

	sprintf(sc->sc_sensor[7].desc, "ambient");
	sprintf(sc->sc_sensor[8].desc, "cpufan voltage");
	sprintf(sc->sc_sensor[9].desc, "psfan voltage");
	sprintf(sc->sc_sensor[10].desc, "ps failed");
	sprintf(sc->sc_sensor[11].desc, "fans failed");

	for (i = 0; i < 12; i++) {
		if (sysmon_envsys_sensor_attach(sc->sc_sme,
						&sc->sc_sensor[i])) {
			sysmon_envsys_destroy(sc->sc_sme);
			return;
		}
	}

	sc->sc_sme->sme_name = device_xname(self);
	sc->sc_sme->sme_flags = SME_DISABLE_REFRESH;

	if (sysmon_envsys_register(sc->sc_sme)) {
		aprint_error("%s: unable to register with sysmon\n",
		    device_xname(self));
		sysmon_envsys_destroy(sc->sc_sme);
		return;
	}

	envctrl_update_sensors(sc);

	for (i = 0; i < 3; i++) {
		aprint_normal("%s: PS %i: ", device_xname(self), i);
		if (sc->sc_ps_state[i] & ENVCTRL_PS_PRESENT) {
			aprint_normal("absent\n");
			continue;
		}
		aprint_normal("%iW, %s",
		    (sc->sc_ps_state[i] & ENVCTRL_PS_550W) ? 650 : 550,
		    (sc->sc_ps_state[i] & ENVCTRL_PS_OK) ?
		    "online" : "FAILED");
		if ((sc->sc_ps_state[i] & ENVCTRL_PS_OVERLOAD) == 0)
			aprint_normal(" ***OVERLOADED***");
		if ((sc->sc_ps_state[i] & ENVCTRL_PS_LOADSHARE_ERROR) == 0)
			aprint_normal(" ***LOADSHARE ERROR***");
		aprint_normal("\n");
	}

	aprint_verbose("%s: keyswitch is ", device_xname(self));
	if ((sc->sc_keyswitch & ENVCTRL_KEY_LOCK) == 0)
		aprint_verbose("unlocked\n");
	else {
		if ((sc->sc_keyswitch & ENVCTRL_KEY_DIAG) == 0)
			aprint_verbose("in diagnostic mode\n");
		else
			aprint_verbose("locked\n");
	}

	mutex_init(&sc->sc_sleepmtx, MUTEX_DEFAULT, IPL_NONE);
	cv_init(&sc->sc_sleepcond, "envidle");

	error = kthread_create(PRI_NONE, 0, NULL, envctrl_thread, sc,
	    &sc->sc_thread, "envctrl");
	if (error)
		panic("cannot start envctrl thread; error %d", error);
}

static void
envctrl_sleep(struct envctrl_softc *sc, int ms)
{

	cv_timedwait(&sc->sc_sleepcond, &sc->sc_sleepmtx, mstohz(ms));
}

/*
 * read cpu temperature, in microCelcius
 */
static int
envctrl_get_cputemp(struct envctrl_softc *sc, uint8_t cpu)
{
	uint8_t r;

	if (envctrl_write_1(sc, ENVCTRL_CPUTEMP_ADDR, (1 << 6) | cpu))
		return -1;
	if (envctrl_read(sc, ENVCTRL_CPUTEMP_ADDR, &r, 1))
		return -1;
	if (envctrl_write_1(sc, ENVCTRL_CPUTEMP_ADDR, 0))
		return -1;

	return sc->sc_cpu_temp_factors[r];
}

/*
 * read power supply temperature in microCelcius
 */
static int
envctrl_get_pstemp(struct envctrl_softc *sc, uint8_t ps)
{
	uint8_t r;

	if (envctrl_write_1(sc, ENVCTRL_PS0TEMP_ADDR + ps, (1 << 6)))
		return -1;
	if (envctrl_read(sc, ENVCTRL_PS0TEMP_ADDR + ps, &r, 1))
		return -1;
	if (envctrl_write_1(sc, ENVCTRL_PS0TEMP_ADDR + ps, 0))
		return -1;

	return sc->sc_ps_temp_factors[r];
}

/*
 * read ambient temperature in microCelcius
 */
static int
envctrl_get_ambtemp(struct envctrl_softc *sc)
{
	int8_t temp[2];

	if (envctrl_write_1(sc, ENVCTRL_AMB_ADDR, 0))
		return -1;
	if (envctrl_read(sc, ENVCTRL_AMB_ADDR, temp, 2))
		return -1;

	return ((temp[0] << 1) | ((temp[1] >> 7) & 1)) * 500000;
}

/*
 * set fan voltage, 0 - 63, scaled to 0V-12V.
 */
static int
envctrl_set_fanvoltage(struct envctrl_softc *sc, uint8_t port, uint8_t value)
{

	return envctrl_write_2(sc, ENVCTRL_FANVOLTAGE_ADDR, port, value);
}

static uint8_t
envctrl_cputemp_to_voltage(struct envctrl_softc *sc, int temp)
{
	uint8_t ret;

	temp -= 20;		/* magic offset, from opensolaris */

	if (temp < 0)
		temp = 0;
	if (temp > 111)
		temp = 111;

	ret = sc->sc_cpu_fan_speeds[temp];
	if (ret < ENVCTRL_FANVOLTAGE_MIN)
		return ENVCTRL_FANVOLTAGE_MIN;
	if (ret > ENVCTRL_FANVOLTAGE_MAX)
		return ENVCTRL_FANVOLTAGE_MAX;
	return ret;
}

static uint8_t
envctrl_pstemp_to_voltage(struct envctrl_softc *sc, int temp)
{
	uint8_t ret;

	temp -= 30;		/* magic offset, from opensolaris */

	if (temp < 0)
		temp = 0;

	if (temp > 111)
		temp = 111;

	ret = sc->sc_ps_fan_speeds[temp];
	if (ret < ENVCTRL_FANVOLTAGE_MIN)
		return ENVCTRL_FANVOLTAGE_MIN;
	if (ret > ENVCTRL_FANVOLTAGE_MAX)
		return ENVCTRL_FANVOLTAGE_MAX;
	return ret;
}

static void
envctrl_init_components(struct envctrl_softc *sc)
{

	/* configure ports as pure inputs */
	envctrl_write_1(sc, ENVCTRL_FANFAIL_ADDR, ~0);
	envctrl_write_1(sc, ENVCTRL_PS0_ADDR, ~0);
	envctrl_write_1(sc, ENVCTRL_PS1_ADDR, ~0);
	envctrl_write_1(sc, ENVCTRL_PS2_ADDR, ~0);

	/* light up power LED */
	envctrl_write_1(sc, ENVCTRL_LED_ADDR, ~ENVCTRL_LED_PWR);

	/*
	 * Set fans to full speed. The last two ports are usually not
	 * connected, but it doesn't hurt to enable them.
	 */
	envctrl_set_fanvoltage(sc, ENVCTRL_FANPORT_CPU, ENVCTRL_FANVOLTAGE_MAX);
	envctrl_set_fanvoltage(sc, ENVCTRL_FANPORT_PS, ENVCTRL_FANVOLTAGE_MAX);
	envctrl_set_fanvoltage(sc, ENVCTRL_FANPORT_AFB, ENVCTRL_FANVOLTAGE_MAX);
	envctrl_set_fanvoltage(sc, 3, ENVCTRL_FANVOLTAGE_MAX);

	/* set fan watchdog timer to a 60 sec timeout */
	envctrl_write_2(sc, ENVCTRL_WATCHDOG_ADDR, PCF8583_REG_CSR, 0x80);
	envctrl_write_2(sc, ENVCTRL_WATCHDOG_ADDR, PCF8583_REG_CSR, 0x80);
	envctrl_write_2(sc, ENVCTRL_WATCHDOG_ADDR, PCF8583_REG_TIMER, 0);
	envctrl_write_2(sc, ENVCTRL_WATCHDOG_ADDR, PCF8583_REG_ALMTIMER, 60);
	envctrl_write_2(sc, ENVCTRL_WATCHDOG_ADDR, PCF8583_REG_ALMCTL, 0xca);
	envctrl_write_2(sc, ENVCTRL_WATCHDOG_ADDR, PCF8583_REG_CSR, 0x04);

	/* recover from previous watchdog failure, if any */
	envctrl_write_1(sc, ENVCTRL_INTR_ADDR, ~0);
	envctrl_write_1(sc, ENVCTRL_INTR_ADDR, ~ENVCTRL_INTR_WDT_RST);
	envctrl_write_1(sc, ENVCTRL_INTR_ADDR, (uint8_t) ~ENVCTRL_INTR_ENABLE);
}

#if 0
/*
 * Service routine for environmental monitoring events. Currently unused.
 */
static void
envctrl_isr(void)
{
	uint8_t intstate;
	uint8_t v;

	envctrl_read(ENVCTRL_INTR_ADDR, 1, &intstate);
	if ((intstate & ENVCTRL_INTR_PS0) == 0 ||
	    (intstate & ENVCTRL_INTR_PS1) == 0 ||
	    (intstate & ENVCTRL_INTR_PS2) == 0) {
		printf("envctrl: power supply event\n");
		envctrl_read(ENVCTRL_PS0_ADDR, 1, &v);
		envctrl_read(ENVCTRL_PS1_ADDR, 1, &v);
		envctrl_read(ENVCTRL_PS2_ADDR, 1, &v);
	}
	if ((intstate & ENVCTRL_INTR_FANFAIL) == 0) {
		printf("envctrl: fan failure event\n");
		envctrl_read(ENVCTRL_FANFAIL_ADDR, 1, &v);
	}
	if ((intstate & ENVCTRL_INTR_UNKNOWN1) == 0) {
	}
	if ((intstate & ENVCTRL_INTR_UNKNOWN2) == 0) {
	}
}
#endif

/*
 * Refresh all sensor readings
 */
static void
envctrl_update_sensors(struct envctrl_softc *sc)
{
	int cputemp_max;
	int pstemp_max;
	uint8_t cpufan_voltage;
	uint8_t psfan_voltage;
	int temp;
	int nfail;
	int i;

	/* read cpu temperatures */
	cputemp_max = -1;
	for (i = 0; i < 4; i++) {
		temp = envctrl_get_cputemp(sc, i);
		if (temp != -1) {
			if (cputemp_max < temp)
				cputemp_max = temp;
			sc->sc_sensor[i].value_cur = temp + 273150000;
			sc->sc_sensor[i].state = ENVSYS_SVALID;
		} else
			sc->sc_sensor[i].state = ENVSYS_SINVALID;
	}

	/* read power supply state & temperature */
	pstemp_max = -1;
	nfail = 0;
	for (i = 0; i < 3; i++) {
		if (envctrl_read(sc, ENVCTRL_PS0_ADDR - i,
			&sc->sc_ps_state[i], 1))
			sc->sc_ps_state[i] = ENVCTRL_PS_PRESENT;

		if ((sc->sc_ps_state[i] & ENVCTRL_PS_PRESENT) == 0) {
			if ((sc->sc_ps_state[i] & 0x38) != 0x38)
				nfail++;
			temp = envctrl_get_pstemp(sc, i);
			if (pstemp_max < temp)
				pstemp_max = temp;
			sc->sc_sensor[i + 4].value_cur = temp + 273150000;
			sc->sc_sensor[i + 4].state = ENVSYS_SVALID;
		} else
			sc->sc_sensor[i + 4].state = ENVSYS_SINVALID;
	}
	sc->sc_sensor[10].value_cur = nfail;
	sc->sc_sensor[10].state = ENVSYS_SVALID;

	/* read ambient temperature */
	temp = envctrl_get_ambtemp(sc);
	if (temp != -1) {
		sc->sc_sensor[7].value_cur = temp + 273150000;
		sc->sc_sensor[7].state = ENVSYS_SVALID;
	} else
		sc->sc_sensor[7].state = ENVSYS_SINVALID;

	/* read fan state */
	if (envctrl_read(sc, ENVCTRL_FANFAIL_ADDR, &sc->sc_fanstate, 1)) {
		sc->sc_fanstate = 0;
		sc->sc_sensor[11].state = ENVSYS_SINVALID;
	} else {
		nfail = 0;
		for (i = 0; i < 8; i++) {
			if (((sc->sc_fanstate >> i) & 1) == 0)
				nfail++;
		}
		sc->sc_sensor[11].value_cur = nfail;
		sc->sc_sensor[11].state = ENVSYS_SVALID;
	}

	/* read keyswitch */
	envctrl_read(sc, ENVCTRL_LED_ADDR, &sc->sc_keyswitch, 1);

	/*
	 * Update fan voltages. If any fans have failed, set voltage
	 * to max to compensate for lost air flow.
	 */
	cpufan_voltage = envctrl_cputemp_to_voltage(sc, cputemp_max / 1000000);
	if (cputemp_max == -1 || sc->sc_fanstate != 0xFF)
		cpufan_voltage = ENVCTRL_FANVOLTAGE_MAX;

	psfan_voltage = envctrl_pstemp_to_voltage(sc, pstemp_max / 1000000);
	if (pstemp_max == -1 || sc->sc_fanstate != 0xFF)
		psfan_voltage = ENVCTRL_FANVOLTAGE_MAX;

	envctrl_set_fanvoltage(sc, ENVCTRL_FANPORT_CPU, cpufan_voltage);
	envctrl_set_fanvoltage(sc, ENVCTRL_FANPORT_PS, psfan_voltage);

	sc->sc_sensor[8].value_cur = cpufan_voltage * ENVCTRL_UVFACT;
	sc->sc_sensor[9].value_cur = psfan_voltage * ENVCTRL_UVFACT;
	sc->sc_sensor[8].state = ENVSYS_SVALID;
	sc->sc_sensor[9].state = ENVSYS_SVALID;
}

/*
 * envctrl worker thread. Reads sensors periodically.
 */
static void
envctrl_thread(void *arg)
{

#ifdef BLINK
	int i;
#endif
	struct envctrl_softc *sc = arg;

	mutex_enter(&sc->sc_sleepmtx);

	/*
	 * Poll sensors every 15 seconds. Optionally blink the activity LED.
	 */
	for (;;) {
		/* kick wdt */
		envctrl_write_2(sc, ENVCTRL_WATCHDOG_ADDR,
		    PCF8583_REG_TIMER, 0);

		/* refresh sensor readings, update fan speeds */
		envctrl_update_sensors(sc);

#ifdef BLINK
		for (i = 0; i < 15; i++) {
			envctrl_write_1(sc, ENVCTRL_LED_ADDR,
			    ~(ENVCTRL_LED_PWR | ENVCTRL_LED_ACT));
			envctrl_sleep(sc, 500);
			envctrl_write_1(sc, ENVCTRL_LED_ADDR,
			    ~ENVCTRL_LED_PWR);
			envctrl_sleep(sc, 500);
		}
#else
		envctrl_sleep(sc, 15 * 1000);
#endif
	}
}

/*
 * Make a table with interpolated values based on an OPB temperature table.
 */
static void
envctrl_interpolate_ob_table(int *ftbl, uint8_t *utbl)
{
	int i;
	int e;
	int k;
	int y;

	i = 255;
	e = i;
	while (e >= 0) {
		e = i;
		y = utbl[i] * 1000000;
		do {
			i--;
		}
		while (utbl[e] == utbl[i] && i > 0);
		k = (utbl[i] * 1000000 - y) / (e - i);
		while (e >= i) {
			ftbl[e] = y;
			y += k;
			e--;
		}
	}
}

/*
 * Copy temperature tables from OBP. Return 0 on success.
 */
static int
envctrl_init_tables(struct envctrl_softc *sc, int node)
{
	uint8_t buf[256];
	uint8_t *p;
	int nitem;
	int err;

	p = buf;
	nitem = 1;
	err = prom_getprop(node, "cpu-temp-factors", 254, &nitem, &p);
	if (nitem != 1 || err != 0)
		return -1;

	buf[255] = buf[253];
	buf[254] = buf[253];
	envctrl_interpolate_ob_table(sc->sc_cpu_temp_factors, buf);

	p = buf;
	nitem = 1;
	err = prom_getprop(node, "ps-temp-factors", 254, &nitem, &p);
	if (nitem != 1 || err != 0)
		return -1;

	buf[255] = buf[253];
	buf[254] = buf[253];
	envctrl_interpolate_ob_table(sc->sc_ps_temp_factors, buf);

	p = sc->sc_cpu_fan_speeds;
	nitem = 1;
	err = prom_getprop(node, "cpu-fan-speeds", 112, &nitem, &p);
	if (nitem != 1 || err != 0)
		return -1;

	p = sc->sc_ps_fan_speeds;
	nitem = 1;
	err = prom_getprop(node, "ps-fan-speeds", 112, &nitem, &p);
	if (nitem != 1 || err != 0)
		return -1;

	return 0;
}

static int
envctrl_write_1(struct envctrl_softc *sc, int addr, uint8_t v1)
{

	return iic_exec(&sc->sc_pcfiic.ha_i2c, I2C_OP_WRITE_WITH_STOP, addr,
	    NULL, 0, &v1, 1, cold ? I2C_F_POLL : 0);
}


static int
envctrl_write_2(struct envctrl_softc *sc, int addr, uint8_t v1, uint8_t v2)
{

	uint8_t buf[] = {v1, v2};
	return iic_exec(&sc->sc_pcfiic.ha_i2c, I2C_OP_WRITE_WITH_STOP, addr,
	    NULL, 0, buf, 2, cold ? I2C_F_POLL : 0);
}

static int
envctrl_read(struct envctrl_softc *sc, int addr, uint8_t *buf, int len)
{

	return iic_exec(&sc->sc_pcfiic.ha_i2c, I2C_OP_READ_WITH_STOP, addr,
	    NULL, 0, buf, len,  cold ? I2C_F_POLL : 0);
}