/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * ENVCTRL_ Environment Monitoring driver for i2c
 *
 */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/signal.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/termio.h>
#include <sys/termios.h>
#include <sys/cmn_err.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/stropts.h>
#include <sys/strtty.h>
#include <sys/debug.h>
#include <sys/eucioctl.h>
#include <sys/cred.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <sys/kmem.h>

#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/obpdefs.h>
#include <sys/conf.h>		/* req. by dev_ops flags MTSAFE etc. */
#include <sys/modctl.h>		/* for modldrv */
#include <sys/stat.h>		/* ddi_create_minor_node S_IFCHR */
#include <sys/open.h>		/* for open params.	 */
#include <sys/uio.h>		/* for read/write */
#include <sys/envctrl.h>	/* Environment header */

/* driver entry point fn definitions */
static int 	envctrl_open(queue_t *, dev_t *, int, int, cred_t *);
static int	envctrl_close(queue_t *, int, cred_t *);
static uint_t 	envctrl_bus_isr(caddr_t);
static uint_t 	envctrl_dev_isr(caddr_t);

/* configuration entry point fn definitions */
static int 	envctrl_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int	envctrl_attach(dev_info_t *, ddi_attach_cmd_t);
static int	envctrl_detach(dev_info_t *, ddi_detach_cmd_t);

/* Driver private routines */
static void	envctrl_init_bus(struct envctrlunit *);
static int	envctrl_xmit(struct envctrlunit *, caddr_t *, int);
static void	envctrl_recv(struct envctrlunit *, caddr_t *, int);
static void	envctrl_get_sys_temperatures(struct envctrlunit *, uint8_t *);
static int	envctrl_get_lm75_temp(struct envctrlunit *);
static int	envctrl_get_ps_temp(struct envctrlunit *, uint8_t);
static int	envctrl_get_cpu_temp(struct envctrlunit *, int);
static void	envctrl_fan_fail_service(struct envctrlunit *);
static void	envctrl_PS_intr_service(struct envctrlunit *, uint8_t);
static void	envctrl_ps_probe(struct envctrlunit *);
static void	envctrl_tempr_poll(void *);
static void	envctrl_pshotplug_poll(void *);
static void	envctrl_led_blink(void *);
static void	envctrl_reset_dflop(struct envctrlunit *);
static void	envctrl_enable_devintrs(struct envctrlunit *);
static void	envctrl_stop_clock(struct envctrlunit *);
static void	envctrl_reset_watchdog(struct envctrlunit *, uint8_t *);
static void	envctrl_abort_seq_handler(char *msg);
static uint8_t	envctrl_get_fpm_status(struct envctrlunit *);
static void	envctrl_set_fsp(struct envctrlunit *, uint8_t *);
static int	envctrl_set_dskled(struct envctrlunit *,
				struct envctrl_pcf8574_chip *);
static int	envctrl_get_dskled(struct envctrlunit *,
				struct envctrl_pcf8574_chip *);
static void	envctrl_probe_cpus(struct envctrlunit *);
static int	envctrl_match_cpu(dev_info_t *, void *);
static int	envctrl_isother_fault_led(struct envctrlunit *,
		    uint8_t, uint8_t);

/* Kstat routines */
static void	envctrl_add_kstats(struct envctrlunit *);
static int	envctrl_ps_kstat_update(kstat_t *, int);
static int	envctrl_fanstat_kstat_update(kstat_t *, int);
static int	envctrl_encl_kstat_update(kstat_t *, int);
static void	envctrl_init_fan_kstats(struct envctrlunit *);
static void	envctrl_init_encl_kstats(struct envctrlunit *);
static void	envctrl_add_encl_kstats(struct envctrlunit *, int, int,
			uint8_t);
static void	envctrl_mod_encl_kstats(struct envctrlunit *, int, int,
			uint8_t);


/* Streams Routines */
static int	envctrl_wput(queue_t *, mblk_t *);

/* External routines */
extern void power_down(const char *);
extern int prom_getprop();
extern int prom_getproplen();
extern	void	prom_printf(const char *fmt, ...);
extern void (*abort_seq_handler)();

static void    *envctrlsoft_statep;

/* Local Variables */
/* Indicates whether or not the overtemp thread has been started */
static int	envctrl_debug_flags = 0;
static int	envctrl_afb_present = 0;
static int	envctrl_power_off_overide = 0;
static int	envctrl_max_retries = 100;
static int	envctrl_allow_detach = 0;
static int	envctrl_numcpus = 1;
static int	envctrl_p0_enclosure = 0; /* set to 1 if it is a P0 */
static int envctrl_handler = 1; /* 1 is the default */
static clock_t overtemp_timeout_hz;
static clock_t blink_timeout_hz;
static clock_t pshotplug_timeout_hz;
static int controller_present[] = {-1, -1, -1};
#ifdef MULTIFAN
static int	envctrl_fan_debug = 0;
#endif
static int 	eHc_debug = 0;
static int	power_supply_previous_state[] = {-1, -1, -1};

extern void	pci_thermal_rem_intr(dev_info_t *, uint_t);

#define	LOOP_TIMEOUT 25
#define	INIT_FAN_VAL 35
#define	DCMNERR if (eHc_debug & 0x1) cmn_err
#define	DCMN2ERR if (eHc_debug & 0x2) cmn_err
#define	MAX_FAN_FAIL_RETRY 3

uint8_t backaddrs[] = {ENVCTRL_PCF8574_DEV0, ENVCTRL_PCF8574_DEV1,
    ENVCTRL_PCF8574_DEV2};

struct module_info envctrlinfo = {
	/* id, name, min pkt siz, max pkt siz, hi water, low water */
	42, "envctrl", 0, 2048, (1024 * 20), (1024 * 1)
};

static struct qinit envctrl_rinit = {
	putq, NULL, envctrl_open, envctrl_close, NULL, &envctrlinfo, NULL
};

static struct qinit envctrl_wint = {
	envctrl_wput, NULL, envctrl_open, envctrl_close,
	    NULL, &envctrlinfo, NULL
};

struct streamtab envctrl_str_info = {
	&envctrl_rinit, &envctrl_wint, NULL, NULL
};

static struct cb_ops envctrl_cb_ops = {
	nodev,			/* cb_open */
	nodev,			/* cb_close */
	nodev,			/* cb_strategy */
	nodev,			/* cb_print */
	nodev,			/* cb_dump */
	nodev,			/* cb_read */
	nodev,			/* cb_write */
	nodev,			/* cb_ioctl */
	nodev,			/* cb_devmap */
	nodev,			/* cb_mmap */
	nodev,			/* cb_segmap */
	nochpoll,		/* cb_chpoll */
	ddi_prop_op,		/* cb_prop_op */
	&envctrl_str_info,	/* cb_stream */
	D_MP			/* cb_flag */
};

/*
 * Declare ops vectors for auto configuration.
 */
struct dev_ops  envctrl_ops = {
	DEVO_REV,		/* devo_rev */
	0,			/* devo_refcnt */
	envctrl_getinfo,	/* devo_getinfo */
	nulldev,		/* devo_identify */
	nulldev,		/* devo_probe */
	envctrl_attach,		/* devo_attach */
	envctrl_detach,		/* devo_detach */
	nodev,			/* devo_reset */
	&envctrl_cb_ops,	/* devo_cb_ops */
	(struct bus_ops *)NULL,	/* devo_bus_ops */
	nulldev			/* devo_power */
};

extern struct mod_ops mod_driverops;

static struct modldrv envctrlmodldrv = {
	&mod_driverops,		/* type of module - driver */
	"I2C ENVCTRL_driver: %I% %E%",
	&envctrl_ops,
};

static struct modlinkage envctrlmodlinkage = {
	MODREV_1,
	&envctrlmodldrv,
	0
};

/*
 * The following defines are for the i2c protocol routines.
 * This section of defines should be removed once the envctrl_targets.c
 * file is included.
 */

#define	EHC_SUCCESS 0
#define	EHC_FAILURE (-1)
#define	EHC_NO_SLAVE_ACK 3

#define	EHC_MAX_WAIT 7 /* decimal */

#define	EHC_S1_PIN 0x80
#define	EHC_S1_ES1 0x20
#define	EHC_S1_ES0 0x40
#define	EHC_S1_NBB 0x01
#define	EHC_S1_ACK 0x01
#define	EHC_S1_STA 0x04
#define	EHC_S1_STO 0x02
#define	EHC_S1_LRB 0x08
#define	EHC_S1_BER 0x10
#define	EHC_S1_LAB 0x02

#define	EHC_S0_OWN 0x55
#define	EHC_S0_CLK 0x1c

#define	EHC_BYTE_READ 0x01

#define	EHC_LONGEST_MSG 1000 /* decimal */

/*
 * PCF8591 Chip Used for temperature sensors
 *
 * Addressing Register definition.
 * A0-A2 valid range is 0-7
 *
 *  7    6  5   4    3     2     1      0
 * ------------------------------------------------
 * | 1 | 0 | 0 | 1 | A2 | A1 | A0 | R/W |
 * ------------------------------------------------
 */


#define	EHC_PCF8591_MAX_DEVS	0x08

#define	EHC_DEV0	0x00
#define	EHC_DEV1	0x02
#define	EHC_DEV2	0x04
#define	EHC_DEV3	0x06
#define	EHC_DEV4	0x08
#define	EHC_DEV5	0x0A
#define	EHC_DEV6    	0x0C
#define	EHC_DEV7	0x0E


/*
 * 		CONTROL OF CHIP
 * PCF8591 Temp sensing control register definitions
 *
 *   7      6     5   4  3   2      1   0
 * ---------------------------------------------
 * | 0 | AOE | X | X | 0 | AIF | X | X |
 * ---------------------------------------------
 * AOE = Analog out enable.. not used on out implementation
 * 5 & 4 = Analog Input Programming.. see data sheet for bits..
 *
 * AIF = Auto increment flag
 * bits 1 & 0 are for the Chennel number.
 */

#define	EHC_PCF8591_ANALOG_OUTPUT_EN	0x40
#define	EHC_PCF8591_ANALOG_INPUT_EN	0x00
#define	EHC_PCF8591_READ_BIT		0x01


#define	EHC_PCF8591_AUTO_INCR 0x04
#define	EHC_PCF8591_OSCILATOR 0x40

#define	EHC_PCF8591_MAX_PORTS	0x04

#define	EHC_PCF8591_CH_0	0x00
#define	EHC_PCF8591_CH_1	0x01
#define	EHC_PCF8591_CH_2	0x02
#define	EHC_PCF8591_CH_3	0x03


/*
 * PCF8574 Fan Fail, Power Supply Fail Detector
 * This device is driven by interrupts. Each time it interrupts
 * you must look at the CSR to see which ports caused the interrupt
 * they are indicated by a 1.
 *
 * Address map of this chip
 *
 * -------------------------------------------
 * | 0 | 1 | 1 | 1 | A2 | A1 | A0 | 0 |
 * -------------------------------------------
 *
 */

#define	EHC_PCF8574_PORT0	0x01
#define	EHC_PCF8574_PORT1	0x02
#define	EHC_PCF8574_PORT2	0x04
#define	EHC_PCF8574_PORT3	0x08
#define	EHC_PCF8574_PORT4	0x10
#define	EHC_PCF8574_PORT5	0x20
#define	EHC_PCF8574_PORT6	0x40
#define	EHC_PCF8574_PORT7	0x80

/*
 * Defines for the PCF8583 Clock Calendar Chip.
 */
#define	EHC_PCF8583_READ_BIT	0x01
#define	ALARM_CTR_REG_MINS	0x03
#define	ALARM_REG_MINS		0x0B
#define	ALARM_TIMER_REG		0x0F

struct eHc_pcd8584_regs {
	uint8_t s0;		/* Own Address S0' */
	uint8_t s1;		/* Control Status register */
	uint8_t clock_s2;	/* Clock programming register */
};

struct eHc_envcunit {
	struct eHc_pcd8584_regs *bus_ctl_regs;
	ddi_acc_handle_t ctlr_handle;
	kmutex_t umutex;
};


/*
 * Prototypes for static routines
 */

static int eHc_write_tda8444(struct eHc_envcunit *, int, int, int, uint8_t *,
	int);
static int eHc_read_pcf8591(struct eHc_envcunit *, int, int, int, int, int,
	uint8_t *, int);
static int eHc_read_pcf8574a(struct eHc_envcunit *, int, uint8_t *, int);
static int eHc_write_pcf8574a(struct eHc_envcunit *, int, uint8_t *, int);
static int eHc_read_pcf8574(struct eHc_envcunit *, int, uint8_t *, int);
static int eHc_write_pcf8574(struct eHc_envcunit *, int, uint8_t *, int);
static int eHc_read_lm75(struct eHc_envcunit *, int, uint8_t *, int);
static int eHc_write_pcf8583(struct eHc_envcunit *, int, uint8_t *, int);

static int eHc_start_pcf8584(struct eHc_envcunit *, uint8_t);
static void eHc_stop_pcf8584(struct eHc_envcunit *);
static int eHc_read_pcf8584(struct eHc_envcunit *, uint8_t *);
static int eHc_write_pcf8584(struct eHc_envcunit *, uint8_t);
static int eHc_after_read_pcf8584(struct eHc_envcunit *, uint8_t *);

/*
 * End of i2c protocol definitions section
 */

int
_init(void)
{
	int    error;

	if ((error = mod_install(&envctrlmodlinkage)) == 0) {
		(void) ddi_soft_state_init(&envctrlsoft_statep,
			sizeof (struct envctrlunit), 1);
	}

	return (error);
}

int
_fini(void)
{
	int    error;

	if ((error = mod_remove(&envctrlmodlinkage)) == 0)
		ddi_soft_state_fini(&envctrlsoft_statep);

	return (error);
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&envctrlmodlinkage, modinfop));
}

static int
envctrl_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	int	instance;
	char		name[16];
	uint8_t fspval;
	struct	envctrlunit *unitp;
	struct ddi_device_acc_attr attr;
	int *reg_prop;
	uchar_t *creg_prop;
	uint_t len, tblsz;
	int i, cputemp, status;
	uint8_t buf[3];

	status = len = tblsz = 0;

	attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
	attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;

	attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;

	instance = ddi_get_instance(dip);

	switch (cmd) {
	case DDI_ATTACH:
		break;
	case DDI_RESUME:
		if (!(unitp = ddi_get_soft_state(envctrlsoft_statep, instance)))
			return (DDI_FAILURE);
		mutex_enter(&unitp->umutex);
		if (!unitp->suspended) {
			mutex_exit(&unitp->umutex);
			return (DDI_FAILURE);
		}
		unitp->suspended = 0;
		mutex_exit(&unitp->umutex);
		unitp->initting = B_TRUE;
		envctrl_init_bus(unitp);
		unitp->initting = B_FALSE;

		mutex_enter(&unitp->umutex);
		envctrl_ps_probe(unitp);
		envctrl_probe_cpus(unitp);
		mutex_exit(&unitp->umutex);

		return (DDI_SUCCESS);

	default:
		return (DDI_FAILURE);
	}

	/* Set up timer values */
	overtemp_timeout_hz = drv_usectohz(OVERTEMP_TIMEOUT_USEC);
	blink_timeout_hz = drv_usectohz(BLINK_TIMEOUT_USEC);
	pshotplug_timeout_hz = drv_usectohz(BLINK_TIMEOUT_USEC * 6);

	if (ddi_soft_state_zalloc(envctrlsoft_statep, instance) != 0) {
		cmn_err(CE_WARN, "envctrl failed to zalloc softstate\n");
		goto failed;
	}

	unitp = ddi_get_soft_state(envctrlsoft_statep, instance);

	if (ddi_regs_map_setup(dip, 0, (caddr_t *)&unitp->bus_ctl_regs, 0,
			sizeof (struct envctrl_pcd8584_regs), &attr,
			&unitp->ctlr_handle) != DDI_SUCCESS) {
		cmn_err(CE_WARN, "I2c failed to map in bus_control regs\n");
		return (DDI_FAILURE);
	}

	/*
	 * If the PCI nexus has added a thermal interrupt, we first need
	 * to remove that interrupt handler.
	 *
	 * WARNING: Removing another driver's interrupt handler is not
	 * allowed. The pci_thermal_rem_intr() call below is needed to retain
	 * the legacy behavior on Tazmo systems.
	 */

	pci_thermal_rem_intr(dip, (uint_t)0);

	/* add interrupts */

	if (ddi_get_iblock_cookie(dip, 1,
			&unitp->ic_trap_cookie) != DDI_SUCCESS)  {
		cmn_err(CE_WARN, "ddi_get_iblock_cookie FAILED \n");
		goto failed;
	}

	mutex_init(&unitp->umutex, NULL, MUTEX_DRIVER,
		(void *)unitp->ic_trap_cookie);


	if (ddi_add_intr(dip, 0, &unitp->ic_trap_cookie, NULL, envctrl_bus_isr,
			(caddr_t)unitp) != DDI_SUCCESS) {
		cmn_err(CE_WARN, "envctrl_attach failed to add hard intr %d\n",
			instance);
		goto remlock;
	}


	if (ddi_add_intr(dip, 1, &unitp->ic_trap_cookie, NULL, envctrl_dev_isr,
			(caddr_t)unitp) != DDI_SUCCESS) {
		cmn_err(CE_WARN, "envctrl_attach failed to add hard intr %d\n",
			instance);
		goto remhardintr;
	}


	(void) sprintf(name, "envctrl%d", instance);

	if (ddi_create_minor_node(dip, name, S_IFCHR, instance, DDI_PSEUDO,
			NULL) == DDI_FAILURE) {
		ddi_remove_minor_node(dip, NULL);
		goto remhardintr1;
	}

	mutex_enter(&unitp->umutex);
	switch (ddi_getprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
	    ENVCTRL_LED_BLINK, -1)) {
	case 1:
		unitp->activity_led_blink = B_TRUE;
		break;
	case 0:
	default:
		unitp->activity_led_blink = B_FALSE;
		break;
	}
	unitp->shutdown = B_FALSE;
	unitp->num_ps_present = unitp->num_encl_present = 0;
	unitp->num_fans_present = MIN_FAN_BANKS;
	unitp->num_fans_failed = ENVCTRL_CHAR_ZERO;
	unitp->AFB_present = B_TRUE;
	unitp->dip = dip;

#ifdef	DEBUG
	if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip,
			DDI_PROP_DONTPASS, ENVCTRL_PANEL_LEDS_PR,
			&reg_prop, &len) == DDI_PROP_SUCCESS)
		ddi_prop_free((void *)reg_prop);
	ASSERT(len != 0);

	len = 0;

	if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip,
			DDI_PROP_DONTPASS, ENVCTRL_PANEL_LEDS_STA,
			&reg_prop, &len) == DDI_PROP_SUCCESS)
		ddi_prop_free((void *)reg_prop);
	ASSERT(len != 0);

	len = 0;

	if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip,
			DDI_PROP_DONTPASS, ENVCTRL_DISK_LEDS_STA,
			&reg_prop, &len) == DDI_PROP_SUCCESS)
		ddi_prop_free((void *)reg_prop);
	ASSERT(len != 0);
#endif	/* DEBUG */

	/*
	 * if we have prom fan tables, overide the static tables in
	 * header file.
	 */

	if (ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, dip,
			DDI_PROP_DONTPASS, "cpu-fan-speeds",
			&creg_prop, &len) == DDI_PROP_SUCCESS) {

		tblsz = (sizeof (acme_cpu_fanspd) / sizeof (short));

		if (len <= tblsz) {
			for (i = 0; i < len; i++) {
				acme_cpu_fanspd[i] = creg_prop[i];
			}
		}
		ddi_prop_free((void *)creg_prop);
	}

	len = 0;

	if (ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, dip,
			DDI_PROP_DONTPASS, "ps-fan-speeds",
			&creg_prop, &len) == DDI_PROP_SUCCESS) {

		tblsz = (sizeof (acme_ps_fanspd) / sizeof (short));

		if (len <= tblsz) {
			for (i = 0; i < len; i++) {
				acme_ps_fanspd[i] = creg_prop[i];
			}
		}
		ddi_prop_free((void *)creg_prop);
	}

	switch (ddi_getprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
	    "fan-override", -1)) {
	case 1:
	case 2:
		unitp->AFB_present = B_TRUE;
		break;
	case 0:
	default:
		unitp->AFB_present = B_FALSE;
		break;
	}

	/* For debug */
	if (envctrl_afb_present) {
		unitp->AFB_present = B_TRUE;
	}

	if (unitp->AFB_present == B_TRUE)
		unitp->num_fans_present++;

	/* initialize the envctrl bus controller */
	mutex_exit(&unitp->umutex);

	unitp->initting = B_TRUE;
	envctrl_init_bus(unitp);
	unitp->initting = B_FALSE;
	drv_usecwait(1000);

	mutex_enter(&unitp->umutex);

	/* Initialize the PCF8583 eggtimer registers */
	buf[0] = ALARM_CTR_REG_MINS;
	buf[1] = 0x0;
	status = eHc_write_pcf8583((struct eHc_envcunit *)unitp,
			PCF8583_BASE_ADDR | 0, buf, 2);
	if (status != DDI_SUCCESS)
		cmn_err(CE_WARN, "write to PCF8583 failed\n");

	buf[0] = ALARM_REG_MINS;
	buf[1] = 0x58;
	status = eHc_write_pcf8583((struct eHc_envcunit *)unitp,
			PCF8583_BASE_ADDR | 0, buf, 2);
	if (status != DDI_SUCCESS)
		cmn_err(CE_WARN, "write to PCF8583 failed\n");

	buf[0] = ALARM_TIMER_REG;
	buf[1] = 0x80;
	status = eHc_write_pcf8583((struct eHc_envcunit *)unitp,
			PCF8583_BASE_ADDR | 0, buf, 2);
	if (status != DDI_SUCCESS)
		cmn_err(CE_WARN, "write to PCF8583 failed\n");

	unitp->timeout_id = 0;
	unitp->blink_timeout_id = 0;

	if (envctrl_numcpus > 1) {
		unitp->num_cpus_present = envctrl_numcpus;
	}
	envctrl_probe_cpus(unitp);
	envctrl_ps_probe(unitp);
	/*
	 * clear the fan failures, if any before we do
	 * real work
	 */

	unitp->initting = B_TRUE;
	envctrl_fan_fail_service(unitp);
	unitp->initting = B_FALSE;

	/*
	 * we need to init the fan kstats before the tempr_poll
	 */
	envctrl_add_kstats(unitp);
	envctrl_init_fan_kstats(unitp);
	envctrl_init_encl_kstats(unitp);
	if (unitp->activity_led_blink == B_TRUE) {
		unitp->present_led_state = B_FALSE;
		mutex_exit(&unitp->umutex);
		envctrl_led_blink((void *)unitp);
		mutex_enter(&unitp->umutex);
	} else {
		fspval = ENVCTRL_FSP_ACTIVE;
		envctrl_set_fsp(unitp, &fspval);
	}

#ifndef TESTBED
	for (i = 0; i < ENVCTRL_MAX_CPUS; i++) {
		if (unitp->cpu_pr_location[i] == B_TRUE) {
			cputemp = envctrl_get_cpu_temp(unitp, i);
			envctrl_add_encl_kstats(unitp, ENVCTRL_ENCL_CPUTEMPR,
			    i, cputemp);
			if (cputemp >= MAX_CPU_TEMP) {
				if (!(envctrl_power_off_overide)) {
					cmn_err(CE_WARN,
					    "CPU %d OVERHEATING!!", i);
					unitp->shutdown = B_TRUE;
				} else {
					cmn_err(CE_WARN,
					    "CPU %d OVERHEATING!!", i);
				}
			}
		}
	}
#else
	cputemp = envctrl_get_cpu_temp(unitp, 0);
	envctrl_add_encl_kstats(unitp, ENVCTRL_ENCL_CPUTEMPR, INSTANCE_0,
	    cputemp);
#endif
	mutex_exit(&unitp->umutex);

	envctrl_tempr_poll((void *)unitp);

	/*
	 * interpose envctrl's abort sequence handler
	 */
	if (envctrl_handler) {
		abort_seq_handler = envctrl_abort_seq_handler;
	}

	ddi_report_dev(dip);

	return (DDI_SUCCESS);

remhardintr1:
	ddi_remove_intr(dip, (uint_t)1, unitp->ic_trap_cookie);
remhardintr:
	ddi_remove_intr(dip, (uint_t)0, unitp->ic_trap_cookie);

remlock:
	mutex_destroy(&unitp->umutex);

failed:
	if (unitp->ctlr_handle)
		ddi_regs_map_free(&unitp->ctlr_handle);

	cmn_err(CE_WARN, "envctrl_attach:failed.\n");

	return (DDI_FAILURE);

}

static int
envctrl_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	int		instance;
	struct envctrlunit *unitp;

	instance = ddi_get_instance(dip);
	unitp = ddi_get_soft_state(envctrlsoft_statep, instance);

	switch (cmd) {
	case DDI_DETACH:
		if (envctrl_allow_detach) {

			if (unitp->psksp != NULL) {
				kstat_delete(unitp->psksp);
			}
			if (unitp->fanksp != NULL) {
				kstat_delete(unitp->fanksp);
			}
			if (unitp->enclksp != NULL) {
				kstat_delete(unitp->enclksp);
			}

			if (unitp->timeout_id != 0) {
				(void) untimeout(unitp->timeout_id);
				unitp->timeout_id = 0;
			}
			if (unitp->blink_timeout_id != 0) {
				(void) untimeout(unitp->blink_timeout_id);
				unitp->blink_timeout_id = 0;
			}

			ddi_remove_minor_node(dip, NULL);

			ddi_remove_intr(dip, (uint_t)0, unitp->ic_trap_cookie);
			ddi_remove_intr(dip, (uint_t)1, unitp->ic_trap_cookie);

			ddi_regs_map_free(&unitp->ctlr_handle);

			mutex_destroy(&unitp->umutex);

			return (DDI_SUCCESS);
		} else {
			return (DDI_FAILURE);
		}

	case DDI_SUSPEND:
		if (!(unitp = ddi_get_soft_state(envctrlsoft_statep, instance)))
		    return (DDI_FAILURE);
		mutex_enter(&unitp->umutex);
		if (unitp->suspended) {
			cmn_err(CE_WARN, "envctrl already suspended\n");
			mutex_exit(&unitp->umutex);
			return (DDI_FAILURE);
		}
		unitp->suspended = 1;
		mutex_exit(&unitp->umutex);
		return (DDI_SUCCESS);

	default:
		cmn_err(CE_WARN, "envctrl suspend general fault\n");
		return (DDI_FAILURE);
	}


}

/* ARGSUSED */
int
envctrl_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
    void **result)
{
	dev_t	dev = (dev_t)arg;
	struct envctrlunit *unitp;
	int	ret;
	minor_t instance = getminor(dev);

	switch (infocmd) {
		case DDI_INFO_DEVT2DEVINFO:
			if ((unitp = (struct envctrlunit *)
				ddi_get_soft_state(envctrlsoft_statep,
				    instance)) != NULL) {
				*result = unitp->dip;
				ret = DDI_SUCCESS;
			} else {
				*result = NULL;
				ret = DDI_FAILURE;
			}
			break;
		case DDI_INFO_DEVT2INSTANCE:
			*result = (void *)(uintptr_t)instance;
			ret = DDI_SUCCESS;
			break;
		default:
			ret = DDI_FAILURE;
			break;
	}

	return (ret);
}

/* ARGSUSED */
static int
envctrl_open(queue_t *q, dev_t *dev, int flag, int sflag, cred_t *credp)
{
	struct envctrlunit *unitp;
	int status = 0;
	int	instance;

	instance = getminor(*dev);
	if (instance < 0)
		return (ENXIO);
	unitp = (struct envctrlunit *)
		    ddi_get_soft_state(envctrlsoft_statep, instance);

	if (unitp == NULL)
		return (ENXIO);

	mutex_enter(&unitp->umutex);

	if (flag & FWRITE) {
		if ((unitp->oflag & FWRITE)) {
			mutex_exit(&unitp->umutex);
			return (EBUSY);
		} else {
			unitp->oflag |= FWRITE;
		}
	}

	q->q_ptr = WR(q)->q_ptr = (caddr_t)unitp;

	/*
	 * if device is open with O_NONBLOCK flag set, let read(2) return 0
	 * if no data waiting to be read.  Writes will block on flow control.
	 */

	/* enable the stream */
	qprocson(q);

	unitp->readq = RD(q);
	unitp->writeq = WR(q);
	unitp->msg = (mblk_t *)NULL;

	mutex_exit(&unitp->umutex);
	return (status);
}

/* ARGSUSED */
static int
envctrl_close(queue_t *q, int flag, cred_t *cred_p)
{
	struct envctrlunit *unitp;

	unitp = (struct envctrlunit *)q->q_ptr;

	mutex_enter(&unitp->umutex);

	unitp->oflag = B_FALSE;
	unitp->current_mode = ENVCTRL_NORMAL_MODE;

	/* disable the stream */
	q->q_ptr = WR(q)->q_ptr = NULL;
	qprocsoff(q);

	mutex_exit(&unitp->umutex);
	return (DDI_SUCCESS);
}

/*
 * standard put procedure for envctrl
 */
static int
envctrl_wput(queue_t *q, mblk_t *mp)
{
	struct msgb *mp1;
	struct envctrlunit *unitp;
	struct iocblk *iocp;
	struct copyresp *csp;
	struct envctrl_tda8444t_chip *fanspeed;
	struct envctrl_pcf8574_chip *ledchip;
	struct envctrl_pcf8591_chip *temp, *a_fanspeed;
	struct copyreq *cqp;
	int cmd;

	unitp = (struct envctrlunit *)q->q_ptr;

	switch (DB_TYPE(mp)) {

	case M_DATA:

		while (mp) {
			DB_TYPE(mp) = M_DATA;
			mp1 = unlinkb(mp);
			mp->b_cont = NULL;
			if ((mp->b_wptr - mp->b_rptr) <= 0) {
				freemsg(mp);
			} else {
				(void) putq(q, mp);
			}
			mp = mp1;
		}

		break;

	case M_IOCTL:
	{
		iocp = (struct iocblk *)(void *)mp->b_rptr;
		cmd = iocp->ioc_cmd;

		switch (cmd) {
		case ENVCTRL_IOC_SETMODE:
		case ENVCTRL_IOC_GETMODE:
			if (iocp->ioc_count == TRANSPARENT) {
				mcopyin(mp, *(caddr_t *)mp->b_cont->b_rptr,
				    sizeof (uchar_t), NULL);
				qreply(q, mp);
			} else {
				miocnak(q, mp, 0, EINVAL);
			}
			break;
		case ENVCTRL_IOC_RESETTMPR:
			/*
			 * For diags, cancel the current temp poll
			 * and reset it for a new one.
			 */
			if (unitp->current_mode == ENVCTRL_DIAG_MODE) {
				if (unitp->timeout_id != 0) {
					(void) untimeout(unitp->timeout_id);
					unitp->timeout_id = 0;
				}
				envctrl_tempr_poll((void *)unitp);
				miocack(q, mp, 0, 0);
			} else {
				miocnak(q, mp, 0, EINVAL);
			}
			break;
		case ENVCTRL_IOC_GETTEMP:
			if (iocp->ioc_count == TRANSPARENT) {
				mcopyin(mp, *(caddr_t *)mp->b_cont->b_rptr,
				    sizeof (struct envctrl_pcf8591_chip), NULL);
				qreply(q, mp);
			} else {
				miocnak(q, mp, 0, EINVAL);
			}
			break;
		case ENVCTRL_IOC_SETTEMP:
			if (unitp->current_mode == ENVCTRL_DIAG_MODE &&
			    iocp->ioc_count == TRANSPARENT) {
				mcopyin(mp, *(caddr_t *)mp->b_cont->b_rptr,
				    sizeof (uint8_t), NULL);
				qreply(q, mp);
			} else {
				miocnak(q, mp, 0, EINVAL);
			}
			break;
		case ENVCTRL_IOC_SETWDT:
			if (unitp->current_mode == ENVCTRL_DIAG_MODE &&
			    iocp->ioc_count == TRANSPARENT) {
				mcopyin(mp, *(caddr_t *)mp->b_cont->b_rptr,
				    sizeof (uint8_t), NULL);
				qreply(q, mp);
			} else {
				miocnak(q, mp, 0, EINVAL);
			}
			break;
		case ENVCTRL_IOC_SETFAN:
			/*
			 * we must be in diag mode before we can
			 * set any fan speeds.
			 */
			if (unitp->current_mode == ENVCTRL_DIAG_MODE &&
			    iocp->ioc_count == TRANSPARENT) {
				mcopyin(mp, *(caddr_t *)mp->b_cont->b_rptr,
				    sizeof (struct envctrl_tda8444t_chip),
				    NULL);
				qreply(q, mp);
			} else {
				miocnak(q, mp, 0, EINVAL);
			}
			break;
		case ENVCTRL_IOC_GETFAN:
			if (iocp->ioc_count == TRANSPARENT) {
				mcopyin(mp, *(caddr_t *)mp->b_cont->b_rptr,
				    sizeof (struct envctrl_pcf8591_chip), NULL);
				qreply(q, mp);
			} else {
				miocnak(q, mp, 0, EINVAL);
			}
			break;
		case ENVCTRL_IOC_SETFSP:
			if (iocp->ioc_count == TRANSPARENT) {
				mcopyin(mp, *(caddr_t *)mp->b_cont->b_rptr,
				    sizeof (uint8_t), NULL);
				qreply(q, mp);
			} else {
				miocnak(q, mp, 0, EINVAL);
			}
			break;
		case ENVCTRL_IOC_SETDSKLED:
		case ENVCTRL_IOC_GETDSKLED:
			if (iocp->ioc_count == TRANSPARENT) {
				mcopyin(mp, *(caddr_t *)mp->b_cont->b_rptr,
				    sizeof (struct envctrl_pcf8574_chip), NULL);
				qreply(q, mp);
			} else {
				miocnak(q, mp, 0, EINVAL);
			}
			break;
		default:
			miocnak(q, mp, 0, EINVAL);
			break;
		}

		break;

	}
	case M_IOCDATA:
	{
		uint8_t *tempr, *wdval;
		long state;

		csp = (struct copyresp *)(void *)mp->b_rptr;

		/*
		 * If copy request failed, quit now
		 */
		if (csp->cp_rval != 0) {
			miocnak(q, mp, 0, EINVAL);
			return (0);
		}

		cqp = (struct copyreq *)(void *)mp->b_rptr;

		cmd = csp->cp_cmd;
		state = (long)cqp->cq_private;

		switch (cmd) {
		case ENVCTRL_IOC_SETFAN:
			fanspeed = (struct envctrl_tda8444t_chip *)
			    (void *)mp->b_cont->b_rptr;
			mutex_enter(&unitp->umutex);
			if (envctrl_xmit(unitp, (caddr_t *)(void *)fanspeed,
			    fanspeed->type) == DDI_FAILURE) {
				/*
				 * Fix for a ADF bug
				 * move mutex to after fan fail call
				 * bugid 4016121
				 */
				envctrl_fan_fail_service(unitp);
				mutex_exit(&unitp->umutex);
				miocnak(q, mp, 0, EINVAL);
			} else {
				mutex_exit(&unitp->umutex);
				miocack(q, mp, 0, 0);
			}
			break;
		case ENVCTRL_IOC_SETFSP:
			wdval = (uint8_t *)(void *)mp->b_cont->b_rptr;
			mutex_enter(&unitp->umutex);
			/*
			 * If a user is in normal mode and they try
			 * to set anything other than a disk fault or
			 * a gen fault it is an invalid operation.
			 * in diag mode we allow everything to be
			 * twiddled.
			 */
			if (unitp->current_mode == ENVCTRL_NORMAL_MODE) {
				if (*wdval & ~ENVCTRL_FSP_USRMASK) {
					mutex_exit(&unitp->umutex);
					miocnak(q, mp, 0, EINVAL);
					break;
				}
			}
			envctrl_set_fsp(unitp, wdval);
			mutex_exit(&unitp->umutex);
			miocack(q, mp, 0, 0);
			break;
		case ENVCTRL_IOC_SETDSKLED:
			ledchip = (struct envctrl_pcf8574_chip *)
			    (void *)mp->b_cont->b_rptr;
			mutex_enter(&unitp->umutex);
			if (envctrl_set_dskled(unitp, ledchip)) {
				miocnak(q, mp, 0, EINVAL);
			} else {
				miocack(q, mp, 0, 0);
			}
			mutex_exit(&unitp->umutex);
			break;
		case ENVCTRL_IOC_GETDSKLED:
			if (state  == -1) {
				miocack(q, mp, 0, 0);
				break;
			}
			ledchip = (struct envctrl_pcf8574_chip *)
			    (void *)mp->b_cont->b_rptr;
			mutex_enter(&unitp->umutex);
			if (envctrl_get_dskled(unitp, ledchip)) {
				miocnak(q, mp, 0, EINVAL);
			} else {
				mcopyout(mp, (void *)-1,
				    sizeof (struct envctrl_pcf8574_chip),
				    csp->cp_private, NULL);
				qreply(q, mp);
			}
			mutex_exit(&unitp->umutex);
			break;
		case ENVCTRL_IOC_GETTEMP:
			/* Get the user buffer address */

			if (state  == -1) {
				miocack(q, mp, 0, 0);
				break;
			}
			temp = (struct envctrl_pcf8591_chip *)
			    (void *)mp->b_cont->b_rptr;
			mutex_enter(&unitp->umutex);
			envctrl_recv(unitp, (caddr_t *)(void *)temp, PCF8591);
			mutex_exit(&unitp->umutex);
			mcopyout(mp, (void *)-1,
			    sizeof (struct envctrl_pcf8591_chip),
			    csp->cp_private, NULL);
			qreply(q, mp);
			break;
		case ENVCTRL_IOC_GETFAN:
			/* Get the user buffer address */

			if (state == -1) {
				miocack(q, mp, 0, 0);
				break;
			}
			a_fanspeed = (struct envctrl_pcf8591_chip *)
			    (void *)mp->b_cont->b_rptr;
			mutex_enter(&unitp->umutex);
			envctrl_recv(unitp, (caddr_t *)(void *)a_fanspeed,
				PCF8591);
			mutex_exit(&unitp->umutex);
			mcopyout(mp, (void *)-1,
			    sizeof (struct envctrl_pcf8591_chip),
			    csp->cp_private, NULL);
			qreply(q, mp);
			break;
		case ENVCTRL_IOC_SETTEMP:
			tempr = (uint8_t *)(void *)mp->b_cont->b_rptr;
			if (*tempr > MAX_DIAG_TEMPR) {
				miocnak(q, mp, 0, EINVAL);
			} else {
				mutex_enter(&unitp->umutex);
				envctrl_get_sys_temperatures(unitp, tempr);
				mutex_exit(&unitp->umutex);
				miocack(q, mp, 0, 0);
			}
			break;
		case ENVCTRL_IOC_SETWDT:
			/* reset watchdog timeout period */
			wdval = (uint8_t *)(void *)mp->b_cont->b_rptr;
			if (*wdval > MAX_CL_VAL) {
				miocnak(q, mp, 0, EINVAL);
			} else {
				mutex_enter(&unitp->umutex);
				envctrl_reset_watchdog(unitp, wdval);
				mutex_exit(&unitp->umutex);
				miocack(q, mp, 0, 0);
			}
			break;
		case ENVCTRL_IOC_GETMODE:
			/* Get the user buffer address */

			if (state == -1) {
				miocack(q, mp, 0, 0);
				break;
			}
			tempr = (uchar_t *)(void *)mp->b_cont->b_rptr;
			*tempr = unitp->current_mode;
			mcopyout(mp, (void *)-1, sizeof (uchar_t),
			    csp->cp_private, NULL);
			qreply(q, mp);
			break;
		case ENVCTRL_IOC_SETMODE:
			/* Set mode */
			wdval = (uint8_t *)(void *)mp->b_cont->b_rptr;
			if (*wdval == ENVCTRL_DIAG_MODE || *wdval ==
			    ENVCTRL_NORMAL_MODE) {
				mutex_enter(&unitp->umutex);
				unitp->current_mode = *wdval;
				if (unitp->timeout_id != 0 &&
				    *wdval == ENVCTRL_DIAG_MODE) {
					(void) untimeout(unitp->timeout_id);
					unitp->timeout_id =
					    (timeout(envctrl_tempr_poll,
					(caddr_t)unitp, overtemp_timeout_hz));

				}
				if (*wdval == ENVCTRL_NORMAL_MODE) {
					envctrl_get_sys_temperatures(unitp,
					    (uint8_t *)NULL);
					/*
					 * going to normal mode we
					 * need to go to diag mode
					 * just in case we have
					 * injected a fan fault. It
					 * may not be cleared and if
					 * we call fan_failsrvc it will
					 * power off the ystem if we are
					 * in NORMAL_MODE. Also we need
					 * to delay 1 bit of time here
					 * to  allow the fans to rotate
					 * back up and clear the intr
					 * after we get the sys temps.
					 */
					unitp->current_mode =
					    ENVCTRL_DIAG_MODE;
					envctrl_fan_fail_service(unitp);
					unitp->current_mode =
					    ENVCTRL_NORMAL_MODE;
				}
				mutex_exit(&unitp->umutex);
				miocack(q, mp, 0, 0);
			} else {
				miocnak(q, mp, 0, EINVAL);
			}
			break;
		default:
			freemsg(mp);
			break;
		}

		break;
	}

	case M_FLUSH:
		if (*mp->b_rptr & FLUSHR) {
			*mp->b_rptr &= ~FLUSHW;
			qreply(q, mp);
		} else {
			freemsg(mp);
		}
		break;

	default:
		freemsg(mp);
		break;
	}

	return (0);
}

uint_t
envctrl_bus_isr(caddr_t arg)
{
	struct envctrlunit *unitp = (struct envctrlunit *)(void *)arg;
	int ic = DDI_INTR_UNCLAIMED;

	mutex_enter(&unitp->umutex);

	/*
	 * NOT USED
	 */

	mutex_exit(&unitp->umutex);
	return (ic);
}

uint_t
envctrl_dev_isr(caddr_t arg)
{
	struct envctrlunit *unitp = (struct envctrlunit *)(void *)arg;
	uint8_t recv_data;
	int ic;
	int retrys = 0;
	int status;

	ic = DDI_INTR_UNCLAIMED;

	mutex_enter(&unitp->umutex);

	/*
	 * First check to see if it is an interrupt for us by
	 * looking at the "ganged" interrrupt and vector
	 * according to the major type
	 * 0x70 is the addr of the ganged interrupt controller.
	 * Address map for the port byte read is as follows
	 * MSB
	 * -------------------------
	 * |  |  |  |  |  |  |  |  |
	 * -------------------------
	 *  P7 P6 P5 P4 P3 P2 P1 P0
	 * P0 = Power Supply 1 intr
	 * P1 = Power Supply 2 intr
	 * P2 = Power Supply 3 intr
	 * P3 = Dlfop enable for fan sped set
	 * P4 = ENVCTRL_ Fan Fail intr
	 * P5 =	Front Panel Interrupt
	 * P6 = Power Fail Detect Low.
	 * P7 = Enable Interrupts to system
	 */

retry:

	status = eHc_read_pcf8574a((struct eHc_envcunit *)unitp,
		PCF8574A_BASE_ADDR | ENVCTRL_PCF8574_DEV0, &recv_data, 1);

	/*
	 * This extra read is needed since the first read is discarded
	 * and the second read seems to return 0xFF.
	 */
	if (recv_data == 0xFF) {
		status = eHc_read_pcf8574a((struct eHc_envcunit *)unitp,
		PCF8574A_BASE_ADDR | ENVCTRL_PCF8574_DEV0, &recv_data, 1);
	}
	if (envctrl_debug_flags)
		cmn_err(CE_WARN, "envctrl_dev_isr: status= %d, data = %x\n",
			status, recv_data);

	/*
	 * if the i2c bus is hung it is imperative that this
	 * be cleared on an interrupt or else it will
	 * hang the system with continuous interrupts
	 */

	if (status == DDI_FAILURE) {
		drv_usecwait(1000);
		if (retrys < envctrl_max_retries) {
			retrys++;
			goto retry;
		} else {
			if (envctrl_debug_flags)
				cmn_err(CE_WARN,
				    "DEVISR FAILED received 0x%x\n", recv_data);
			mutex_exit(&unitp->umutex);
			envctrl_init_bus(unitp);
			mutex_enter(&unitp->umutex);
			envctrl_ps_probe(unitp);
			mutex_exit(&unitp->umutex);
			ic = DDI_INTR_CLAIMED;
			return (ic);
		}
	}

	/*
	 * Port 0 = PS1 interrupt
	 * Port 1 = PS2 Interrupt
	 * Port 2 = PS3 Interrupt
	 * Port 3 = SPARE
	 * Port 4 = Fan Fail Intr
	 * Port 5 = Front Panle Module intr
	 * Port 6 = Keyswitch Intr
	 * Port 7 = ESINTR ENABLE ???
	 */

	if (!(recv_data & ENVCTRL_PCF8574_PORT0)) {
		envctrl_PS_intr_service(unitp, PS1);
		ic = DDI_INTR_CLAIMED;
	}

	if (!(recv_data & ENVCTRL_PCF8574_PORT1)) {
		envctrl_PS_intr_service(unitp, PS2);
		ic = DDI_INTR_CLAIMED;
	}

	if (!(recv_data & ENVCTRL_PCF8574_PORT2)) {
		envctrl_PS_intr_service(unitp, PS3);
		ic = DDI_INTR_CLAIMED;
	}

	if (!(recv_data & ENVCTRL_PCF8574_PORT3)) {
		ic = DDI_INTR_CLAIMED;
	}

	if (!(recv_data & ENVCTRL_PCF8574_PORT4)) {
		/*
		 * Check for a fan fail
		 * Single fan fail
		 * shutdown system
		 */
		envctrl_fan_fail_service(unitp);
		ic = DDI_INTR_CLAIMED;
	}

	if (!(recv_data & ENVCTRL_PCF8574_PORT5)) {
		(void) envctrl_get_fpm_status(unitp);
		ic = DDI_INTR_CLAIMED;
	}

	if (!(recv_data & ENVCTRL_PCF8574_PORT6)) {
		ic = DDI_INTR_CLAIMED;
	}

	if (!(recv_data & ENVCTRL_PCF8574_PORT7)) {
		ic = DDI_INTR_CLAIMED;
	}

	if ((recv_data == 0xFF)) {
		ic = DDI_INTR_CLAIMED;
	}

	mutex_exit(&unitp->umutex);
	return (ic);

}

static void
envctrl_init_bus(struct envctrlunit *unitp)
{

	int i;
	uint8_t noval = NULL;
	struct envctrl_tda8444t_chip fan;
	int fans[] = {ENVCTRL_CPU_FANS, ENVCTRL_PS_FANS, ENVCTRL_AFB_FANS};

	mutex_enter(&unitp->umutex);
	/* Sets the Mode to 808x type bus */
	ddi_put8(unitp->ctlr_handle,
	    &unitp->bus_ctl_regs->s0, ENVCTRL_CHAR_ZERO);

	/* SET UP SLAVE ADDR XXX Required..send 0x80 */

	ddi_put8(unitp->ctlr_handle, &unitp->bus_ctl_regs->s1,
	ENVCTRL_BUS_INIT0);
	(void) ddi_put8(unitp->ctlr_handle, &unitp->bus_ctl_regs->s0,
	ENVCTRL_BUS_INIT1);

	/* Set the clock now */
	ddi_put8(unitp->ctlr_handle,
	    &unitp->bus_ctl_regs->s1, ENVCTRL_BUS_CLOCK0);

	/* S0 is now S2  necause of the previous write to S1 */
	/* clock= 12MHz, SCL=90KHz */
	ddi_put8(unitp->ctlr_handle,
	    &unitp->bus_ctl_regs->s0, ENVCTRL_BUS_CLOCK1);

	/* Enable serial interface */
	ddi_put8(unitp->ctlr_handle,
	    &unitp->bus_ctl_regs->s1, ENVCTRL_BUS_ESI);

	envctrl_stop_clock(unitp);

	/*
	 * This has been added here because the DAC is powered
	 * on at "0". When the reset_dflop routine is called
	 * this switched the  fans from blast to DAC control.
	 * if the DAC is at "0", then the fans momentarily lose
	 * power until the temp polling and fan set routine is
	 * first called. If the fans lose power, then there is
	 * a fan fault generated and the system will power off.
	 * We only want to do this IF the bus is first being
	 * initted. This will cause errors in Sunvts if we reset
	 * the fan speed under normal operation. Sometimes we need
	 * to be able to induce fan faults. Init bus is a common
	 * routine to unwedge the i2c bus in some cases.
	 */

	if (unitp->initting == B_TRUE) {
		fan.chip_num = ENVCTRL_TDA8444T_DEV7;
		fan.val = INIT_FAN_VAL;

		for (i = 0; i < sizeof (fans)/sizeof (int); i++) {
			fan.fan_num = fans[i];
			if ((fans[i] == ENVCTRL_AFB_FANS) &&
				(unitp->AFB_present == B_FALSE))
				continue;
			(void) envctrl_xmit(unitp, (caddr_t *)(void *)&fan,
				TDA8444T);
		}
	}

	envctrl_reset_dflop(unitp);

	envctrl_enable_devintrs(unitp);

	unitp->current_mode = ENVCTRL_NORMAL_MODE;
	envctrl_reset_watchdog(unitp, &noval);

	mutex_exit(&unitp->umutex);
}

static int
envctrl_xmit(struct envctrlunit *unitp, caddr_t *data, int chip_type)
{

	struct envctrl_tda8444t_chip *fanspeed;
	struct envctrl_pcf8574_chip *ioport;
	uint8_t slave_addr;
	uint8_t buf[2];
	int retrys = 0;
	int status;

	ASSERT(MUTEX_HELD(&unitp->umutex));

	switch (chip_type) {
	case TDA8444T:

		fanspeed = (struct envctrl_tda8444t_chip *)data;

		if (fanspeed->chip_num > ENVCTRL_FAN_ADDR_MAX) {
			return (DDI_FAILURE);
		}

		if (fanspeed->fan_num > ENVCTRL_PORT7) {
			return (DDI_FAILURE);
		}

		if (fanspeed->val > MAX_FAN_VAL) {
			return (DDI_FAILURE);
		}

retry0:
		slave_addr = (TDA8444T_BASE_ADDR | fanspeed->chip_num);
		buf[0] = fanspeed->val;

		status = eHc_write_tda8444((struct eHc_envcunit *)unitp,
			TDA8444T_BASE_ADDR | fanspeed->chip_num, 0xF,
			fanspeed->fan_num, buf, 1);
		if (status != DDI_SUCCESS) {
			drv_usecwait(1000);
			if (retrys < envctrl_max_retries) {
				retrys++;
				goto retry0;
			} else {
				mutex_exit(&unitp->umutex);
				envctrl_init_bus(unitp);
				mutex_enter(&unitp->umutex);
				if (envctrl_debug_flags)
					cmn_err(CE_WARN,
					    "envctrl_xmit: Write to TDA8444 " \
					    "failed\n");
				return (DDI_FAILURE);
			}
		}

		/*
		 * Update the kstats.
		 */
		switch (fanspeed->fan_num) {
		case ENVCTRL_CPU_FANS:
			unitp->fan_kstats[ENVCTRL_FAN_TYPE_CPU].fanspeed =
			    fanspeed->val;
			break;
		case ENVCTRL_PS_FANS:
			unitp->fan_kstats[ENVCTRL_FAN_TYPE_PS].fanspeed =
			    fanspeed->val;
			break;
		case ENVCTRL_AFB_FANS:
			unitp->fan_kstats[ENVCTRL_FAN_TYPE_AFB].fanspeed =
			    fanspeed->val;
			break;
		default:
			break;
		}
		break;
	case PCF8574:
		ioport = (struct envctrl_pcf8574_chip *)data;
		buf[0] = ioport->val;
		if (ioport->chip_num > ENVCTRL_PCF8574_DEV7)
			return (DDI_FAILURE);

retry:
		if (ioport->type == PCF8574A) {
			slave_addr = (PCF8574A_BASE_ADDR | ioport->chip_num);
			status =
				eHc_write_pcf8574a((struct eHc_envcunit *)unitp,
				PCF8574A_BASE_ADDR | ioport->chip_num, buf, 1);
		} else {
			slave_addr = (PCF8574_BASE_ADDR | ioport->chip_num);
			status = eHc_write_pcf8574((struct eHc_envcunit *)unitp,
				PCF8574_BASE_ADDR | ioport->chip_num, buf, 1);
		}

		if (status != DDI_SUCCESS) {
			drv_usecwait(1000);
			if (retrys < envctrl_max_retries) {
				retrys++;
				goto retry;
			} else {
				mutex_exit(&unitp->umutex);
				envctrl_init_bus(unitp);
				mutex_enter(&unitp->umutex);
				if (envctrl_debug_flags)
					cmn_err(CE_WARN, "Write to PCF8574 " \
					    "failed, addr = %X\n", slave_addr);
				if (envctrl_debug_flags)
					cmn_err(CE_WARN, "envctrl_xmit: PCF8574\
						dev = %d, port = %d\n",
						ioport->chip_num, ioport->type);
				return (DDI_FAILURE);
			}
		}
		break;

	default:
		return (DDI_FAILURE);
	}

	return (DDI_SUCCESS);
}

static void
envctrl_recv(struct envctrlunit *unitp, caddr_t *data, int chip_type)
{

	struct envctrl_pcf8591_chip *temp;
	struct envctrl_pcf8574_chip *ioport;
	uint8_t slave_addr, recv_data;
	int retrys = 0;
	int status;
	uint8_t buf[1];

	ASSERT(MUTEX_HELD(&unitp->umutex));

	switch (chip_type) {
	case PCF8591:
		temp = (struct envctrl_pcf8591_chip *)data;
		slave_addr = (PCF8591_BASE_ADDR | temp->chip_num);

retry:
		status = eHc_read_pcf8591((struct eHc_envcunit *)unitp,
			PCF8591_BASE_ADDR | temp->chip_num & 0xF,
			temp->sensor_num, 0, 0, 1, &recv_data, 1);

		/*
		 * another place to catch the i2c bus hang on an 8591 read
		 * In this instance we will just return the data that is read
		 * after the max_retry because this could be a valid value.
		 */
		if (status != DDI_SUCCESS) {
			drv_usecwait(1000);
			if (retrys < envctrl_max_retries) {
				retrys++;
				goto retry;
			} else {
				mutex_exit(&unitp->umutex);
				envctrl_init_bus(unitp);
				mutex_enter(&unitp->umutex);
				if (envctrl_debug_flags)
					cmn_err(CE_WARN, "Read from PCF8591 " \
					    "failed, slave_addr = %x\n",
					    slave_addr);
			}
		}
		temp->temp_val = recv_data;
		break;
	case TDA8444T:
		printf("envctrl_recv: attempting to read TDA8444T\n");
		return;
	case PCF8574:
		ioport = (struct envctrl_pcf8574_chip *)data;

retry1:
		if (ioport->chip_num > ENVCTRL_PCF8574_DEV7)
			cmn_err(CE_WARN, "envctrl: dev out of range 0x%x\n",
ioport->chip_num);

		if (ioport->type == PCF8574A) {
			slave_addr = (PCF8574_READ_BIT | PCF8574A_BASE_ADDR |
			    ioport->chip_num);
			status = eHc_read_pcf8574a((struct eHc_envcunit *)unitp,
				PCF8574A_BASE_ADDR | ioport->chip_num, buf, 1);
		} else {
			slave_addr = (PCF8574_READ_BIT | PCF8574_BASE_ADDR |
			    ioport->chip_num);
			status = eHc_read_pcf8574((struct eHc_envcunit *)unitp,
				PCF8574_BASE_ADDR | ioport->chip_num, buf, 1);
		}

		if (status != DDI_SUCCESS) {
			drv_usecwait(1000);
			if (retrys < envctrl_max_retries) {
				retrys++;
				goto retry1;
			} else {
				mutex_exit(&unitp->umutex);
				envctrl_init_bus(unitp);
				mutex_enter(&unitp->umutex);
				if (envctrl_debug_flags)
					cmn_err(CE_WARN, "Read from PCF8574 "\
					    "failed, addr = %X\n", slave_addr);
				if (envctrl_debug_flags)
					cmn_err(CE_WARN, "envctrl_recv: PCF8574\
						dev = %d, port = %d\n",
						ioport->chip_num, ioport->type);
			}
		}
		ioport->val = buf[0];
		break;
	default:
		break;
	}
}

static int
envctrl_get_ps_temp(struct envctrlunit *unitp, uint8_t psaddr)
{
	uint8_t tempr;
	int i, retrys;
	int status;
	uint8_t buf[4];

	ASSERT(MUTEX_HELD(&unitp->umutex));

	tempr = 0;
	retrys = 0;

retry:
	status = eHc_read_pcf8591((struct eHc_envcunit *)unitp,
			PCF8591_BASE_ADDR | psaddr & 0xF, 0, 1, 0, 1, buf, 4);

	tempr = 0;
	for (i = 0; i < PCF8591_MAX_PORTS; i++) {
		/*
		 * The pcf8591 will return 0xff if no port
		 * is there.. this is bogus for setting temps.
		 * so just ignore it!
		 */
		if (envctrl_debug_flags) {
			cmn_err(CE_WARN, "PS addr 0x%x recvd 0x%x on port %d\n",
			    psaddr, buf[i], i);
		}
		if (buf[i] > tempr && buf[i] < MAX_PS_ADVAL) {
			tempr = buf[i];
		}
	}

	/*
	 * This routine is a safeguard to make sure that if the
	 * powersupply temps cannot be read that we do something
	 * to make sure that the system will notify the user and
	 * it will stay running with the fans at 100%. The calling
	 * routine should take care of that.
	 */
	if (status != DDI_SUCCESS) {
		drv_usecwait(1000);
		if (retrys < envctrl_max_retries) {
			retrys++;
			goto retry;
		} else {
			mutex_exit(&unitp->umutex);
			envctrl_init_bus(unitp);
			mutex_enter(&unitp->umutex);
			if (envctrl_debug_flags)
				cmn_err(CE_WARN,
				    "Cannot read Power Supply Temps addr = %X",
				    psaddr);
			return (PS_DEFAULT_VAL);
		}
	}

	return (ps_temps[tempr]);
}

static int
envctrl_get_cpu_temp(struct envctrlunit *unitp, int cpunum)
{
	uint8_t recv_data;
	int retrys;
	int status;

	ASSERT(MUTEX_HELD(&unitp->umutex));

	/*
	 * This routine takes in the number of the port that
	 * we want to read in the 8591. This should be the
	 * location of the COU thermistor for one of the 4
	 * cpu's. It will return the temperature in degrees C
	 * to the caller.
	 */

	retrys = 0;

retry:
	status = eHc_read_pcf8591((struct eHc_envcunit *)unitp,
			PCF8591_BASE_ADDR | PCF8591_DEV7, cpunum, 0, 0, 0,
			&recv_data, 1);

	/*
	 * We need to take a sledge hammer to the bus if we get back
	 * value of the chip. This means that the i2c bus got wedged.
	 * On the 1.4 systems this happens sometimes while running
	 * sunvts. We will return the max cpu temp minus 10 to make
	 * the fans run at full speed so that we don;t cook the
	 * system.
	 * At this point this is a workaround for hardware glitch.
	 */
	if (status == DDI_FAILURE) {
		drv_usecwait(1000);
		if (retrys < envctrl_max_retries) {
			retrys++;
			goto retry;
		} else {
			mutex_exit(&unitp->umutex);
			envctrl_init_bus(unitp);
			mutex_enter(&unitp->umutex);
			if (envctrl_debug_flags)
				cmn_err(CE_WARN, "envctrl CPU TEMP read " \
				    "failed\n");
			/* we don't want to power off the system */
			return (MAX_CPU_TEMP - 10);
		}
	}

	return (cpu_temps[recv_data]);
}

static int
envctrl_get_lm75_temp(struct envctrlunit *unitp)
{

	int k;
	ushort_t lmval;
	uint8_t tmp1;
	uint8_t tmp2;
	int status;
	uint8_t buf[2];


	ASSERT(MUTEX_HELD(&unitp->umutex));

	status = eHc_read_lm75((struct eHc_envcunit *)unitp,
		LM75_BASE_ADDR | LM75_CONFIG_ADDRA, buf, 2);
	if (status != DDI_SUCCESS)
		cmn_err(CE_WARN, "read of LM75 failed\n");

	tmp1 = buf[0];
	tmp2 = buf[1];

	/*
	 * Store the forst 8 bits in the upper nibble of the
	 * short, then store the lower 8 bits in the lower nibble
	 * of the short, shift 7 to the right to get the 9 bit value
	 * that the lm75 is really sending.
	 */
	lmval = tmp1 << 8;
	lmval = (lmval | tmp2);
	lmval = (lmval >> 7);
	/*
	 * Check the 9th bit to see if it is a negative
	 * temperature. If so change into 2's compliment
	 * and divide by 2 since each value is equal to a
	 * half degree strp in degrees C
	 */
	if (lmval & LM75_COMP_MASK) {
		tmp1 = (lmval & LM75_COMP_MASK_UPPER);
		tmp1 = -tmp1;
		tmp1 = tmp1/2;
		k = 0 - tmp1;
	} else {
		k = lmval /2;
	}
		return (k);
}


static void
envctrl_tempr_poll(void *arg)
{
	int diag_flag = 0;
	struct envctrlunit *unitp = (struct envctrlunit *)arg;

	mutex_enter(&unitp->umutex);

	if (unitp->shutdown == B_TRUE) {
		(void) power_down("Fatal System Environmental Control Error");
	}

	/*
	 * if we are in diag mode and the temp poll thread goes off,
	 * this means that the system is too heavily loaded and the 60 second
	 * window to execute the test is failing. We will change the fanspeed
	 * but will not check for a fanfault. This will cause a system shutdown
	 * if the system has had a fanfault injected.
	 */
	if (unitp->current_mode == ENVCTRL_DIAG_MODE) {
		diag_flag++;
		if (envctrl_debug_flags) {
			cmn_err(CE_WARN,
			    "Tempr poll went off while in DIAG MODE");
		}
	}
	unitp->current_mode = ENVCTRL_NORMAL_MODE;
	envctrl_get_sys_temperatures(unitp, (uint8_t *)NULL);
	if (diag_flag == 0) {
		envctrl_fan_fail_service(unitp);
	}
	/* now have this thread sleep for a while */
	unitp->timeout_id = (timeout(envctrl_tempr_poll,
	    (caddr_t)unitp, overtemp_timeout_hz));

	mutex_exit(&unitp->umutex);
}

static void
envctrl_led_blink(void *arg)
{
	struct envctrl_pcf8574_chip fspchip;
	struct envctrlunit *unitp = (struct envctrlunit *)arg;

	mutex_enter(&unitp->umutex);

	fspchip.type = PCF8574A;
	fspchip.chip_num = ENVCTRL_PCF8574_DEV6; /* 0x01 port 1 */
	envctrl_recv(unitp, (caddr_t *)(void *)&fspchip, PCF8574);

	if (unitp->present_led_state == B_TRUE) {
		/*
		 * Now we need to "or" in fault bits of the FSP
		 * module for the mass storage fault led.
		 * and set it.
		 */
		fspchip.val = (fspchip.val & ~(ENVCTRL_PCF8574_PORT4) |
				    0xC0);
		unitp->present_led_state = B_FALSE;
	} else {
		fspchip.val = (fspchip.val | ENVCTRL_PCF8574_PORT4 | 0xC0);
		unitp->present_led_state = B_TRUE;
	}

	(void) envctrl_xmit(unitp, (caddr_t *)(void *)&fspchip, PCF8574);

	/* now have this thread sleep for a while */
	unitp->blink_timeout_id = (timeout(envctrl_led_blink,
	    (caddr_t)unitp, blink_timeout_hz));

	mutex_exit(&unitp->umutex);
}

/* called with mutex held */
static void
envctrl_get_sys_temperatures(struct envctrlunit *unitp, uint8_t *diag_tempr)
{
	int temperature, tmptemp, cputemp, hicputemp, ambtemp;
	int i;
	struct envctrl_tda8444t_chip fan;
	uint8_t psaddr[] = {PSTEMP3, PSTEMP2, PSTEMP1, PSTEMP0};
	uint8_t noval = NULL;
	uint8_t fspval;

	ASSERT(MUTEX_HELD(&unitp->umutex));

	fan.fan_num = ENVCTRL_CPU_FANS;
	fan.chip_num = ENVCTRL_TDA8444T_DEV7;

	tmptemp = 0;	/* Right init value ?? */

	/*
	 * THis routine is caled once every minute
	 * we wil re-se the watchdog timer each time
	 * we poll the temps. The watchdog timer is
	 * set up for 3 minutes. Should the kernel thread
	 * wedge, for some reason the watchdog will go off
	 * and blast the fans.
	 */

	if (unitp->current_mode == ENVCTRL_DIAG_MODE) {
		unitp->current_mode = ENVCTRL_NORMAL_MODE;
		envctrl_reset_watchdog(unitp, &noval);
		unitp->current_mode = ENVCTRL_DIAG_MODE;
	} else {
		envctrl_reset_watchdog(unitp, &noval);
	}

	/*
	 * we need to reset the dflop to allow the fans to be
	 * set if the watchdog goes of and the kernel resumes
	 * resetting the dflop alos resets the device interrupts
	 * we need to reenable them also.
	 */
	envctrl_reset_dflop(unitp);

	envctrl_enable_devintrs(unitp);

	/*
	 * If we are in diag mode we allow the system to be
	 * faked out as to what the temperature is
	 * to see if the fans speed up.
	 */
	if (unitp->current_mode == ENVCTRL_DIAG_MODE && diag_tempr != NULL) {
		if (unitp->timeout_id != 0) {
		    (void) untimeout(unitp->timeout_id);
		}

		ambtemp = *diag_tempr;
		unitp->timeout_id = (timeout(envctrl_tempr_poll,
		    (caddr_t)unitp, overtemp_timeout_hz));
	} else {
		ambtemp = envctrl_get_lm75_temp(unitp);
		/*
		 * Sometimes when we read the temp it comes back bogus
		 * to fix this we just need to reset the envctrl bus
		 */
		if (ambtemp == -100) {
			mutex_exit(&unitp->umutex);
			envctrl_init_bus(unitp);
			mutex_enter(&unitp->umutex);
			ambtemp = envctrl_get_lm75_temp(unitp);
		}
	}

	envctrl_mod_encl_kstats(unitp, ENVCTRL_ENCL_AMBTEMPR, INSTANCE_0,
	    ambtemp);

	fspval = envctrl_get_fpm_status(unitp);

	if (ambtemp > MAX_AMB_TEMP) {
		fspval |= (ENVCTRL_FSP_TEMP_ERR | ENVCTRL_FSP_GEN_ERR);
		if (!(envctrl_power_off_overide) &&
		    unitp->current_mode == ENVCTRL_NORMAL_MODE) {
			unitp->shutdown = B_TRUE;
		}
			if (unitp->current_mode == ENVCTRL_NORMAL_MODE) {
				cmn_err(CE_WARN,
			"Ambient Temperature is %d C, shutdown now\n",
				    ambtemp);
			}
	} else {
		if (envctrl_isother_fault_led(unitp, fspval,
			ENVCTRL_FSP_TEMP_ERR)) {
			fspval &= ~(ENVCTRL_FSP_TEMP_ERR);
		} else {
			fspval &= ~(ENVCTRL_FSP_TEMP_ERR | ENVCTRL_FSP_GEN_ERR);
		}
	}

	envctrl_set_fsp(unitp, &fspval);

	cputemp = hicputemp = 0;
#ifndef TESTBED
	for (i = 0; i < ENVCTRL_MAX_CPUS; i++) {
		if (unitp->cpu_pr_location[i] == B_TRUE) {
			cputemp = envctrl_get_cpu_temp(unitp, i);
			envctrl_mod_encl_kstats(unitp, ENVCTRL_ENCL_CPUTEMPR,
			    i, cputemp);
			if (cputemp >= MAX_CPU_TEMP) {
				if (!(envctrl_power_off_overide)) {
					unitp->shutdown = B_TRUE;
				}
				cmn_err(CE_WARN,
				    "CPU %d OVERHEATING!!!", i);
			}

			if (cputemp > hicputemp) {
				hicputemp = cputemp;
			}
		}
	}
#else
	cputemp = envctrl_get_cpu_temp(unitp, 0);
	envctrl_mod_encl_kstats(unitp, ENVCTRL_ENCL_CPUTEMPR, 0, cputemp);
#endif

	fspval = envctrl_get_fpm_status(unitp);

	/*
	 * We first look at the ambient temp. If the system is at idle
	 * the cpu temps will be approx 20 degrees above ambient.
	 * If the cpu's rise above 20, then the CPU fans are set
	 * according to the cpu temp minus 20 degrees C.
	 */
	if (unitp->current_mode == ENVCTRL_DIAG_MODE && diag_tempr != NULL) {
		temperature = ambtemp;
	} else {
		temperature = hicputemp - CPU_AMB_RISE;
	}

	if (temperature < 0) {
		fan.val = MAX_FAN_SPEED; 	/* blast it is out of range */
	} else if (temperature > MAX_AMB_TEMP) {
		fan.val = MAX_FAN_SPEED;
		fspval |= (ENVCTRL_FSP_TEMP_ERR | ENVCTRL_FSP_GEN_ERR);

			if (unitp->current_mode == ENVCTRL_NORMAL_MODE) {
				cmn_err(CE_WARN,
				    "CPU Fans set to MAX. CPU Temp is %d C\n",
				    hicputemp);
			}
	} else if (ambtemp < MAX_AMB_TEMP) {
		if (!envctrl_p0_enclosure) {
			fan.val = acme_cpu_fanspd[temperature];
		} else {
			fan.val = fan_speed[temperature];
		}
		if (envctrl_isother_fault_led(unitp, fspval,
			ENVCTRL_FSP_TEMP_ERR)) {
			fspval &= ~(ENVCTRL_FSP_TEMP_ERR);
		} else {
			fspval &= ~(ENVCTRL_FSP_TEMP_ERR | ENVCTRL_FSP_GEN_ERR);
		}
	}

	envctrl_set_fsp(unitp, &fspval);

	/*
	 * Update temperature kstats. FSP kstats are updated in the
	 * set and get routine.
	 */

	unitp->fan_kstats[ENVCTRL_FAN_TYPE_CPU].fanspeed = fan.val;

	/* CPU FANS */
	(void) envctrl_xmit(unitp, (caddr_t *)(void *)&fan, TDA8444T);

	/* The afb Fan is always at max */
	if (unitp->AFB_present == B_TRUE) {
		fan.val = AFB_MAX;
		/* AFB FANS */
		unitp->fan_kstats[ENVCTRL_FAN_TYPE_AFB].fanspeed = fan.val;
		fan.fan_num = ENVCTRL_AFB_FANS;
		(void) envctrl_xmit(unitp, (caddr_t *)(void *)&fan, TDA8444T);
	}

	/*
	 * Now set the Powersupply fans
	 */

	tmptemp = temperature = 0;
	for (i = 0; i <= MAXPS; i++) {
		if (unitp->ps_present[i]) {
			tmptemp = envctrl_get_ps_temp(unitp, psaddr[i]);
			unitp->ps_kstats[i].ps_tempr = tmptemp & 0xFFFF;
			if (tmptemp > temperature) {
				temperature = tmptemp;
			}
			if (temperature >= MAX_PS_TEMP) {
				if (!(envctrl_power_off_overide)) {
					unitp->shutdown = B_TRUE;
				}
				cmn_err(CE_WARN,
				    "Power Supply %d OVERHEATING!!!\
				    Temp is %d C", i, temperature);
			}
		}
	}


	fan.fan_num = ENVCTRL_PS_FANS;
	if (temperature > PS_TEMP_WARN) {
		fspval = envctrl_get_fpm_status(unitp);
		fspval |= (ENVCTRL_FSP_TEMP_ERR | ENVCTRL_FSP_GEN_ERR);
		envctrl_set_fsp(unitp, &fspval);
		fan.val = MAX_FAN_SPEED;
		cmn_err(CE_WARN, "A Power Supply is close to  OVERHEATING!!!");
	} else {
		if (temperature - ambtemp > PS_AMB_RISE) {
			ambtemp = temperature - PS_AMB_RISE;
		}
		if (!envctrl_p0_enclosure) {
			fan.val = acme_ps_fanspd[ambtemp];
		} else {
			fan.val = ps_fans[ambtemp];
		}
	}

	/*
	 * XXX add in error condition for ps overtemp
	 */

	unitp->fan_kstats[ENVCTRL_FAN_TYPE_PS].fanspeed = fan.val;
	(void) envctrl_xmit(unitp, (caddr_t *)(void *)&fan, TDA8444T);
}

/* called with mutex held */
static void
envctrl_fan_fail_service(struct envctrlunit *unitp)
{
	uint8_t recv_data, fpmstat;
	int fantype;
	int psfanflt, cpufanflt, afbfanflt;
	int retries = 0, max_retry_count;
	int status;

	psfanflt = cpufanflt = afbfanflt = 0;
	/*
	 * The fan fail sensor is located at address 0x70
	 * on the envctrl bus.
	 */

	ASSERT(MUTEX_HELD(&unitp->umutex));

retry:
	status = eHc_read_pcf8574a((struct eHc_envcunit *)unitp,
		PCF8574A_BASE_ADDR | ENVCTRL_PCF8574_DEV4, &recv_data, 1);
	if (status != DDI_SUCCESS)
		cmn_err(CE_WARN, "fan_fail_service: status = %d, data = %x\n",
			status, recv_data);

	/*
	 * If all fan ports are high (0xff) then we don't have any
	 * fan faults. Reset the kstats
	 */
	if (recv_data == 0xff) {
		unitp->fan_kstats[ENVCTRL_FAN_TYPE_PS].fans_ok = B_TRUE;
		unitp->fan_kstats[ENVCTRL_FAN_TYPE_CPU].fans_ok = B_TRUE;
		unitp->fan_kstats[ENVCTRL_FAN_TYPE_AFB].fans_ok = B_TRUE;
		unitp->fan_kstats[ENVCTRL_FAN_TYPE_PS].fanflt_num = 0;
		unitp->fan_kstats[ENVCTRL_FAN_TYPE_CPU].fanflt_num = 0;
		unitp->fan_kstats[ENVCTRL_FAN_TYPE_AFB].fanflt_num = 0;
		unitp->num_fans_failed = 0;
		fpmstat = envctrl_get_fpm_status(unitp);
		if (!(envctrl_isother_fault_led(unitp, fpmstat, 0))) {
			fpmstat &= ~(ENVCTRL_FSP_GEN_ERR);
		}
		if (unitp->shutdown != B_TRUE) {
			envctrl_set_fsp(unitp, &fpmstat);
		}
		return;
	}

	fantype = ENVCTRL_FAN_TYPE_PS;

	if (!(recv_data & ENVCTRL_PCF8574_PORT0)) {
		psfanflt = PS_FAN_3;
	}
	if (!(recv_data & ENVCTRL_PCF8574_PORT1)) {
		psfanflt = PS_FAN_2;
	}
	if (!(recv_data & ENVCTRL_PCF8574_PORT2)) {
		psfanflt = PS_FAN_1;
	}

	if (psfanflt != 0) {
		unitp->fan_kstats[fantype].fans_ok = B_FALSE;
		unitp->fan_kstats[fantype].fanflt_num = psfanflt - 1;
		if (retries == MAX_FAN_FAIL_RETRY && status == DDI_SUCCESS &&
		    unitp->current_mode == ENVCTRL_NORMAL_MODE) {
			cmn_err(CE_WARN, "PS Fan Number %d Failed",
			    psfanflt - 1);
		}
	} else {
		unitp->fan_kstats[fantype].fans_ok = B_TRUE;
		unitp->fan_kstats[fantype].fanflt_num = 0;
	}

	fantype = ENVCTRL_FAN_TYPE_CPU;

	if (!(recv_data & ENVCTRL_PCF8574_PORT3)) {
		cpufanflt = CPU_FAN_1;
	}
	if (!(recv_data & ENVCTRL_PCF8574_PORT4)) {
		cpufanflt = CPU_FAN_2;
	}
	if (!(recv_data & ENVCTRL_PCF8574_PORT5)) {
		cpufanflt = CPU_FAN_3;
	}

	if (cpufanflt != 0) {
		unitp->fan_kstats[fantype].fans_ok = B_FALSE;
		unitp->fan_kstats[fantype].fanflt_num = cpufanflt - 1;
		if (retries == MAX_FAN_FAIL_RETRY && status == DDI_SUCCESS &&
		    unitp->current_mode == ENVCTRL_NORMAL_MODE) {
			cmn_err(CE_WARN, "CPU Fan Number %d Failed",
			    cpufanflt - 1);
		}
	} else {
		unitp->fan_kstats[fantype].fans_ok = B_TRUE;
		unitp->fan_kstats[fantype].fanflt_num = 0;
	}

	if (!(recv_data & ENVCTRL_PCF8574_PORT6) &&
		(unitp->AFB_present == B_TRUE)) {
		/*
		 * If the afb is present and the afb fan fails,
		 * we need to power off or else it will melt!
		 * If it isn't present just log the error.
		 * We make the decision off of the afbfanflt
		 * flag later on in an if statement.
		 */
		afbfanflt++;
		unitp->fan_kstats[ENVCTRL_FAN_TYPE_AFB].fans_ok
		    = B_FALSE;
		unitp->fan_kstats[ENVCTRL_FAN_TYPE_AFB].fanflt_num =
		    AFB_FAN_1;
		if (unitp->current_mode == ENVCTRL_NORMAL_MODE) {
			cmn_err(CE_WARN, "AFB Fan Failed");
		}

	}

	/*
	 * If we have no Fan Faults Clear the LED's
	 * If we have fan faults set the Gen Fault LED.
	 */
	if (psfanflt == 0 && cpufanflt == 0 && afbfanflt == 0 &&
	    unitp->num_fans_failed != 0) {
		fpmstat = envctrl_get_fpm_status(unitp);
		if (!(envctrl_isother_fault_led(unitp,
		    fpmstat, 0))) {
			fpmstat &= ~(ENVCTRL_FSP_GEN_ERR);
		}
		envctrl_set_fsp(unitp, &fpmstat);
	} else if (psfanflt != 0 || cpufanflt != 0 || afbfanflt != 0) {
		fpmstat = envctrl_get_fpm_status(unitp);
		fpmstat |= ENVCTRL_FSP_GEN_ERR;
		envctrl_set_fsp(unitp, &fpmstat);
	}

	if (unitp->AFB_present == B_FALSE) {
		afbfanflt = 0;
	}

	if ((cpufanflt > 0 || psfanflt > 0 || afbfanflt > 0 ||
		(status != DDI_SUCCESS)) && !unitp->initting &&
		unitp->current_mode == ENVCTRL_NORMAL_MODE) {
		if (status != DDI_SUCCESS)
			max_retry_count = envctrl_max_retries;
		else
			max_retry_count = MAX_FAN_FAIL_RETRY;
		if (retries <= max_retry_count) {
			retries++;
			drv_usecwait(1000);
			if (retries == max_retry_count) {
				cmn_err(CE_WARN,
				    "Fan Fail is 0x%x, retries = %d\n",
				    recv_data, retries);
			}
			envctrl_get_sys_temperatures(unitp,
			    (uint8_t *)NULL);
			goto retry;
		}
		if (!(envctrl_power_off_overide)) {
			unitp->shutdown = B_TRUE;
		}
		cmn_err(CE_WARN, "Fan Failure(s), System Shutdown");
	}

	unitp->num_fans_failed = (psfanflt + cpufanflt + afbfanflt);

}

/*
 * Check for power supply insertion and failure.
 * This is a bit tricky, because a power supply insertion will
 * trigger a load share interrupt as well as PS present in the
 * new supply. if we detect an insertion clear
 * interrupts, disable interrupts, wait for a couple of seconds
 * come back and see if the PSOK bit is set, PS_PRESENT is set
 * and the share fail interrupts are gone. If not this is a
 * real load share fail event.
 * Called with mutex held
 */

static void
envctrl_PS_intr_service(struct envctrlunit *unitp, uint8_t psaddr)
{
	uint8_t recv_data;
	int status, retrys = 0;

	ASSERT(MUTEX_HELD(&unitp->umutex));

	if (unitp->current_mode == ENVCTRL_DIAG_MODE) {
		return;
	}

retry:
	status = eHc_read_pcf8574a((struct eHc_envcunit *)unitp,
			PCF8574A_BASE_ADDR | psaddr & 0xF, &recv_data, 1);
	if (status != DDI_SUCCESS) {
		drv_usecwait(1000);
		if (retrys < envctrl_max_retries) {
			retrys++;
			goto retry;
		} else {
			mutex_exit(&unitp->umutex);
			envctrl_init_bus(unitp);
			mutex_enter(&unitp->umutex);
			if (envctrl_debug_flags)
				cmn_err(CE_WARN,
					"PS_intr_service: Read from 8574A " \
					    "failed\n");
		}
	}

	/*
	 * setup a timeout thread to poll the ps after a
	 * couple of seconds. This allows for the PS to settle
	 * and doesn't report false errors on a hotplug
	 */

	unitp->pshotplug_id = (timeout(envctrl_pshotplug_poll,
	    (caddr_t)unitp, pshotplug_timeout_hz));

}

/* called with mutex held */
static void
envctrl_reset_dflop(struct envctrlunit *unitp)
{
	struct envctrl_pcf8574_chip initval;

	ASSERT(MUTEX_HELD(&unitp->umutex));

	/*
	 * This initialization sequence allows a
	 * to change state to stop the fans from
	 * blastion upon poweron. If this isn't
	 * done the writes to the 8444 will not complete
	 * to the hardware because the dflop will
	 * be closed
	 */
	initval.chip_num = ENVCTRL_PCF8574_DEV0; /* 0x01 port 1 */
	initval.type = PCF8574A;

	initval.val = ENVCTRL_DFLOP_INIT0;
	(void) envctrl_xmit(unitp, (caddr_t *)(void *)&initval, PCF8574);

	initval.val = ENVCTRL_DFLOP_INIT1;
	(void) envctrl_xmit(unitp, (caddr_t *)(void *)&initval, PCF8574);
}

static void
envctrl_add_encl_kstats(struct envctrlunit *unitp, int type,
    int instance, uint8_t val)
{
	int i = 0;
	boolean_t inserted = B_FALSE;

	ASSERT(MUTEX_HELD(&unitp->umutex));

	while (i < MAX_DEVS && inserted == B_FALSE) {
		if (unitp->encl_kstats[i].instance == I2C_NODEV) {
			unitp->encl_kstats[i].instance = instance;
			unitp->encl_kstats[i].type = type;
			unitp->encl_kstats[i].value = val;
			inserted = B_TRUE;
		}
		i++;
	}
	unitp->num_encl_present++;
}

/* called with mutex held */
static void
envctrl_enable_devintrs(struct envctrlunit *unitp)
{
	struct envctrl_pcf8574_chip initval;

	ASSERT(MUTEX_HELD(&unitp->umutex));

	/*
	 * This initialization sequence allows a
	 * to change state to stop the fans from
	 * blastion upon poweron. If this isn't
	 * done the writes to the 8444 will not complete
	 * to the hardware because the dflop will
	 * be closed
	 */
	initval.chip_num = ENVCTRL_PCF8574_DEV0; /* 0x01 port 1 */
	initval.type = PCF8574A;

	initval.val = ENVCTRL_DEVINTR_INTI0;
	(void) envctrl_xmit(unitp, (caddr_t *)(void *)&initval, PCF8574);

	/*
	 * set lowerbits all high p0 = PS1, p1 = PS2
	 * p2 = PS3 p4 = envctrl intr_ctrl
	 */
	initval.val = ENVCTRL_DEVINTR_INTI1;
	(void) envctrl_xmit(unitp, (caddr_t *)(void *)&initval, PCF8574);
}

/* called with mutex held */
static void
envctrl_stop_clock(struct envctrlunit *unitp)
{
	int status;
	uint8_t buf[2];

	/*
	 * This routine talks to the PCF8583 which
	 * is a clock calendar chip on the envctrl bus.
	 * We use this chip as a watchdog timer for the
	 * fan control. At reset this chip pulses the interrupt
	 * line every 1 second. We need to be able to shut
	 * this off.
	 */

	ASSERT(MUTEX_HELD(&unitp->umutex));

	buf[0] = CLOCK_CSR_REG;
	buf[1] = CLOCK_DISABLE;

	status = eHc_write_pcf8583((struct eHc_envcunit *)unitp,
			PCF8583_BASE_ADDR | 0, buf, 2);
	if (status != DDI_SUCCESS)
		cmn_err(CE_WARN, "write to PCF8583 failed\n");
}

static void
envctrl_reset_watchdog(struct envctrlunit *unitp, uint8_t *wdval)
{

	uint8_t w, r;
	uint8_t res = 0;
	int status;
	uint8_t buf[3];

	ASSERT(MUTEX_HELD(&unitp->umutex));

	/* the clock MUST be stopped before we re-set it */
	envctrl_stop_clock(unitp);

	/*
	 * Reset the minutes counter to 0.
	 */
	buf[0] = ALARM_CTR_REG_MINS;
	buf[1] = 0x0;
	status = eHc_write_pcf8583((struct eHc_envcunit *)unitp,
			PCF8583_BASE_ADDR | 0, buf, 2);
	if (status != DDI_SUCCESS)
		cmn_err(CE_WARN, "write to PCF8583 failed\n");

	/*
	 * set up the alarm timer for 3 minutes
	 * start by setting reg 8 ALARM_CTRL_REG
	 * If we are in diag mode, we set the timer in
	 * seconds. Valid values are 40-99. The timer
	 * counts up to 99. 40 would be 59 seconds
	 */
	buf[0] = CLOCK_ALARM_REG_A;
	if (unitp->current_mode == ENVCTRL_DIAG_MODE) {
		if (unitp->timeout_id != 0) {
			(void) untimeout(unitp->timeout_id);
			unitp->timeout_id = 0;
			unitp->timeout_id = (timeout(envctrl_tempr_poll,
			    (caddr_t)unitp, overtemp_timeout_hz));
		}
		buf[1] = CLOCK_ENABLE_TIMER_S;
	} else {
		buf[1] = CLOCK_ENABLE_TIMER;
	}

	/* STEP 10: End Transmission */
	status = eHc_write_pcf8583((struct eHc_envcunit *)unitp,
			PCF8583_BASE_ADDR | 0, buf, 2);
	if (status != DDI_SUCCESS)
		cmn_err(CE_WARN, "Reset envctrl watchdog failed\n");

	/*
	 * Now set up the alarm timer register it
	 * counts from 0-99 with an intr triggered
	 * when it gets to overflow.. or 99. It will
	 * also count from a pre-set value which is
	 * where we are seting from. We want a 3 minute fail
	 * safe so our value is 99-3 or 96.
	 * we are programming register 7 in the 8583.
	 */

	buf[0] = ALARM_CTRL_REG;
	/*
	 * Allow the diagnostic to set the egg timer val.
	 * never allow it to be set greater than the default.
	 */
	if (unitp->current_mode == ENVCTRL_DIAG_MODE) {
		if (*wdval > MAX_CL_VAL) {
			buf[1] = EGG_TIMER_VAL;
		} else {

			w = *wdval/10;
			r = *wdval%10;

			res = res | r;
			res = (0x99 - (res | (w << 4)));
			buf[1] = res;
		}
	} else {
		buf[1] = EGG_TIMER_VAL;
	}

	status = eHc_write_pcf8583((struct eHc_envcunit *)unitp,
			PCF8583_BASE_ADDR | 0, buf, 2);
	if (status != DDI_SUCCESS)
		cmn_err(CE_WARN, "Reset envctrl watchdog failed\n");


	/*
	 * Now that we have set up.. it is time
	 * to re-start the clock in the CSR.
	 */

	buf[0] = CLOCK_CSR_REG;
	buf[1] = CLOCK_ENABLE;
	status = eHc_write_pcf8583((struct eHc_envcunit *)unitp,
			PCF8583_BASE_ADDR | 0, buf, 2);
	if (status != DDI_SUCCESS)
		cmn_err(CE_WARN, "Reset envctrl watchdog failed\n");

}

/* Called with unip mutex held */
static void
envctrl_ps_probe(struct envctrlunit *unitp)
{

	uint8_t recv_data, fpmstat;
	uint8_t psaddr[] = {PS1, PS2, PS3, PSTEMP0};
	int i;
	int ps_error = 0, retrys = 0;
	int devaddr;
	int status;
	int twotimes = 0;

	ASSERT(MUTEX_HELD(&unitp->umutex));

	unitp->num_ps_present = 0;

	for (i = 0; i <= MAXPS; i++) {
		unitp->ps_present[i] = B_FALSE;
		unitp->ps_kstats[i].ps_rating = 0;
		unitp->ps_kstats[i].ps_tempr = 0;

		switch (psaddr[i]) {
		case PS1:
			devaddr = ENVCTRL_PCF8574_DEV3;
			break;
		case PS2:
			devaddr = ENVCTRL_PCF8574_DEV2;
			break;
		case PS3:
			devaddr = ENVCTRL_PCF8574_DEV1;
			break;
		case PSTEMP0:
			devaddr = 0;
			break;
		}
		retrys = 0;
retry:
		status = eHc_read_pcf8574a((struct eHc_envcunit *)unitp,
				PCF8574A_BASE_ADDR | devaddr, &recv_data, 1);
		if (status != DDI_SUCCESS) {
			drv_usecwait(1000);
			if (retrys < envctrl_max_retries) {
				retrys++;
				goto retry;
			} else {
				mutex_exit(&unitp->umutex);
				envctrl_init_bus(unitp);
				mutex_enter(&unitp->umutex);
				/*
				 * If we just reset the bus we need to reread
				 * the status.  If a second attempt still fails
				 * then report the read failure.
				 */
				if (twotimes == 0) {
					twotimes++;
					retrys = 0;
					goto retry;
				} else {
					cmn_err(CE_WARN,
					"PS_probe: Read from 8574A failed\n");
				}
			}
		}

		/*
		 * Port 0 = PS Present
		 * Port 1 = PS Type
		 * Port 2 = PS Type
		 * Port 3 = PS TYpe
		 * Port 4 = DC Status
		 * Port 5 = Current Limit
		 * Port 6 = Current Share
		 * Port 7 = SPARE
		 */

		/*
		 * Port 0 = PS Present
		 * Port is pulled LOW "0" to indicate
		 * present.
		 */

		if (!(recv_data & ENVCTRL_PCF8574_PORT0)) {
			unitp->ps_present[i] = B_TRUE;
			/* update unit kstat array */
			unitp->ps_kstats[i].instance = i;
			unitp->ps_kstats[i].ps_tempr = ENVCTRL_INIT_TEMPR;
			++unitp->num_ps_present;

			if (power_supply_previous_state[i] == 0) {
				cmn_err(CE_NOTE,
				    "Power Supply %d inserted\n", i);
			}
			power_supply_previous_state[i] = 1;

			if (!(recv_data & ENVCTRL_PCF8574_PORT1)) {
				unitp->ps_kstats[i].ps_rating = ENVCTRL_PS_550;
			}
			if (!(recv_data & ENVCTRL_PCF8574_PORT2)) {
				unitp->ps_kstats[i].ps_rating = ENVCTRL_PS_650;
			}
			if (!(recv_data & ENVCTRL_PCF8574_PORT3)) {
				cmn_err(CE_WARN,
				    "Power Supply %d NOT okay\n", i);
				unitp->ps_kstats[i].ps_ok = B_FALSE;
				ps_error++;
			} else {
				unitp->ps_kstats[i].ps_ok = B_TRUE;
			}
			if (!(recv_data & ENVCTRL_PCF8574_PORT4)) {
				cmn_err(CE_WARN,
				    "Power Supply %d Overloaded\n", i);
				unitp->ps_kstats[i].limit_ok = B_FALSE;
				ps_error++;
			} else {
				unitp->ps_kstats[i].limit_ok = B_TRUE;
			}
			if (!(recv_data & ENVCTRL_PCF8574_PORT5)) {
				cmn_err(CE_WARN,
				    "Power Supply %d load share err\n", i);
				unitp->ps_kstats[i].curr_share_ok = B_FALSE;
				ps_error++;
			} else {
				unitp->ps_kstats[i].curr_share_ok = B_TRUE;
			}

			if (!(recv_data & ENVCTRL_PCF8574_PORT6)) {
				cmn_err(CE_WARN,
				    "PS %d Shouln't interrupt\n", i);
				ps_error++;
			}

			if (!(recv_data & ENVCTRL_PCF8574_PORT7)) {
				cmn_err(CE_WARN,
				    "PS %d Shouln't interrupt\n", i);
				ps_error++;
			}
		} else {
			/* No power supply present */
			if (power_supply_previous_state[i] == 1) {
				cmn_err(CE_NOTE,
				    "Power Supply %d removed\n", i);
			}
			power_supply_previous_state[i] = 0;
		}
	}

	fpmstat = envctrl_get_fpm_status(unitp);
	if (ps_error) {
		fpmstat |= (ENVCTRL_FSP_PS_ERR | ENVCTRL_FSP_GEN_ERR);
	} else {
		if (envctrl_isother_fault_led(unitp, fpmstat,
			ENVCTRL_FSP_PS_ERR)) {
			fpmstat &= ~(ENVCTRL_FSP_PS_ERR);
		} else {
			fpmstat &= ~(ENVCTRL_FSP_PS_ERR |
			    ENVCTRL_FSP_GEN_ERR);
		}

	}
	envctrl_set_fsp(unitp, &fpmstat);

	/*
	 * We need to reset all of the fans etc when a supply is
	 * interrupted and added, but we don't want to reset the
	 * fans if we are in DIAG mode. This will mess up SUNVTS.
	 */
	if (unitp->current_mode == ENVCTRL_NORMAL_MODE) {
		envctrl_get_sys_temperatures(unitp, (uint8_t *)NULL);
	}
}

/*
 * consider key switch position when handling an abort sequence
 */
static void
envctrl_abort_seq_handler(char *msg)
{
	struct envctrlunit *unitp;
	int i;
	uint8_t secure = 0;

	/*
	 * Find the instance of the device available on this host.
	 * Note that there may be only one, but the instance may
	 * not be zero.
	 */
	for (i = 0; i < MAX_DEVS; i++) {
		if (unitp = (struct envctrlunit *)
				ddi_get_soft_state(envctrlsoft_statep, i))
			break;
	}

	ASSERT(unitp);

	for (i = 0; i < MAX_DEVS; i++) {
		if ((unitp->encl_kstats[i].type == ENVCTRL_ENCL_FSP) &&
			(unitp->encl_kstats[i].instance != I2C_NODEV)) {
			secure = unitp->encl_kstats[i].value;
			break;
		}
	}

	/*
	 * take the logical not because we are in hardware mode only
	 */

	if ((secure & ENVCTRL_FSP_KEYMASK) == ENVCTRL_FSP_KEYLOCKED) {
			cmn_err(CE_CONT,
			    "!envctrl: ignoring debug enter sequence\n");
	} else {
		if (envctrl_debug_flags) {
			cmn_err(CE_CONT, "!envctrl: allowing debug enter\n");
		}
		debug_enter(msg);
	}
}

/*
 * get the front Panel module LED and keyswitch status.
 * this part is addressed at 0x7C on the i2c bus.
 * called with mutex held
 */
static uint8_t
envctrl_get_fpm_status(struct envctrlunit *unitp)
{
	uint8_t recv_data;
	int status, retrys = 0;

	ASSERT(MUTEX_HELD(&unitp->umutex));

retry:
	status = eHc_read_pcf8574a((struct eHc_envcunit *)unitp,
		PCF8574A_BASE_ADDR | ENVCTRL_PCF8574_DEV6, &recv_data, 1);

	/*
	 * yet another place where a read can cause the
	 * the SDA line of the i2c bus to get stuck low.
	 * this funky sequence frees the SDA line.
	 */
	if (status != DDI_SUCCESS) {
		drv_usecwait(1000);
		if (retrys < envctrl_max_retries) {
			retrys++;
			goto retry;
		} else {
			mutex_exit(&unitp->umutex);
			envctrl_init_bus(unitp);
			mutex_enter(&unitp->umutex);
			if (envctrl_debug_flags)
				cmn_err(CE_WARN, "Read from PCF8574 (FPM) "\
				    "failed\n");
		}
	}
	recv_data = ~recv_data;
	envctrl_mod_encl_kstats(unitp, ENVCTRL_ENCL_FSP,
	    INSTANCE_0, recv_data);

	return (recv_data);
}

static void
envctrl_set_fsp(struct envctrlunit *unitp, uint8_t *val)
{
	struct envctrl_pcf8574_chip chip;

	ASSERT(MUTEX_HELD(&unitp->umutex));

	chip.val = ENVCTRL_FSP_OFF; /* init all values to off */
	chip.chip_num = ENVCTRL_PCF8574_DEV6; /* 0x01 port 1 */
	chip.type = PCF8574A;

	/*
	 * strip off bits that are R/O
	 */
	chip.val = (~(ENVCTRL_FSP_KEYMASK | ENVCTRL_FSP_POMASK) & (*val));

	chip.val = ~chip.val;
	(void) envctrl_xmit(unitp, (caddr_t *)(void *)&chip, PCF8574);

}

static int
envctrl_get_dskled(struct envctrlunit *unitp, struct envctrl_pcf8574_chip *chip)
{
	uint_t oldtype;

	ASSERT(MUTEX_HELD(&unitp->umutex));

	if (chip->chip_num > ENVCTRL_PCF8574_DEV2 ||
		chip->type != ENVCTRL_ENCL_BACKPLANE4 &&
		chip->type != ENVCTRL_ENCL_BACKPLANE8) {
		return (DDI_FAILURE);
	}
	oldtype = chip->type;
	chip->type = PCF8574;
	envctrl_recv(unitp, (caddr_t *)(void *)chip, PCF8574);
	chip->type = oldtype;
	chip->val = ~chip->val;

	return (DDI_SUCCESS);
}
static int
envctrl_set_dskled(struct envctrlunit *unitp, struct envctrl_pcf8574_chip *chip)
{

	struct envctrl_pcf8574_chip fspchip;
	struct envctrl_pcf8574_chip backchip;
	int i, instance;
	int diskfault = 0;
	uint8_t controller_addr[] = {ENVCTRL_PCF8574_DEV0, ENVCTRL_PCF8574_DEV1,
	    ENVCTRL_PCF8574_DEV2};

	/*
	 * We need to check the type of disk led being set. If it
	 * is a 4 slot backplane then the upper 4 bits (7, 6, 5, 4) are
	 * invalid.
	 */
	ASSERT(MUTEX_HELD(&unitp->umutex));


	if (chip->chip_num > ENVCTRL_PCF8574_DEV2 ||
		chip->val > ENVCTRL_DISK8LED_ALLOFF ||
		chip->val < ENVCTRL_CHAR_ZERO) {
		return (DDI_FAILURE);
	}

	if (chip->type != ENVCTRL_ENCL_BACKPLANE4 &&
	    chip->type != ENVCTRL_ENCL_BACKPLANE8) {
		return (DDI_FAILURE);
	}

	/*
	 * Check all of the other controllwes LED states to make sure
	 * that there are no disk faults. If so then if the user is
	 * clearing the disk faults on this contoller, turn off
	 * the mass storage fault led.
	 */

	backchip.type = PCF8574;
	for (i = 0; i <= MAX_TAZ_CONTROLLERS; i++) {
		if (controller_present[i] == -1)
			continue;
		backchip.chip_num = controller_addr[i];
		envctrl_recv(unitp, (caddr_t *)(void *)&backchip, PCF8574);
		if (chip->chip_num == controller_addr[i]) {
			if (chip->val != ENVCTRL_CHAR_ZERO)
				diskfault++;
		} else if ((~backchip.val & 0xFF) != ENVCTRL_CHAR_ZERO) {
			diskfault++;
		}
	}

	fspchip.type = PCF8574A;
	fspchip.chip_num = ENVCTRL_PCF8574_DEV6; /* 0x01 port 1 */
	envctrl_recv(unitp, (caddr_t *)(void *)&fspchip, PCF8574);

	if (diskfault) {
		if (!(envctrl_isother_fault_led(unitp, fspchip.val & 0xFF,
			ENVCTRL_FSP_DISK_ERR))) {
			fspchip.val &= ~(ENVCTRL_FSP_DISK_ERR);
		} else {
			fspchip.val &= ~(ENVCTRL_FSP_DISK_ERR |
			    ENVCTRL_FSP_GEN_ERR);
		}
		fspchip.val = (fspchip.val &
		    ~(ENVCTRL_FSP_DISK_ERR | ENVCTRL_FSP_GEN_ERR));
	} else {
		fspchip.val = (fspchip.val |
		    (ENVCTRL_FSP_DISK_ERR | ENVCTRL_FSP_GEN_ERR));
	}
	fspchip.type = PCF8574A;
	fspchip.chip_num = ENVCTRL_PCF8574_DEV6; /* 0x01 port 1 */
	(void) envctrl_xmit(unitp, (caddr_t *)(void *)&fspchip, PCF8574);

	for (i = 0; i < (sizeof (backaddrs) / sizeof (uint8_t)); i++) {
		if (chip->chip_num == backaddrs[i]) {
			instance =  i;
		}
	}

	switch (chip->type) {
	case ENVCTRL_ENCL_BACKPLANE4:
		envctrl_mod_encl_kstats(unitp, ENVCTRL_ENCL_BACKPLANE4,
		    instance, chip->val);
		break;
	case ENVCTRL_ENCL_BACKPLANE8:
		envctrl_mod_encl_kstats(unitp, ENVCTRL_ENCL_BACKPLANE8,
		    instance, chip->val);
		break;
	default:
		break;
	}
	chip->type = PCF8574;
	/*
	 * we take the ones compliment of the val passed in
	 * because the hardware thinks that a "low" or "0"
	 * is the way to indicate a fault. of course software
	 * knows that a 1 is a TRUE state or fault. ;-)
	 */
	chip->val = ~(chip->val);
	(void) envctrl_xmit(unitp, (caddr_t *)(void *)chip, PCF8574);
	return (DDI_SUCCESS);
}

void
envctrl_add_kstats(struct envctrlunit *unitp)
{

	ASSERT(MUTEX_HELD(&unitp->umutex));

	if ((unitp->enclksp = kstat_create(ENVCTRL_MODULE_NAME, unitp->instance,
	    ENVCTRL_KSTAT_ENCL, "misc", KSTAT_TYPE_RAW,
	    sizeof (unitp->encl_kstats),
	    KSTAT_FLAG_PERSISTENT)) == NULL) {
		cmn_err(CE_WARN, "envctrl%d: encl raw kstat_create failed",
			unitp->instance);
		return;
	}

	unitp->enclksp->ks_update = envctrl_encl_kstat_update;
	unitp->enclksp->ks_private = (void *)unitp;
	kstat_install(unitp->enclksp);


	if ((unitp->fanksp = kstat_create(ENVCTRL_MODULE_NAME, unitp->instance,
	    ENVCTRL_KSTAT_FANSTAT, "misc", KSTAT_TYPE_RAW,
	    sizeof (unitp->fan_kstats),
	    KSTAT_FLAG_PERSISTENT)) == NULL) {
		cmn_err(CE_WARN, "envctrl%d: fans kstat_create failed",
			unitp->instance);
		return;
	}

	unitp->fanksp->ks_update = envctrl_fanstat_kstat_update;
	unitp->fanksp->ks_private = (void *)unitp;
	kstat_install(unitp->fanksp);

	if ((unitp->psksp = kstat_create(ENVCTRL_MODULE_NAME, unitp->instance,
	    ENVCTRL_KSTAT_PSNAME, "misc", KSTAT_TYPE_RAW,
	    sizeof (unitp->ps_kstats),
	    KSTAT_FLAG_PERSISTENT)) == NULL) {
		cmn_err(CE_WARN, "envctrl%d: ps name kstat_create failed",
			unitp->instance);
		return;
	}

	unitp->psksp->ks_update = envctrl_ps_kstat_update;
	unitp->psksp->ks_private = (void *)unitp;
	kstat_install(unitp->psksp);

}

int
envctrl_ps_kstat_update(kstat_t *ksp, int rw)
{
	struct envctrlunit *unitp;
	char *kstatp;



	unitp = (struct envctrlunit *)ksp->ks_private;

	mutex_enter(&unitp->umutex);
	ASSERT(MUTEX_HELD(&unitp->umutex));

	kstatp = (char *)ksp->ks_data;

	if (rw == KSTAT_WRITE) {
		return (EACCES);
	} else {

		unitp->psksp->ks_ndata = unitp->num_ps_present;
		bcopy(&unitp->ps_kstats, kstatp, sizeof (unitp->ps_kstats));
	}
	mutex_exit(&unitp->umutex);
	return (DDI_SUCCESS);
}
int
envctrl_fanstat_kstat_update(kstat_t *ksp, int rw)
{
	struct envctrlunit *unitp;
	char *kstatp;

	kstatp = (char *)ksp->ks_data;
	unitp = (struct envctrlunit *)ksp->ks_private;

	mutex_enter(&unitp->umutex);
	ASSERT(MUTEX_HELD(&unitp->umutex));

	if (rw == KSTAT_WRITE) {
		return (EACCES);
	} else {
		unitp->fanksp->ks_ndata = unitp->num_fans_present;
		bcopy(unitp->fan_kstats, kstatp, sizeof (unitp->fan_kstats));
	}
	mutex_exit(&unitp->umutex);
	return (DDI_SUCCESS);
}

int
envctrl_encl_kstat_update(kstat_t *ksp, int rw)
{
	struct envctrlunit *unitp;
	char *kstatp;


	kstatp = (char *)ksp->ks_data;
	unitp = (struct envctrlunit *)ksp->ks_private;

	mutex_enter(&unitp->umutex);
	ASSERT(MUTEX_HELD(&unitp->umutex));

	if (rw == KSTAT_WRITE) {
		return (EACCES);
	} else {

		unitp->enclksp->ks_ndata = unitp->num_encl_present;
		(void) envctrl_get_fpm_status(unitp);
		/* XXX Need to ad disk updates too ??? */
		bcopy(unitp->encl_kstats, kstatp, sizeof (unitp->encl_kstats));
	}
	mutex_exit(&unitp->umutex);
	return (DDI_SUCCESS);
}

/*
 * called with unitp lock held
 * type, fanspeed and fanflt will be set by the service routines
 */
static void
envctrl_init_fan_kstats(struct envctrlunit *unitp)
{
	int i;

	ASSERT(MUTEX_HELD(&unitp->umutex));

	for (i = 0; i < unitp->num_fans_present; i++) {
		unitp->fan_kstats[i].instance = 0;
		unitp->fan_kstats[i].type = 0;
		unitp->fan_kstats[i].fans_ok = B_TRUE;
		unitp->fan_kstats[i].fanflt_num = B_FALSE;
		unitp->fan_kstats[i].fanspeed = B_FALSE;
	}

	unitp->fan_kstats[ENVCTRL_FAN_TYPE_PS].type = ENVCTRL_FAN_TYPE_PS;
	unitp->fan_kstats[ENVCTRL_FAN_TYPE_CPU].type = ENVCTRL_FAN_TYPE_CPU;
	if (unitp->AFB_present == B_TRUE)
		unitp->fan_kstats[ENVCTRL_FAN_TYPE_AFB].type =
			ENVCTRL_FAN_TYPE_AFB;
}

static void
envctrl_init_encl_kstats(struct envctrlunit *unitp)
{

	int i;
	uint8_t val;
	struct envctrl_pcf8574_chip chip;
	int *reg_prop;
	uint_t len = 0;

	ASSERT(MUTEX_HELD(&unitp->umutex));

	for (i = 0; i < MAX_DEVS; i++) {
		unitp->encl_kstats[i].instance = I2C_NODEV;
	}

	/*
	 * add in kstats now
	 * We ALWAYS HAVE THE FOLLOWING
	 * 1. FSP
	 * 2. AMB TEMPR
	 * 3. (1) CPU TEMPR
	 * 4. (1) 4 slot disk backplane
	 * OPTIONAL
	 * 8 slot backplane
	 * more cpu's
	 */

	chip.type = PCF8574A;
	chip.chip_num = ENVCTRL_PCF8574_DEV6; /* 0x01 port 1 */
	envctrl_recv(unitp, (caddr_t *)(void *)&chip, PCF8574);

	envctrl_add_encl_kstats(unitp, ENVCTRL_ENCL_FSP, INSTANCE_0,
		chip.val & 0xFF);

	val = envctrl_get_lm75_temp(unitp) & 0xFF;
	envctrl_add_encl_kstats(unitp, ENVCTRL_ENCL_AMBTEMPR, INSTANCE_0, val);

	if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, unitp->dip,
			DDI_PROP_DONTPASS, ENVCTRL_DISK_LEDS_PR,
			&reg_prop, &len) != DDI_PROP_SUCCESS) {
		cmn_err(CE_WARN, "prop lookup of %s failed\n",
			ENVCTRL_DISK_LEDS_PR);
		return;
	}

	ASSERT(len != 0);

	chip.type = PCF8574;

	for (i = 0; i < len; i++) {
		chip.chip_num = backaddrs[i];
		if (reg_prop[i] == ENVCTRL_4SLOT_BACKPLANE) {
			envctrl_recv(unitp, (caddr_t *)(void *)&chip, PCF8574);
			envctrl_add_encl_kstats(unitp, ENVCTRL_ENCL_BACKPLANE4,
			    i, ~chip.val);
			controller_present[i] = 1;
		}
		if (reg_prop[i] == ENVCTRL_8SLOT_BACKPLANE) {
			envctrl_recv(unitp, (caddr_t *)(void *)&chip, PCF8574);
			envctrl_add_encl_kstats(unitp, ENVCTRL_ENCL_BACKPLANE8,
			    i, ~chip.val);
			controller_present[i] = 1;
		}
	}
	ddi_prop_free((void *)reg_prop);

}

static void
envctrl_mod_encl_kstats(struct envctrlunit *unitp, int type,
    int instance, uint8_t val)
{
	int i = 0;
	boolean_t inserted = B_FALSE;

	ASSERT(MUTEX_HELD(&unitp->umutex));

	while (i < MAX_DEVS && inserted == B_FALSE) {
		if (unitp->encl_kstats[i].instance == instance &&
		    unitp->encl_kstats[i].type == type) {
			unitp->encl_kstats[i].value = val;
			inserted = B_TRUE;
		}
		i++;
	}
}

static void
envctrl_probe_cpus(struct envctrlunit *unitp)
{
	int instance;

	/*
	 * The cpu search is as follows:
	 * If there is only 1 CPU module it is named as
	 * SUNW,UltraSPARC. If this is a match we still don't
	 * know what slot the cpu module is in therefore
	 * we need to check the "upa-portid" property.
	 * If we have more than 1 cpu, then they are appended by
	 * instance numbers and slot locations. e.g.
	 * SUNW,UltraSPARC@1,0 (slot 1). it would have been
	 * nice to have the naming consistent for one CPU e.g.
	 * SUNW,UltraSPARC@0,0...sigh
	 */

	for (instance = 0; instance < ENVCTRL_MAX_CPUS; instance++) {
		unitp->cpu_pr_location[instance] = B_FALSE;
	}

	ddi_walk_devs(ddi_root_node(), envctrl_match_cpu, unitp);
}

static int
envctrl_match_cpu(dev_info_t *dip, void *arg)
{

	int cpu_slot;
	char name[32];
	char name1[32];
	struct envctrlunit *unitp = (struct envctrlunit *)arg;

	(void) sprintf(name, "%s", ENVCTRL_TAZCPU_STRING);
	(void) sprintf(name1, "%s", ENVCTRL_TAZBLKBRDCPU_STRING);

	if ((strcmp(ddi_node_name(dip), name) == 0) ||
		(strcmp(ddi_node_name(dip), name1) == 0)) {
		if ((cpu_slot = (int)ddi_getprop(DDI_DEV_T_ANY, dip,
			    DDI_PROP_DONTPASS, "upa-portid",
				    -1)) == -1) {
			cmn_err(CE_WARN, "envctrl no cpu upa-portid");
		} else {
			unitp->cpu_pr_location[cpu_slot] = B_TRUE;
			unitp->num_cpus_present++;
		}
	}

	return (DDI_WALK_CONTINUE);
}

/*
 * This routine returns TRUE if some other error condition
 * has set the GEN_ERR FAULT LED. Tp further complicate this
 * LED panel we have overloaded the GEN_ERR LED to indicate
 * that a fan fault has occurred without having a fan fault
 * LED as does all other error conditions. So we just take the
 * software state and return true. The whole purpose of this functon
 * is to tell us wehther or not we can shut off the GEN_FAULT LED.
 * NOTE: this ledval is usually one of the following FSP vals
 * EXCEPT in the case of the fan fail.. we pass in a "0".
 */

static int
envctrl_isother_fault_led(struct envctrlunit *unitp, uint8_t fspval,
    uint8_t thisled)
{
	int status = B_FALSE;

	if (fspval != 0) {
		fspval = (fspval & ~(thisled));
	}
	if (unitp->num_fans_failed > 0 && thisled != 0) {
		status = B_TRUE;
	} else if (fspval & ENVCTRL_FSP_DISK_ERR) {
		status = B_TRUE;
	} else if (fspval & ENVCTRL_FSP_PS_ERR) {
		status = B_TRUE;
	} else if (fspval & ENVCTRL_FSP_TEMP_ERR) {
		status = B_TRUE;
	}
	return (status);
}

static void
envctrl_pshotplug_poll(void *arg)
{
	struct envctrlunit *unitp = (struct envctrlunit *)arg;

	mutex_enter(&unitp->umutex);

	envctrl_ps_probe(unitp);

	mutex_exit(&unitp->umutex);
}

/*
 * The following routines implement the i2c protocol.
 * They should be removed once the envctrl_targets.c file is included.
 */

/*
 * put host interface into master mode
 */
static int
eHc_start_pcf8584(struct eHc_envcunit *ehcp, uint8_t byteaddress)
{
	uint8_t poll_status;
	uint8_t discard;
	int i;

	/* wait if bus is busy */

	i = 0;
	do {
		drv_usecwait(1000);
		poll_status =
			ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1);
		i++;
	} while (((poll_status & EHC_S1_NBB) == 0) && i < EHC_MAX_WAIT);

	if (i == EHC_MAX_WAIT) {
		DCMNERR(CE_WARN, "eHc_start_pcf8584: I2C bus busy");
		return (EHC_FAILURE);
	}

	if (poll_status & EHC_S1_BER) {
		DCMN2ERR(CE_WARN, "eHc_start_pcf8584: I2C bus error");
		return (EHC_FAILURE);
	}

	if (poll_status & EHC_S1_LAB) {
		DCMN2ERR(CE_WARN, "eHc_start_pcf8584: Lost arbitration");
		return (EHC_FAILURE);
	}

	/* load the slave address */
	ddi_put8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0, byteaddress);

	/* generate the "start condition" and clock out the slave address */
	ddi_put8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1,
		EHC_S1_PIN | EHC_S1_ES0 | EHC_S1_STA | EHC_S1_ACK);

	/* wait for completion of transmission */
	i = 0;
	do {
		drv_usecwait(1000);
		poll_status =
			ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1);
		i++;
	} while ((poll_status & EHC_S1_PIN) && i < EHC_MAX_WAIT);

	if (i == EHC_MAX_WAIT) {
		DCMNERR(CE_WARN, "eHc_start_pcf8584: I2C bus busy");
		return (EHC_FAILURE);
	}

	if (poll_status & EHC_S1_BER) {
		DCMN2ERR(CE_WARN, "eHc_start_pcf8584: I2C bus error");
		return (EHC_FAILURE);
	}

	if (poll_status & EHC_S1_LAB) {
		DCMN2ERR(CE_WARN, "eHc_start_pcf8584: Lost arbitration");
		return (EHC_FAILURE);
	}

	if (poll_status & EHC_S1_LRB) {
		DCMNERR(CE_WARN, "eHc_start_pcf8584: No slave ACK");
		return (EHC_NO_SLAVE_ACK);
	}

	/*
	 * If this is a read we are setting up for (as indicated by
	 * the least significant byte being set), read
	 * and discard the first byte off the bus - this
	 * is the slave address.
	 */

	i = 0;
	if (byteaddress & EHC_BYTE_READ) {
		discard = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0);
#ifdef lint
		discard = discard;
#endif

		/* wait for completion of transmission */
		do {
			drv_usecwait(1000);
			poll_status =
			ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1);
			i++;
		} while ((poll_status & EHC_S1_PIN) && i < EHC_MAX_WAIT);

		if (i == EHC_MAX_WAIT) {
			DCMNERR(CE_WARN, "eHc_start_pcf8584: I2C bus busy");
			return (EHC_FAILURE);
		}

		if (poll_status & EHC_S1_BER) {
			DCMN2ERR(CE_WARN,
				"eHc_start_pcf8584: I2C bus error");
			return (EHC_FAILURE);
		}

		if (poll_status & EHC_S1_LAB) {
			DCMN2ERR(CE_WARN,
				"eHc_start_pcf8584: Lost arbitration");
			return (EHC_FAILURE);
		}
	}

	return (EHC_SUCCESS);
}

/*
 * put host interface into slave/receiver mode
 */
static void
eHc_stop_pcf8584(struct eHc_envcunit *ehcp)
{
	ddi_put8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1,
		EHC_S1_PIN | EHC_S1_ES0 | EHC_S1_STO | EHC_S1_ACK);
}

static int
eHc_read_pcf8584(struct eHc_envcunit *ehcp, uint8_t *data)
{
	uint8_t poll_status;
	int i = 0;

	/* Read the byte of interest */
	*data = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0);

	/* wait for completion of transmission */
	do {
		drv_usecwait(1000);
		poll_status =
			ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1);
		i++;
	} while ((poll_status & EHC_S1_PIN) && i < EHC_MAX_WAIT);

	if (i == EHC_MAX_WAIT) {
		DCMNERR(CE_WARN, "eHc_read_pcf8584: I2C bus busy");
		return (EHC_FAILURE);
	}

	if (poll_status & EHC_S1_BER) {
		DCMN2ERR(CE_WARN, "eHc_read_pcf8584: I2C bus error");
		return (EHC_FAILURE);
	}

	if (poll_status & EHC_S1_LAB) {
		DCMN2ERR(CE_WARN, "eHc_read_pcf8584: Lost arbitration");
		return (EHC_FAILURE);
	}

	return (EHC_SUCCESS);
}

/*
 * host interface is in transmitter state, thus mode is master/transmitter
 * NOTE to Bill: this check the LRB bit (only done in transmit mode).
 */

static int
eHc_write_pcf8584(struct eHc_envcunit *ehcp, uint8_t data)
{
	uint8_t poll_status;
	int i = 0;

	/* send the data, EHC_S1_PIN should go to "1" immediately */
	ddi_put8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0, data);

	/* wait for completion of transmission */
	do {
		drv_usecwait(1000);
		poll_status =
			ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1);
		i++;
	} while ((poll_status & EHC_S1_PIN) && i < EHC_MAX_WAIT);

	if (i == EHC_MAX_WAIT) {
		DCMNERR(CE_WARN, "eHc_write_pcf8584: I2C bus busy");
		return (EHC_FAILURE);
	}

	if (poll_status & EHC_S1_BER) {
		DCMN2ERR(CE_WARN, "eHc_write_pcf8584: I2C bus error");
		return (EHC_FAILURE);
	}

	if (poll_status & EHC_S1_LAB) {
		DCMN2ERR(CE_WARN, "eHc_write_pcf8584: Lost arbitration");
		return (EHC_FAILURE);
	}

	if (poll_status & EHC_S1_LRB) {
		DCMNERR(CE_WARN, "eHc_write_pcf8584: No slave ACK");
		return (EHC_NO_SLAVE_ACK);
	}

	return (EHC_SUCCESS);
}

static int
eHc_after_read_pcf8584(struct eHc_envcunit *ehcp, uint8_t *data)
{
	uint8_t discard;
	uint8_t poll_status;
	int i = 0;

	/* set ACK in register S1 to 0 */
	ddi_put8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1, EHC_S1_ES0);

	/*
	 * Read the "byte-before-the-last-byte" - sets PIN bit to '1'
	 */

	*data = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0);

	/* wait for completion of transmission */
	do {
		drv_usecwait(1000);
		poll_status =
			ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1);
		i++;
	} while ((poll_status & EHC_S1_PIN) && i < EHC_MAX_WAIT);

	if (i == EHC_MAX_WAIT) {
		DCMNERR(CE_WARN, "eHc_after_read_pcf8584: I2C bus busy");
		return (EHC_FAILURE);
	}

	if (poll_status & EHC_S1_BER) {
		DCMN2ERR(CE_WARN,
			"eHc_after_read_pcf8584: I2C bus error");
		return (EHC_FAILURE);
	}

	if (poll_status & EHC_S1_LAB) {
		DCMN2ERR(CE_WARN, "eHc_after_read_pcf8584: Lost arbitration");
		return (EHC_FAILURE);
	}

	/*
	 * Generate the "stop" condition.
	 */
	eHc_stop_pcf8584(ehcp);

	/*
	 * Read the "last" byte.
	 */
	discard = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0);
#ifdef lint
	discard = discard;
#endif

	return (EHC_SUCCESS);
}

/*
 * Write to the TDA8444 chip.
 * byteaddress = chip type base address | chip offset address.
 */
static int
eHc_write_tda8444(struct eHc_envcunit *ehcp, int byteaddress, int instruction,
	int subaddress, uint8_t *buf, int size)
{
	uint8_t control;
	int i, status;

	ASSERT((byteaddress & 0x1) == 0);
	ASSERT(subaddress < 8);
	ASSERT(instruction == 0xf || instruction == 0x0);
	ASSERT(MUTEX_HELD(&ehcp->umutex));

	control = (instruction << 4) | subaddress;

	if ((status = eHc_start_pcf8584(ehcp, byteaddress)) != EHC_SUCCESS) {
		if (status == EHC_NO_SLAVE_ACK) {
			/*
			 * Send the "stop" condition.
			 */
			eHc_stop_pcf8584(ehcp);
		}
		return (EHC_FAILURE);
	}

	if ((status = eHc_write_pcf8584(ehcp, control)) != EHC_SUCCESS) {
		if (status == EHC_NO_SLAVE_ACK) {
		/*
		 * Send the "stop" condition.
		 */
		eHc_stop_pcf8584(ehcp);
		}
		return (EHC_FAILURE);
	}

	for (i = 0; i < size; i++) {
		if ((status = eHc_write_pcf8584(ehcp, (buf[i] & 0x3f))) !=
			EHC_SUCCESS) {
			if (status == EHC_NO_SLAVE_ACK)
				eHc_stop_pcf8584(ehcp);
			return (EHC_FAILURE);
		}
	}

	eHc_stop_pcf8584(ehcp);

	return (EHC_SUCCESS);
}

/*
 * Read from PCF8574A chip.
 * byteaddress = chip type base address | chip offset address.
 */
static int
eHc_read_pcf8574a(struct eHc_envcunit *ehcp, int byteaddress, uint8_t *buf,
	int size)
{
	int i;
	int status;
	uint8_t discard;

	ASSERT((byteaddress & 0x1) == 0);
	ASSERT(MUTEX_HELD(&ehcp->umutex));

	/*
	 * Put the bus into the start condition
	 */
	if ((status = eHc_start_pcf8584(ehcp, EHC_BYTE_READ | byteaddress)) !=
			EHC_SUCCESS) {
		if (status == EHC_NO_SLAVE_ACK) {
			/*
			 * Send the "stop" condition.
			 */
			eHc_stop_pcf8584(ehcp);
			/*
			 * Read the last byte - discard it.
			 */
			discard =
			ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0);
#ifdef lint
			discard = discard;
#endif
		}
		return (EHC_FAILURE);
	}

	for (i = 0; i < size - 1; i++) {
		if ((status = eHc_read_pcf8584(ehcp, &buf[i])) != EHC_SUCCESS) {
			return (EHC_FAILURE);
		}
	}

	/*
	 * Handle the part of the bus protocol which comes
	 * after a read, including reading the last byte.
	 */

	if (eHc_after_read_pcf8584(ehcp, &buf[i]) != EHC_SUCCESS) {
		return (EHC_FAILURE);
	}

	return (EHC_SUCCESS);
}

/*
 * Write to the PCF8574A chip.
 * byteaddress = chip type base address | chip offset address.
 */
static int
eHc_write_pcf8574a(struct eHc_envcunit *ehcp, int byteaddress, uint8_t *buf,
	int size)
{
	int i;
	int status;

	ASSERT((byteaddress & 0x1) == 0);
	ASSERT(MUTEX_HELD(&ehcp->umutex));

	/*
	 * Put the bus into the start condition (write)
	 */
	if ((status = eHc_start_pcf8584(ehcp, byteaddress)) != EHC_SUCCESS) {
		if (status == EHC_NO_SLAVE_ACK) {
			/*
			 * Send the "stop" condition.
			 */
			eHc_stop_pcf8584(ehcp);
		}
		return (EHC_FAILURE);
	}

	/*
	 * Send the data - poll as needed.
	 */
	for (i = 0; i < size; i++) {
		if ((status = eHc_write_pcf8584(ehcp, buf[i])) != EHC_SUCCESS) {
			if (status == EHC_NO_SLAVE_ACK)
				eHc_stop_pcf8584(ehcp);
			return (EHC_FAILURE);
		}
	}

	/*
	 * Transmission complete - generate stop condition and
	 * put device back into slave receiver mode.
	 */
	eHc_stop_pcf8584(ehcp);

	return (EHC_SUCCESS);
}

/*
 * Read from the PCF8574 chip.
 * byteaddress = chip type base address | chip offset address.
 */
static int
eHc_read_pcf8574(struct eHc_envcunit *ehcp, int byteaddress, uint8_t *buf,
	int size)
{
	int i;
	int status;
	uint8_t discard;

	ASSERT((byteaddress & 0x1) == 0);
	ASSERT(MUTEX_HELD(&ehcp->umutex));

	/*
	 * Put the bus into the start condition
	 */
	if ((status = eHc_start_pcf8584(ehcp, EHC_BYTE_READ | byteaddress)) !=
			EHC_SUCCESS) {
		if (status == EHC_NO_SLAVE_ACK) {
			/*
			 * Send the "stop" condition.
			 */
			eHc_stop_pcf8584(ehcp);
			/*
			 * Read the last byte - discard it.
			 */
			discard =
			ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0);
#ifdef lint
			discard = discard;
#endif
		}
		return (EHC_FAILURE);
	}

	for (i = 0; i < size - 1; i++) {
		if ((status = eHc_read_pcf8584(ehcp, &buf[i])) != EHC_SUCCESS) {
		return (EHC_FAILURE);
		}
	}

	/*
	 * Handle the part of the bus protocol which comes
	 * after a read.
	 */

	if (eHc_after_read_pcf8584(ehcp, &buf[i]) != EHC_SUCCESS) {
		return (EHC_FAILURE);
	}

	return (EHC_SUCCESS);
}

/*
 * Write to the PCF8574 chip.
 * byteaddress = chip type base address | chip offset address.
 */
static int
eHc_write_pcf8574(struct eHc_envcunit *ehcp, int byteaddress, uint8_t *buf,
	int size)
{
	int i;
	int status;

	ASSERT((byteaddress & 0x1) == 0);
	ASSERT(MUTEX_HELD(&ehcp->umutex));

	/*
	 * Put the bus into the start condition (write)
	 */
	if ((status = eHc_start_pcf8584(ehcp, byteaddress)) != EHC_SUCCESS) {
		if (status == EHC_NO_SLAVE_ACK) {
			/*
			 * Send the "stop" condition.
			 */
			eHc_stop_pcf8584(ehcp);
		}
		return (EHC_FAILURE);
	}

	/*
	 * Send the data - poll as needed.
	 */
	for (i = 0; i < size; i++) {
		if ((status = eHc_write_pcf8584(ehcp, buf[i])) != EHC_SUCCESS) {
			if (status == EHC_NO_SLAVE_ACK)
				eHc_stop_pcf8584(ehcp);
			return (EHC_FAILURE);
		}
	}
	/*
	 * Transmission complete - generate stop condition and
	 * put device back into slave receiver mode.
	 */
	eHc_stop_pcf8584(ehcp);

	return (EHC_SUCCESS);
}

/*
 * Read from the LM75
 * byteaddress = chip type base address | chip offset address.
 */
static int
eHc_read_lm75(struct eHc_envcunit *ehcp, int byteaddress, uint8_t *buf,
	int size)
{
	int i;
	int status;
	uint8_t discard;

	ASSERT((byteaddress & 0x1) == 0);
	ASSERT(MUTEX_HELD(&ehcp->umutex));

	/*
	 * Put the bus into the start condition
	 */
	if ((status = eHc_start_pcf8584(ehcp, EHC_BYTE_READ | byteaddress)) !=
			EHC_SUCCESS) {
		if (status == EHC_NO_SLAVE_ACK) {
			/*
			 * Send the stop condition.
			 */
			eHc_stop_pcf8584(ehcp);
			/*
			 * Read the last byte - discard it.
			 */
			discard =
			ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0);
#ifdef lint
			discard = discard;
#endif
		}
		return (EHC_FAILURE);
	}

	for (i = 0; i < size - 1; i++) {
		if ((status = eHc_read_pcf8584(ehcp, &buf[i])) != EHC_SUCCESS) {
		return (EHC_FAILURE);
		}
	}

	/*
	 * Handle the part of the bus protocol which comes
	 * after a read.
	 */
	if (eHc_after_read_pcf8584(ehcp, &buf[i]) != EHC_SUCCESS) {
		return (EHC_FAILURE);
	}

	return (EHC_SUCCESS);
}

/*
 * Write to the PCF8583 chip.
 * byteaddress = chip type base address | chip offset address.
 */
static int
eHc_write_pcf8583(struct eHc_envcunit *ehcp, int byteaddress, uint8_t *buf,
	int size)
{
	int i;
	int status;

	ASSERT((byteaddress & 0x1) == 0);
	ASSERT(MUTEX_HELD(&ehcp->umutex));

	if ((status = eHc_start_pcf8584(ehcp, byteaddress)) != EHC_SUCCESS) {
		if (status == EHC_NO_SLAVE_ACK) {
			/*
			 * Send the "stop" condition.
			 */
			eHc_stop_pcf8584(ehcp);
		}
		return (EHC_FAILURE);
	}

	/*
	 * Send the data - poll as needed.
	 */
	for (i = 0; i < size; i++) {
		if ((status = eHc_write_pcf8584(ehcp, buf[i])) != EHC_SUCCESS) {
			if (status == EHC_NO_SLAVE_ACK)
				eHc_stop_pcf8584(ehcp);
			return (EHC_FAILURE);
		}
	}

	/*
	 * Transmission complete - generate stop condition and
	 * put device back into slave receiver mode.
	 */
	eHc_stop_pcf8584(ehcp);

	return (EHC_SUCCESS);
}

/*
 * Read from the PCF8581 chip.
 * byteaddress = chip type base address | chip offset address.
 */
static int
eHc_read_pcf8591(struct eHc_envcunit *ehcp, int byteaddress, int channel,
	int autoinc, int amode, int aenable, uint8_t *buf, int size)
{
	int i;
	int status;
	uint8_t control;
	uint8_t discard;

	ASSERT((byteaddress & 0x1) == 0);
	ASSERT(channel < 4);
	ASSERT(amode < 4);
	ASSERT(MUTEX_HELD(&ehcp->umutex));

	/*
	 * Write the control word to the PCF8591.
	 * Follow the control word with a repeated START byte
	 * rather than a STOP so that reads can follow without giving
	 * up the bus.
	 */

	control = ((aenable << 6) | (amode << 4) | (autoinc << 2) | channel);

	if ((status = eHc_start_pcf8584(ehcp, byteaddress)) != EHC_SUCCESS) {
		if (status == EHC_NO_SLAVE_ACK) {
			eHc_stop_pcf8584(ehcp);
		}
		return (EHC_FAILURE);
	}

	if ((status = eHc_write_pcf8584(ehcp, control)) != EHC_SUCCESS) {
		if (status == EHC_NO_SLAVE_ACK)
			eHc_stop_pcf8584(ehcp);
		return (EHC_FAILURE);
	}

	/*
	 * The following two operations, 0x45 to S1, and the byteaddress
	 * to S0, will result in a repeated START being sent out on the bus.
	 * Refer to Fig.8 of Philips Semiconductors PCF8584 product spec.
	 */

	ddi_put8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1,
		EHC_S1_ES0 | EHC_S1_STA | EHC_S1_ACK);

	ddi_put8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0,
		EHC_BYTE_READ | byteaddress);

	i = 0;

	do {
		drv_usecwait(1000);
		status =
			ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1);
		i++;
	} while ((status & EHC_S1_PIN) && i < EHC_MAX_WAIT);

	if (i == EHC_MAX_WAIT) {
		DCMNERR(CE_WARN, "eHc_read_pcf8591(): read of S1 failed");
		return (EHC_FAILURE);
	}

	if (status & EHC_S1_LRB) {
		DCMNERR(CE_WARN, "eHc_read_pcf8591(): No slave ACK");
		/*
		 * Send the stop condition.
		 */
		eHc_stop_pcf8584(ehcp);
		/*
		 * Read the last byte - discard it.
		 */
		discard = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0);
#ifdef lint
		discard = discard;
#endif
		return (EHC_FAILURE);
	}

	if (status & EHC_S1_BER) {
		DCMN2ERR(CE_WARN, "eHc_read_pcf8591(): Bus error");
		return (EHC_FAILURE);
	}

	if (status & EHC_S1_LAB) {
		DCMN2ERR(CE_WARN, "eHc_read_pcf8591(): Lost Arbitration");
		return (EHC_FAILURE);
	}

	/*
	 * Discard first read as per PCF8584 master receiver protocol.
	 * This is normally done in the eHc_start_pcf8584() routine.
	 */
	if ((status = eHc_read_pcf8584(ehcp, &discard)) != EHC_SUCCESS) {
		return (EHC_FAILURE);
	}

	/* Discard second read as per PCF8591 protocol */
	if ((status = eHc_read_pcf8584(ehcp, &discard)) != EHC_SUCCESS) {
		return (EHC_FAILURE);
	}

	for (i = 0; i < size - 1; i++) {
		if ((status = eHc_read_pcf8584(ehcp, &buf[i])) != EHC_SUCCESS) {
			return (EHC_FAILURE);
		}
	}

	if (eHc_after_read_pcf8584(ehcp, &buf[i]) != EHC_SUCCESS) {
		return (EHC_FAILURE);
	}

	return (EHC_SUCCESS);
}