/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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


#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/i2c/misc/i2c_svc.h>
#include <sys/mct_topology.h>
#include <sys/envctrl_gen.h>	/* must be before netract_gen.h	*/
#include <sys/netract_gen.h>
#include <sys/pcf8574_nct.h>
#include <sys/scsb_cbi.h>

#ifdef DEBUG
#define	dbg_print(level, str) cmn_err(level, str);
	static int	pcf8574_debug = 0x00000102;
#else
#define	dbg_print(level, str) {; }
#endif

#define	CV_LOCK(retval)				\
{									\
	mutex_enter(&unitp->umutex);	\
	while (unitp->pcf8574_flags == PCF8574_BUSY) {	\
		if (cv_wait_sig(&unitp->pcf8574_cv,	\
					&unitp->umutex) <= 0) {	\
			mutex_exit(&unitp->umutex);		\
			return (retval);		\
		}							\
	}								\
	unitp->pcf8574_flags = PCF8574_BUSY;	\
	mutex_exit(&unitp->umutex);		\
}

#define	CV_UNLOCK					\
{									\
	mutex_enter(&unitp->umutex);	\
	unitp->pcf8574_flags = 0;		\
	cv_signal(&unitp->pcf8574_cv);	\
	mutex_exit(&unitp->umutex);		\
}

static int nct_p10fan_patch = 0;	/* Fan patch for P1.0 */
static void	*pcf8574_soft_statep;

/*
 * cb ops (only need open,close,read,write,ioctl)
 */
static int	pcf8574_open(dev_t *, int, int, cred_t *);
static int	pcf8574_close(dev_t, int, int, cred_t *);
static int	pcf8574_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
static int	pcf8574_read(dev_t dev, struct uio *uiop, cred_t *cred_p);
static int	pcf8574_chpoll(dev_t, short, int, short *, struct pollhead **);
static uint_t	pcf8574_intr(caddr_t arg);
static int pcf8574_io(dev_t, struct uio *, int);

static struct cb_ops pcf8574_cbops = {
	pcf8574_open,		/* open */
	pcf8574_close,		/* close */
	nodev,				/* strategy */
	nodev,				/* print */
	nodev,				/* dump */
	pcf8574_read,		/* read */
	nodev,				/* write */
	pcf8574_ioctl,		/* ioctl */
	nodev,				/* devmap */
	nodev,				/* mmap */
	nodev,				/* segmap */
	pcf8574_chpoll,		/* poll */
	ddi_prop_op,		/* cb_prop_op */
	NULL,				/* streamtab */
	D_NEW | D_MP | D_HOTPLUG,	/* Driver compatibility flag */
	CB_REV,				/* rev */
	nodev,				/* int (*cb_aread)() */
	nodev				/* int (*cb_awrite)() */
};

/*
 * dev ops
 */
static int pcf8574_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int pcf8574_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);

/* kstat routines */
static int pcf8574_add_kstat(struct pcf8574_unit *, scsb_fru_status_t);
static void pcf8574_delete_kstat(struct pcf8574_unit *);
static int pcf8574_kstat_update(kstat_t *, int);
static int pcf8574_read_chip(struct pcf8574_unit *unitp,
	uint16_t size);
static int pcf8574_write_chip(struct pcf8574_unit *unitp,
	uint16_t size, uint8_t bitpattern);
static int pcf8574_read_props(struct pcf8574_unit *unitp);
static int pcf8574_init_chip(struct pcf8574_unit *unitp, int);
/*
 * SCSB callback function
 */
static void pcf8574_callback(void *, scsb_fru_event_t, scsb_fru_status_t);
extern int scsb_intr_register(uint_t (*intr_handler)(caddr_t), caddr_t,
		fru_id_t);
extern int scsb_intr_unregister(fru_id_t);

extern int nct_i2c_transfer(i2c_client_hdl_t i2c_hdl, i2c_transfer_t *i2c_tran);

static struct dev_ops pcf8574_ops = {
	DEVO_REV,
	0,
	ddi_getinfo_1to1,
	nulldev,
	nulldev,
	pcf8574_attach,
	pcf8574_detach,
	nodev,
	&pcf8574_cbops,
	NULL
};

extern struct mod_ops mod_driverops;

static struct modldrv pcf8574_modldrv = {
	&mod_driverops,		/* type of module - driver */
	"Netract pcf8574 (gpio) %I% ",
	&pcf8574_ops,
};

static struct modlinkage pcf8574_modlinkage = {
	MODREV_1,
	&pcf8574_modldrv,
	0
};

/* char _depends_on[] = "misc/i2c_svc drv/scsb"; */

int
_init(void)
{
	register int    error;

	error = mod_install(&pcf8574_modlinkage);
	if (!error) {
		(void) ddi_soft_state_init(&pcf8574_soft_statep,
			sizeof (struct pcf8574_unit), PCF8574_MAX_DEVS);
	}

	return (error);
}

int
_fini(void)
{
	register int    error;

	error = mod_remove(&pcf8574_modlinkage);
	if (!error)
		ddi_soft_state_fini(&pcf8574_soft_statep);

	return (error);
}

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

/*ARGSUSED*/
static int
pcf8574_open(dev_t *devp, int flags, int otyp, cred_t *credp)
{
	struct pcf8574_unit *unitp;
	register int    instance;
	int err = DDI_SUCCESS;

	instance = getminor(*devp);
	if (instance < 0) {
		return (ENXIO);
	}

	unitp = (struct pcf8574_unit *)
		ddi_get_soft_state(pcf8574_soft_statep, instance);

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

	if (otyp != OTYP_CHR) {
		return (EINVAL);
	}

	mutex_enter(&unitp->umutex);

	if (flags & FEXCL) {
		if (unitp->pcf8574_oflag != 0) {
			err = EBUSY;
		} else {
			unitp->pcf8574_oflag = FEXCL;
		}
	} else {
		if (unitp->pcf8574_oflag == FEXCL) {
			err = EBUSY;
		} else {
			unitp->pcf8574_oflag = FOPEN;
		}
	}

	mutex_exit(&unitp->umutex);

	return (err);
}

/*ARGSUSED*/
static int
pcf8574_close(dev_t dev, int flags, int otyp, cred_t *credp)
{
	struct pcf8574_unit *unitp;
	register int    instance;

#ifdef lint
	flags = flags;
	otyp = otyp;
#endif

	instance = getminor(dev);

	if (instance < 0) {
		return (ENXIO);
	}

	unitp = (struct pcf8574_unit *)
		ddi_get_soft_state(pcf8574_soft_statep, instance);

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

	mutex_enter(&unitp->umutex);

	unitp->pcf8574_oflag = 0;

	mutex_exit(&unitp->umutex);

	return (DDI_SUCCESS);
}


/*ARGSUSED*/
static int
pcf8574_read(dev_t dev, struct uio *uiop, cred_t *cred_p)
{
	return (pcf8574_io(dev, uiop, B_READ));
}

static int
pcf8574_io(dev_t dev, struct uio *uiop, int rw)
{
	struct pcf8574_unit *unitp;
	register int    instance;
	uint16_t	bytes_to_rw;
	int	err = DDI_SUCCESS;

	err = 0;
	instance = getminor(dev);

	if (instance < 0) {
		return (ENXIO);
	}

	unitp = (struct pcf8574_unit *)
		ddi_get_soft_state(pcf8574_soft_statep, instance);
	if (unitp == NULL) {
		return (ENXIO);
	}
	if ((bytes_to_rw = uiop->uio_resid) > PCF8574_TRAN_SIZE) {
		return (EINVAL);
	}

	CV_LOCK(EINTR)

	if (rw == B_WRITE) {
		err = uiomove(unitp->i2c_tran->i2c_wbuf,
			bytes_to_rw, UIO_WRITE, uiop);

		if (!err) {
			err = pcf8574_write_chip(unitp, bytes_to_rw,
				unitp->writemask);
		}

	} else {
			err = pcf8574_read_chip(unitp, bytes_to_rw);
			if (!err) {
				err = uiomove(unitp->i2c_tran->i2c_rbuf,
					bytes_to_rw, UIO_READ, uiop);
			}
	}

	CV_UNLOCK
	if (err)
		err = EIO;

	return (err);
}

static int
pcf8574_do_resume(dev_info_t *dip)
{
	int instance = ddi_get_instance(dip);
	struct pcf8574_unit *unitp =
		ddi_get_soft_state(pcf8574_soft_statep, instance);

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

	CV_UNLOCK

	return (DDI_SUCCESS);
}

static int
pcf8574_do_detach(dev_info_t *dip)
{
	struct pcf8574_unit *unitp;
	int instance;
	uint_t attach_flag;

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

	attach_flag = unitp->attach_flag;

	if (attach_flag & PCF8574_INTR_ADDED) {
		scsb_intr_unregister((fru_id_t)unitp->props.slave_address);
	}

	if (attach_flag & PCF8574_KSTAT_INIT) {
		pcf8574_delete_kstat(unitp);
	}

	if (attach_flag & PCF8574_LOCK_INIT) {
		mutex_destroy(&unitp->umutex);
		cv_destroy(&unitp->pcf8574_cv);
	}

	scsb_fru_unregister((void *)unitp,
		(fru_id_t)unitp->props.slave_address);

	if (attach_flag & PCF8574_ALLOC_TRANSFER) {
		/*
		 * restore the lengths to allocated lengths
		 * before freeing.
		 */
		unitp->i2c_tran->i2c_wlen = MAX_WLEN;
		unitp->i2c_tran->i2c_rlen = MAX_RLEN;
		i2c_transfer_free(unitp->pcf8574_hdl, unitp->i2c_tran);
	}

	if (attach_flag & PCF8574_REGISTER_CLIENT) {
		i2c_client_unregister(unitp->pcf8574_hdl);
	}

	if (attach_flag & PCF8574_MINORS_CREATED) {
		ddi_remove_minor_node(dip, NULL);
	}

	if (attach_flag & PCF8574_PROPS_READ) {
		if (unitp->pcf8574_type == PCF8574_ADR_CPUVOLTAGE &&
			unitp->props.num_chans_used != 0) {
			ddi_prop_free(unitp->props.channels_in_use);
		} else {
			ddi_prop_remove(DDI_DEV_T_NONE, dip,
							"interrupt-priorities");
		}
	}

	if (attach_flag & PCF8574_SOFT_STATE_ALLOC) {
		ddi_soft_state_free(pcf8574_soft_statep, instance);
	}

	return (DDI_SUCCESS);
}

/*
 * NOTE****
 * The OBP will create device tree node for all I2C devices which
 * may be present in a system. This means, even if the device is
 * not physically present, the device tree node exists. We also
 * will succeed the attach routine, since currently there is no
 * hotplug support in the I2C bus, and the FRUs need to be hot
 * swappable. Only during an I2C transaction we figure out whether
 * the particular I2C device is actually present in the system
 * by looking at the system controller board register. The fantray
 * and power-supply devices may be swapped any time after system
 * reboot, and the way we can make sure that the device is attached
 * to the driver, is by always keeping the driver loaded, and report
 * an error during the actual transaction.
 */
static int
pcf8574_do_attach(dev_info_t *dip)
{
	register struct pcf8574_unit *unitp;
	int instance;
	char name[MAXNAMELEN];
	int		i;
	pcf8574_channel_t *chp;
	scsb_fru_status_t	dev_presence;

	instance = ddi_get_instance(dip);
#ifdef DEBUG
	if (pcf8574_debug & 0x04)
		cmn_err(CE_NOTE, "pcf8574_attach: instance=%d\n",
		instance);
#endif /* DEBUG */

	if (ddi_soft_state_zalloc(pcf8574_soft_statep, instance) !=
		DDI_SUCCESS) {
		return (DDI_FAILURE);
	}
	unitp = ddi_get_soft_state(pcf8574_soft_statep, instance);

	if (unitp == NULL) {
		ddi_soft_state_free(pcf8574_soft_statep, instance);
		return (DDI_FAILURE);
	}

	unitp->dip = dip;

	unitp->attach_flag = PCF8574_SOFT_STATE_ALLOC;

	if (pcf8574_read_props(unitp) != DDI_PROP_SUCCESS) {
		ddi_soft_state_free(pcf8574_soft_statep, instance);
		return (DDI_FAILURE);
	}

	unitp->attach_flag |= PCF8574_PROPS_READ;

	/*
	 * Set the current operating mode to NORMAL_MODE.
	 */
	unitp->current_mode = ENVCTRL_NORMAL_MODE;

	snprintf(unitp->pcf8574_name, PCF8574_NAMELEN,
		"%s%d", ddi_driver_name(dip), instance);

	if (unitp->pcf8574_type == PCF8574_TYPE_PWRSUPP) {
		(void) sprintf(name, "pwrsuppply");
		if (ddi_create_minor_node(dip, name, S_IFCHR, instance,
			PCF8574_NODE_TYPE, NULL) == DDI_FAILURE) {
			ddi_remove_minor_node(dip, NULL);
			pcf8574_do_detach(dip);

			return (DDI_FAILURE);
		}
	}
	else
	if (unitp->pcf8574_type == PCF8574_TYPE_FANTRAY) {
		(void) sprintf(name, "fantray");
		if (ddi_create_minor_node(dip, name, S_IFCHR, instance,
			PCF8574_NODE_TYPE, NULL) == DDI_FAILURE) {
			ddi_remove_minor_node(dip, NULL);
			pcf8574_do_detach(dip);

			return (DDI_FAILURE);
		}
	}
	else
	if (unitp->pcf8574_type == PCF8574_TYPE_CPUVOLTAGE) {
		(void) sprintf(name, "cpuvoltage");
		if (ddi_create_minor_node(dip, name, S_IFCHR, instance,
			PCF8574_NODE_TYPE, NULL) == DDI_FAILURE) {
			ddi_remove_minor_node(dip, NULL);
			pcf8574_do_detach(dip);

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

	unitp->attach_flag |= PCF8574_MINORS_CREATED;

	/*
	 * Now we need read/write masks since all the 8574 bits can be either
	 * read/written, but some ports are intended to be RD/WR only, or RW
	 * If no channels-in-use propoerty, set default values.
	 */
	if (unitp->pcf8574_type == PCF8574_TYPE_FANTRAY) {
		unitp->readmask = PCF8574_FAN_READMASK;
		unitp->writemask = PCF8574_FAN_WRITEMASK;
	}
	if (unitp->pcf8574_type == PCF8574_TYPE_PWRSUPP) {
		unitp->readmask = PCF8574_PS_READMASK;
		unitp->writemask = PCF8574_PS_WRITEMASK;
	}

	for (i = unitp->props.num_chans_used,
		chp = unitp->props.channels_in_use; i; --i, ++chp) {
		unitp->readmask |= (uint8_t)(
			(chp->io_dir == I2C_PROP_IODIR_IN ||
			chp->io_dir == I2C_PROP_IODIR_INOUT) << chp->port);
		unitp->writemask |= (uint8_t)(
			(chp->io_dir == I2C_PROP_IODIR_OUT ||
			chp->io_dir == I2C_PROP_IODIR_INOUT) << chp->port);
	}

#ifdef DEBUG
	cmn_err(CE_NOTE, "pcf8574_do_attach: readmask = 0x%x \
		writemask = 0x%x\n", unitp->readmask, unitp->writemask);
#endif /* DEBUG */

	if (i2c_client_register(dip, &unitp->pcf8574_hdl)
		!= I2C_SUCCESS) {
		pcf8574_do_detach(dip);

		return (DDI_FAILURE);
	}
	unitp->attach_flag |= PCF8574_REGISTER_CLIENT;

	/*
	 * Allocate the I2C_transfer structure. The same structure
	 * is used throughout the driver.
	 */
	if (i2c_transfer_alloc(unitp->pcf8574_hdl, &unitp->i2c_tran,
		MAX_WLEN, MAX_RLEN, KM_SLEEP)
		!= I2C_SUCCESS) {
		pcf8574_do_detach(dip);
		return (DDI_FAILURE);
	}
	unitp->attach_flag |= PCF8574_ALLOC_TRANSFER;

	/*
	 * To begin with we set the mode to I2C_RD.
	 */
	unitp->i2c_tran->i2c_flags = I2C_RD;
	unitp->i2c_tran->i2c_version = I2C_XFER_REV;

	/*
	 * Set the busy flag and open flag to 0.
	 */
	unitp->pcf8574_flags = 0;
	unitp->pcf8574_oflag = 0;

	mutex_init(&unitp->umutex, NULL, MUTEX_DRIVER, NULL);
	cv_init(&unitp->pcf8574_cv, NULL, CV_DRIVER, NULL);

	unitp->attach_flag |= PCF8574_LOCK_INIT;

	/*
	 * Register out callback function with the SCSB driver, and save
	 * the returned value to check that the device instance exists.
	 */
	dev_presence = scsb_fru_register(pcf8574_callback, (void *)unitp,
			(fru_id_t)unitp->props.slave_address);
	if (dev_presence == FRU_NOT_AVAILABLE) {
		scsb_fru_unregister((void *)unitp,
			(fru_id_t)unitp->props.slave_address);
	}

	/*
	 * Add the kstats. First we need to get the property values
	 * depending on the device type. For example, for the fan
	 * tray there will be a different set of properties, and there
	 * will be another for the powersupplies, and another one for
	 * the CPU voltage monitor. Initialize the kstat structures with
	 * these values.
	 */

	if (pcf8574_add_kstat(unitp, dev_presence) != DDI_SUCCESS) {
		pcf8574_do_detach(dip);

		return (DDI_FAILURE);
	}

	unitp->attach_flag |= PCF8574_KSTAT_INIT;

	/*
	 * Due to observed behavior on Solaris 8, the handler must be
	 * registered before any interrupts are enabled,
	 * in spite of what the ddi_get_iblock_cookie() manual says.
	 * As per the HW/SW spec, by default interrupts are disabled.
	 */

	if (dev_presence == FRU_PRESENT) { /* program the chip */
		pcf8574_init_chip(unitp, 0);   /* Disable intr first */
	}

	if (unitp->pcf8574_canintr == PCF8574_INTR_ON) {
#ifdef DEBUG
		if (pcf8574_debug & 0x0004)
			cmn_err(CE_NOTE, "registering pcf9574 interrupt "
					"handler");
#endif /* DEBUG */
		if (scsb_intr_register(pcf8574_intr, (void *)unitp,
			(fru_id_t)unitp->props.slave_address) == DDI_SUCCESS) {
			unitp->pcf8574_canintr |= PCF8574_INTR_ENABLED;
			unitp->attach_flag |= PCF8574_INTR_ADDED;
		} else {
			pcf8574_do_detach(dip);

			return (DDI_FAILURE);
		}
	}

	ddi_report_dev(dip);

	return (DDI_SUCCESS);
}

static int
pcf8574_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	switch (cmd) {
	case DDI_ATTACH:
		return (pcf8574_do_attach(dip));
	case DDI_RESUME:
		return (pcf8574_do_resume(dip));
	default:
		return (DDI_FAILURE);
	}
}

static int
pcf8574_do_suspend(dev_info_t *dip)
{
	int instance = ddi_get_instance(dip);
	struct pcf8574_unit *unitp =
		ddi_get_soft_state(pcf8574_soft_statep, instance);

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

	/*
	 * Set the busy flag so that future transactions block
	 * until resume.
	 */
	CV_LOCK(ENXIO)

	return (DDI_SUCCESS);
}

static int
pcf8574_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	switch (cmd) {
	case DDI_DETACH:
		return (pcf8574_do_detach(dip));
	case DDI_SUSPEND:
		return (pcf8574_do_suspend(dip));
	default:
		return (DDI_FAILURE);
	}
}

static int
pcf8574_chpoll(dev_t dev, short events, int anyyet, short *reventsp,
    struct pollhead **phpp)
{
	struct pcf8574_unit	*unitp;
	int		instance;

	instance = getminor(dev);
	if ((unitp = (struct pcf8574_unit *)ddi_get_soft_state(
		pcf8574_soft_statep, instance)) == NULL) {
		return (ENXIO);
	}
	*reventsp = 0;
	mutex_enter(&unitp->umutex);
	if (unitp->poll_event) {
		*reventsp = unitp->poll_event;
		unitp->poll_event = 0;
	} else if ((events & POLLIN) && !anyyet)
		*phpp = &unitp->poll;
	mutex_exit(&unitp->umutex);
	return (0);
}

/*
 * In normal scenarios, this function should never get called.
 * But, we will still come back and call this function if scsb
 * interrupt sources does not indicate an scsb interrupt. We may
 * come to this situation when SunVTS env4test is independently
 * changing the device registers.
 */
uint_t
pcf8574_intr(caddr_t arg)
{
	int			ic;
	uint8_t value;
	struct pcf8574_unit	*unitp = (struct pcf8574_unit *)(void *)arg;
	scsb_fru_status_t	dev_presence;
	i2c_transfer_t *tp = unitp->i2c_tran;

	ic = DDI_INTR_CLAIMED;
#ifdef DEBUG
	cmn_err(CE_NOTE, " In the interrupt service routine, %x",
		unitp->props.slave_address);
#endif

	/*
	 * Initiate an I2C transaction to find out
	 * whether this is the device which interrupted.
	 */
	mutex_enter(&unitp->umutex);
	while (unitp->pcf8574_flags == PCF8574_BUSY) {
		if (cv_wait_sig(&unitp->pcf8574_cv, &unitp->umutex) <= 0) {
			mutex_exit(&unitp->umutex);
			return (DDI_INTR_UNCLAIMED);
		}
	}

	unitp->pcf8574_flags = PCF8574_BUSY;
	mutex_exit(&unitp->umutex);

	switch (unitp->pcf8574_type) {
		case PCF8574_TYPE_CPUVOLTAGE: {
			dev_presence = FRU_PRESENT;
			break;
		}
		case PCF8574_TYPE_PWRSUPP: {
			envctrl_pwrsupp_t *envp =
				(envctrl_pwrsupp_t *)unitp->envctrl_kstat;
			dev_presence = envp->ps_present;
			break;
		}
		case PCF8574_TYPE_FANTRAY: {
			envctrl_fantray_t *envp =
				(envctrl_fantray_t *)unitp->envctrl_kstat;
			dev_presence = envp->fan_present;
			break;
		}
	}
	if (dev_presence != FRU_PRESENT) {
		ic = DDI_INTR_UNCLAIMED;
		goto intr_exit;
	}
	if (pcf8574_read_chip(unitp, 1) != I2C_SUCCESS) {
		ic = DDI_INTR_UNCLAIMED;
		goto intr_exit;
	}
	value = unitp->i2c_tran->i2c_rbuf[0];
	/*
	 * If interrupt is already masked, return
	 */
	if (value & PCF8574_INTRMASK_BIT) {
		ic = DDI_INTR_UNCLAIMED;
		goto intr_exit;
	}

	/*
	 * In case a fault bit is set, claim the interrupt.
	 */
	switch (unitp->pcf8574_type) {
	case PCF8574_TYPE_PWRSUPP:
	{
		envctrl_pwrsupp_t *envp =
			(envctrl_pwrsupp_t *)unitp->envctrl_kstat;

		if (PCF8574_PS_FAULT(value) ||
			PCF8574_PS_TEMPOK(value) ||
			PCF8574_PS_ONOFF(value) ||
			PCF8574_PS_FANOK(value)) {

			envp->ps_ok =		PCF8574_PS_FAULT(value);
			envp->temp_ok =		PCF8574_PS_TEMPOK(value);
			envp->psfan_ok =	PCF8574_PS_FANOK(value);
			envp->on_state =	PCF8574_PS_ONOFF(value);
			envp->ps_ver =		PCF8574_PS_TYPE(value);

			tp->i2c_wbuf[0] =
				PCF8574_PS_DEFAULT | PCF8574_PS_MASKINTR;
			tp->i2c_wlen = 1;
			tp->i2c_rlen = 0;
			tp->i2c_flags = I2C_WR;

			unitp->i2c_status =
				nct_i2c_transfer(unitp->pcf8574_hdl, tp);

			unitp->poll_event = POLLIN;
			pollwakeup(&unitp->poll, POLLIN);
		} else {
			ic = DDI_INTR_UNCLAIMED;
		}
	}
	break;

	case PCF8574_TYPE_FANTRAY:
	{
		envctrl_fantray_t *envp =
			(envctrl_fantray_t *)unitp->envctrl_kstat;

		if (!PCF8574_FAN_FAULT(value)) {

			envp->fan_ver = 	PCF8574_FAN_TYPE(value);
			envp->fan_ok = 		PCF8574_FAN_FAULT(value);
			envp->fanspeed =  	PCF8574_FAN_FANSPD(value);

			tp->i2c_wbuf[0] =
				PCF8574_FAN_DEFAULT | PCF8574_FAN_MASKINTR;
			tp->i2c_wlen = 1;
			tp->i2c_rlen = 0;
			tp->i2c_flags = I2C_WR;

			unitp->i2c_status =
				nct_i2c_transfer(unitp->pcf8574_hdl, tp);

			unitp->poll_event = POLLIN;
			pollwakeup(&unitp->poll, POLLIN);

		} else {
			ic = DDI_INTR_UNCLAIMED;
		}
	}
	break;

	default:
		ic = DDI_INTR_UNCLAIMED;
	} /* switch */

intr_exit:
	mutex_enter(&unitp->umutex);
	unitp->pcf8574_flags = 0;
	cv_signal(&unitp->pcf8574_cv);
	mutex_exit(&unitp->umutex);

	return (ic);
}

static int
call_copyin(caddr_t arg, struct pcf8574_unit *unitp, int mode)
{
	uchar_t *wbuf;
	uchar_t *rbuf;
	i2c_transfer_t i2ct;
	i2c_transfer_t *i2ctp = unitp->i2c_tran;


	if (ddi_copyin((void *)arg, (caddr_t)&i2ct,
		sizeof (i2c_transfer_t), mode) != DDI_SUCCESS) {
		return (I2C_FAILURE);
	}

	/*
	 * Save the read and write buffer pointers in the transfer
	 * structure, otherwise these will get overwritten when we
	 * do a bcopy. Restore once done.
	 */

	wbuf = i2ctp->i2c_wbuf;
	rbuf = i2ctp->i2c_rbuf;

	bcopy(&i2ct, i2ctp, sizeof (i2c_transfer_t));

	i2ctp->i2c_wbuf = wbuf;
	i2ctp->i2c_rbuf = rbuf;

	/*
	 * copyin the read and write buffers to the saved buffers.
	 */

	if (i2ct.i2c_wlen != 0) {
		if (ddi_copyin(i2ct.i2c_wbuf, (caddr_t)i2ctp->i2c_wbuf,
			i2ct.i2c_wlen, mode) != DDI_SUCCESS) {
				return (I2C_FAILURE);
		}
	}

	return (I2C_SUCCESS);
}

static int
call_copyout(caddr_t arg, struct pcf8574_unit *unitp, int mode)
{
	i2c_transfer_t i2ct;
	i2c_transfer_t *i2ctp = unitp->i2c_tran;

	/*
	 * We will copyout the last three fields only, skipping
	 * the remaining ones, before copying the rbuf to the
	 * user buffer.
	 */

	int uskip = sizeof (i2c_transfer_t) - 3*sizeof (int16_t),
		kskip = sizeof (i2c_transfer_t) - 3*sizeof (int16_t);

	/*
	 * First copyin the user structure to the temporary i2ct,
	 * so that we have the wbuf and rbuf addresses in it.
	 */

	uskip = sizeof (i2c_transfer_t) - 3 * (sizeof (uint16_t));

	/*
	 * copyout the last three out fields now.
	 */

	if (ddi_copyout((void *)((intptr_t)i2ctp+kskip), (void *)
		((intptr_t)arg + uskip), 3*sizeof (uint16_t), mode)
		!= DDI_SUCCESS) {
		return (I2C_FAILURE);
		}

	/*
	 * In case we have something to write, get the address of the read
	 * buffer.
	 */

	if (i2ctp->i2c_rlen > i2ctp->i2c_r_resid) {

		if (ddi_copyin((void *)arg, &i2ct,
			sizeof (i2c_transfer_t), mode) != DDI_SUCCESS) {
			return (I2C_FAILURE);
		}

		/*
		 * copyout the read buffer to the saved user buffer in i2ct.
		 */

		if (ddi_copyout(i2ctp->i2c_rbuf, i2ct.i2c_rbuf,
			i2ctp->i2c_rlen - i2ctp->i2c_r_resid, mode)
			!= DDI_SUCCESS) {
			return (I2C_FAILURE);
		}
	}

	return (I2C_SUCCESS);
}

/*ARGSUSED*/
static int
pcf8574_ioctl(dev_t dev, int cmd, intptr_t arg,
		int mode, cred_t *credp, int *rvalp)
{
	struct pcf8574_unit *unitp;
	register int    instance;
	int err = 0;
	uint8_t value, inval, outval;
	scsb_fru_status_t dev_presence;

	instance = getminor(dev);

	if (instance < 0) {
		return (ENXIO);
	}
	unitp = (struct pcf8574_unit *)
		ddi_get_soft_state(pcf8574_soft_statep, instance);

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

	dev_presence =
		scsb_fru_status((uchar_t)unitp->props.slave_address);

	CV_LOCK(EINTR)

	switch (cmd) {
	case ENVC_IOC_INTRMASK:
	if (dev_presence == FRU_NOT_PRESENT) {
		break;
	}

	if (ddi_copyin((caddr_t)arg, (caddr_t)&inval,
		sizeof (uint8_t), mode) != DDI_SUCCESS) {
		err = EFAULT;
		break;
	}

	if (inval != 0 && inval != 1) {
		err = EINVAL;
	} else {
		unitp->i2c_tran->i2c_wbuf[0] =
			PCF8574_INT_MASK(inval);
		if (pcf8574_write_chip(unitp, 1, PCF8574_INTRMASK_BIT)
			!= I2C_SUCCESS) {
			err = EFAULT;
		}
	}
	break;

	case ENVC_IOC_SETFAN:
	if (unitp->pcf8574_type != PCF8574_TYPE_FANTRAY) {
		err = EINVAL;
		break;
	}
	if (dev_presence == FRU_NOT_PRESENT) {
		err = EINVAL;
		break;
	}
	if (ddi_copyin((caddr_t)arg, (caddr_t)&inval, sizeof (uint8_t),
		mode) != DDI_SUCCESS) {
			err = EFAULT;
			break;
	}
	if (inval != PCF8574_FAN_SPEED_LOW &&
		inval != PCF8574_FAN_SPEED_HIGH) {
		err = EINVAL;
		break;
	}

	unitp->i2c_tran->i2c_wbuf[0] = PCF8574_FAN_SPEED(inval);

	if (pcf8574_write_chip(unitp, 1, PCF8574_FANSPEED_BIT)
		!= I2C_SUCCESS) {
		err = EFAULT;
	}
	break;

	case ENVC_IOC_SETSTATUS:
	/*
	 * Allow this ioctl only in DIAG mode.
	 */
	if (unitp->current_mode != ENVCTRL_DIAG_MODE) {
		err = EINVAL;
	} else {
		if (dev_presence == FRU_NOT_PRESENT) {
			err = EINVAL;
			break;
		}
		if (ddi_copyin((caddr_t)arg, (caddr_t)&inval,
			sizeof (uint8_t), mode) != DDI_SUCCESS) {
			err = EFAULT;
		} else {
			unitp->i2c_tran->i2c_wbuf[0] = inval & 0xff;
			if (pcf8574_write_chip(unitp, 1, 0xff)
				!= I2C_SUCCESS) {
				err = EFAULT;
			}
		}
	}
	break;

	case ENVC_IOC_GETFAN:
	case ENVC_IOC_GETSTATUS:
	case ENVC_IOC_GETTYPE:
	case ENVC_IOC_GETFAULT:
	case ENVC_IOC_PSTEMPOK:
	case ENVC_IOC_PSFANOK:
	case ENVC_IOC_PSONOFF: {
		if (dev_presence == FRU_NOT_PRESENT) {
			err = EINVAL;
			break;
		}
		if (pcf8574_read_chip(unitp, 1)
			!= I2C_SUCCESS) {
			err = EFAULT;
			break;
		}
		value = unitp->i2c_tran->i2c_rbuf[0];
		if (cmd == ENVC_IOC_GETFAN) {
			if (unitp->pcf8574_type != PCF8574_TYPE_FANTRAY) {
				err = EINVAL;
				break;
			} else {
				outval = PCF8574_FAN_FANSPD(value);
			}
		}
		else
		if (cmd == ENVC_IOC_GETSTATUS) {
			outval = value;
		}
		else
		if (cmd == ENVC_IOC_GETTYPE) {
			if (unitp->pcf8574_type == PCF8574_TYPE_PWRSUPP)
				outval = PCF8574_PS_TYPE(value);
			if (unitp->pcf8574_type == PCF8574_TYPE_FANTRAY)
				outval = PCF8574_FAN_TYPE(value);
		}
		else
		if (cmd == ENVC_IOC_GETFAULT) {
			if (unitp->pcf8574_type == PCF8574_TYPE_PWRSUPP)
				outval = PCF8574_PS_FAULT(value);
			if (unitp->pcf8574_type == PCF8574_TYPE_FANTRAY)
				outval = PCF8574_PS_FAULT(value);
		}
		else
		if (cmd == ENVC_IOC_PSTEMPOK) {
			outval = PCF8574_PS_TEMPOK(value);
		}
		else
		if (cmd == ENVC_IOC_PSFANOK) {
			outval = PCF8574_PS_FANOK(value);
		}
		else
		if (cmd == ENVC_IOC_PSONOFF) {
			outval = PCF8574_PS_ONOFF(value);
		} else {
			outval = 0;
		}

		if (ddi_copyout((caddr_t)&outval, (caddr_t)arg,
			sizeof (uint8_t), mode) != DDI_SUCCESS) {
			err = EFAULT;
		}
	}
	break;

	case ENVC_IOC_GETMODE: {
		uint8_t curr_mode = unitp->current_mode;

		if (ddi_copyout((caddr_t)&curr_mode, (caddr_t)arg,
			sizeof (uint8_t), mode) != DDI_SUCCESS) {
			err = EFAULT;
		}
		break;
	}

	case ENVC_IOC_SETMODE: {
		uint8_t curr_mode;
		if (ddi_copyin((caddr_t)arg, (caddr_t)&curr_mode,
			sizeof (uint8_t), mode) != DDI_SUCCESS) {
				err = EFAULT;
				break;
		}
		if (curr_mode == ENVCTRL_DIAG_MODE ||
			curr_mode == ENVCTRL_NORMAL_MODE) {
			unitp->current_mode = curr_mode; /* Don't do anything */
		}
		break;
	}


	case I2CDEV_TRAN:
		if (call_copyin((caddr_t)arg, unitp, mode) != DDI_SUCCESS) {
			err = EFAULT;
			break;
		}
		unitp->i2c_status = err =
			nct_i2c_transfer(unitp->pcf8574_hdl, unitp->i2c_tran);

		if (err != I2C_SUCCESS) {
			err = EIO;
		} else {
			if (call_copyout((caddr_t)arg, unitp, mode)
				!= DDI_SUCCESS) {
				err = EFAULT;
				break;
			}
		}
		break;

	default:
		err = EINVAL;
	}

	CV_UNLOCK

	return (err);
}

static int
pcf8574_add_kstat(struct pcf8574_unit *unitp, scsb_fru_status_t dev_presence)
{
	char ksname[50];
	int id;
	uint8_t i2c_address = unitp->props.slave_address;

	/*
	 * We create the kstat depending on the device function,
	 * allocate the kstat placeholder and initialize the
	 * values.
	 */
	unitp->envctrl_kstat = NULL;
	switch (unitp->pcf8574_type) {
	case PCF8574_TYPE_CPUVOLTAGE:
	{
		if ((unitp->kstatp = kstat_create(I2C_PCF8574_NAME,
			unitp->instance, I2C_KSTAT_CPUVOLTAGE, "misc",
			KSTAT_TYPE_RAW, sizeof (envctrl_cpuvoltage_t),
			KSTAT_FLAG_PERSISTENT)) != NULL) {

			if ((unitp->envctrl_kstat = kmem_zalloc(
				sizeof (envctrl_cpuvoltage_t), KM_NOSLEEP)) ==
				NULL) {
				kstat_delete(unitp->kstatp);
				return (DDI_FAILURE);
			}
		} else {
			return (DDI_FAILURE);
		}

		break;
	}
	case PCF8574_TYPE_PWRSUPP:
	{
		envctrl_pwrsupp_t *envp;
		if (i2c_address == PCF8574_ADR_PWRSUPPLY1) {
			id = 1;
		} else if (i2c_address == PCF8574_ADR_PWRSUPPLY2) {
			id = 2;
		} else  {
			id = i2c_address - PCF8574_ADR_PWRSUPPLY1;
		}
		sprintf(ksname, "%s%d", I2C_KSTAT_PWRSUPPLY, id);
		if ((unitp->kstatp = kstat_create(I2C_PCF8574_NAME,
			unitp->instance, ksname, "misc",
			KSTAT_TYPE_RAW, sizeof (envctrl_pwrsupp_t),
			KSTAT_FLAG_PERSISTENT)) != NULL) {

			if ((unitp->envctrl_kstat = kmem_zalloc(
				sizeof (envctrl_pwrsupp_t), KM_NOSLEEP)) ==
				NULL) {
				kstat_delete(unitp->kstatp);
				return (DDI_FAILURE);
			}
			/*
			 * Initialize the kstat fields. Need to initialize
			 * the present field from SCSB info (dev_presence)
			 */
			envp = (envctrl_pwrsupp_t *)unitp->envctrl_kstat;

			envp->ps_present = dev_presence;
			envp->ps_ok = 0;
			envp->temp_ok = 0;
			envp->psfan_ok = 0;
			envp->on_state = 0;
			envp->ps_ver = 0;
		} else {
			return (DDI_FAILURE);
		}

		break;
	}
	case PCF8574_TYPE_FANTRAY:
	{
		envctrl_fantray_t *envp;
		if (i2c_address == PCF8574_ADR_FANTRAY1) {
			id = 1;
		} else if (i2c_address == PCF8574_ADR_FANTRAY2) {
			id = 2;
		} else  {
			id = i2c_address - PCF8574_ADR_FANTRAY1;
		}
		sprintf(ksname, "%s%d", I2C_KSTAT_FANTRAY, id);
		if ((unitp->kstatp = kstat_create(I2C_PCF8574_NAME,
			unitp->instance, ksname, "misc",
			KSTAT_TYPE_RAW, sizeof (envctrl_fantray_t),
			KSTAT_FLAG_PERSISTENT | KSTAT_FLAG_WRITABLE)) != NULL) {

			if ((unitp->envctrl_kstat = kmem_zalloc(
				sizeof (envctrl_fantray_t), KM_NOSLEEP)) ==
				NULL) {
				kstat_delete(unitp->kstatp);
				return (DDI_FAILURE);
			}

			/*
			 * Initialize the kstat fields. Need to initialize
			 * the present field from SCSB info (dev_presence)
			 */
			envp = (envctrl_fantray_t *)unitp->envctrl_kstat;

			envp->fan_present = dev_presence;
			envp->fan_ok = 0;
			envp->fanspeed =  PCF8574_FAN_SPEED60;
			envp->fan_ver = 0;
		} else {
			return (DDI_FAILURE);
		}

		break;
	}
	default:
		return (DDI_FAILURE);
	}

	unitp->kstatp->ks_private = (void *)unitp;
	unitp->kstatp->ks_update = pcf8574_kstat_update;

	kstat_install(unitp->kstatp);

	return (DDI_SUCCESS);
}

/*
 * This function reads a single byte from the pcf8574 chip, for use by the
 * kstat routines. The protocol for read will depend on the function.
 */

static int
pcf8574_read_chip(struct pcf8574_unit *unitp, uint16_t size)
{
	int retval, i;
	i2c_transfer_t *tp = unitp->i2c_tran;


	tp->i2c_flags = I2C_RD;
	tp->i2c_rlen = size;
	tp->i2c_wlen = 0;

	/*
	 * Read the bytes from the pcf8574, mask off the
	 * non-read bits and return the value. Block with
	 * the driverwide lock.
	 */
	unitp->i2c_status = retval =
		nct_i2c_transfer(unitp->pcf8574_hdl, unitp->i2c_tran);

	if (retval != I2C_SUCCESS) {
		return (retval);
	}

	for (i = 0; i < size; i++) {
		tp->i2c_rbuf[i] &= unitp->readmask;
	}

	return (I2C_SUCCESS);
}

/*
 * This function writes a single byte to the pcf8574 chip, for use by the
 * ioctl routines. The protocol for write will depend on the function.
 * The bitpattern tells which bits are being modified, by setting these
 * bits in bitpattern to 1, e.g for fanspeed, bitpattern = 0x08, fanspeed
 * and intr 0x0c, only intr 0x04.
 */

static int
pcf8574_write_chip(struct pcf8574_unit *unitp,
		uint16_t size, uint8_t bitpattern)
{
	i2c_transfer_t *tp = unitp->i2c_tran;
	int i;

		/*
		 * pcf8574_write
		 *
		 * First read the byte, modify only the writable
		 * ports, then write back the modified data.
		 */
		tp->i2c_wlen = 0;
		tp->i2c_rlen = size;
		tp->i2c_flags = I2C_RD;

		unitp->i2c_status = nct_i2c_transfer(unitp->pcf8574_hdl, tp);

		if (unitp->i2c_status != I2C_SUCCESS) {
			return (I2C_FAILURE);
		}

		/*
		 * Our concern is when we have to write only a few bits.
		 * We need to make sure we write the same value to those
		 * bit positions which does not appear in bitpattern.
		 */

		/*
		 * 1) Ignore all bits than the one we are writing
		 * 2) Now 0 the bits we intend to modify in the value
		 * read from the chip, preserving all others.
		 * 3) Now turn all non-writable ( read only/reserved )
		 * bits to 1. The value now should contain:
		 * 1 			in all non-writable bits.
		 * 0 			in the bis(s) we intend to modify.
		 * no change 	in the writable bits we don't modify.
		 * 4) Now OR it with the bits we got before, i.e. after
		 * ignoring all bits other than one we are writing.
		 */

		for (i = 0; i < size; i++) {
			tp->i2c_rbuf[i] &= ~(bitpattern);

			tp->i2c_rbuf[i] |= ~(unitp->writemask);

			tp->i2c_wbuf[i] = tp->i2c_rbuf[i] |
					(tp->i2c_wbuf[i] & bitpattern);
		}

		tp->i2c_rlen = 0;
		tp->i2c_wlen = size;
		tp->i2c_flags = I2C_WR;

		unitp->i2c_status = nct_i2c_transfer(unitp->pcf8574_hdl, tp);

		return (unitp->i2c_status);
}

static int
pcf8574_kstat_update(kstat_t *ksp, int rw)
{
	struct pcf8574_unit *unitp;
	char *kstatp;
	uint8_t value;
	int err = DDI_SUCCESS;
	scsb_fru_status_t	dev_presence;

	unitp = (struct pcf8574_unit *)ksp->ks_private;
	if (unitp->envctrl_kstat == NULL) { /* May be detaching */
		return (err);
	}

	CV_LOCK(EINTR)

	/*
	 * Need to call scsb to find whether device is present.
	 * For I2C devices, the I2C address is used as a FRU ID.
	 */
	if (unitp->pcf8574_type == PCF8574_TYPE_CPUVOLTAGE) {
		dev_presence = FRU_PRESENT;
	} else {
		dev_presence =
			scsb_fru_status((uchar_t)unitp->props.slave_address);
	}

	kstatp = (char *)ksp->ks_data;

	/*
	 * We could have write on the power supply and the fantray
	 * pcf8574 chips. For masking the interrupt on both, or
	 * controlling the fan speed on the fantray. But write
	 * will not be allowed through the kstat interface. For
	 * the present field, call SCSB.
	 */

	if (rw == KSTAT_WRITE) {
		if (unitp->pcf8574_type != PCF8574_TYPE_FANTRAY) {
			err = EACCES;
			goto kstat_exit;
		}
		value = ((envctrl_fantray_t *)kstatp)->fanspeed;
		if (value != PCF8574_FAN_SPEED_LOW &&
			value != PCF8574_FAN_SPEED_HIGH) {
			err = EINVAL;
			goto kstat_exit;
		}

		unitp->i2c_tran->i2c_wbuf[0] = PCF8574_FAN_SPEED(value);

		if (dev_presence == FRU_PRESENT &&
			pcf8574_write_chip(unitp, 1, PCF8574_FANSPEED_BIT)
			!= I2C_SUCCESS) {
			err = EFAULT;
			goto kstat_exit;
		}

	} else {
		/*
		 * First make sure that the FRU exists by checking the SCSB
		 * dev_presence info.  If not present, set the change field,
		 * clear the kstat fields and make sure the kstat *_present
		 * field is set to dev_presence from the SCSB driver.
		 */
		if (dev_presence == FRU_PRESENT &&
				pcf8574_read_chip(unitp, 1) != I2C_SUCCESS) {
			/*
			 * Looks like a real IO error.
			 */
			err = EIO;
			CV_UNLOCK

			return (err);
		}
		if (dev_presence == FRU_PRESENT)
			value = unitp->i2c_tran->i2c_rbuf[0];
		else
			value = 0;

		switch (unitp->pcf8574_type) {
		case PCF8574_TYPE_CPUVOLTAGE: {
			envctrl_cpuvoltage_t *envp =
				(envctrl_cpuvoltage_t *)unitp->envctrl_kstat;
			envp->value = value;
			bcopy((caddr_t)envp, kstatp,
				sizeof (envctrl_cpuvoltage_t));

			break;
		}
		case PCF8574_TYPE_PWRSUPP: {
			envctrl_pwrsupp_t *envp =
				(envctrl_pwrsupp_t *)unitp->envctrl_kstat;

			envp->ps_present = 	dev_presence;
			envp->ps_ok =		PCF8574_PS_FAULT(value);
			envp->temp_ok =		PCF8574_PS_TEMPOK(value);
			envp->psfan_ok =	PCF8574_PS_FANOK(value);
			envp->on_state =	PCF8574_PS_ONOFF(value);
			envp->ps_ver =		PCF8574_PS_TYPE(value);

			bcopy((caddr_t)envp, kstatp,
				sizeof (envctrl_pwrsupp_t));

			break;
		}
		case PCF8574_TYPE_FANTRAY: {
			envctrl_fantray_t *envp =
				(envctrl_fantray_t *)unitp->envctrl_kstat;

			envp->fan_present = dev_presence;
			envp->fan_ver = 	PCF8574_FAN_TYPE(value);
			envp->fan_ok = 		PCF8574_FAN_FAULT(value);
			envp->fanspeed =  	PCF8574_FAN_FANSPD(value);

			bcopy((caddr_t)unitp->envctrl_kstat, kstatp,
				sizeof (envctrl_fantray_t));

			break;
		}

		default:
			break;
		}
	}

kstat_exit:

	CV_UNLOCK

	return (err);
}

static void
pcf8574_delete_kstat(struct pcf8574_unit *unitp)
{
	/*
	 * Depending on the function, deallocate the correct
	 * kernel allocated memory.
	 */
	if (unitp->kstatp != NULL) {
		kstat_delete(unitp->kstatp);
	}

	switch (unitp->pcf8574_type) {
	case PCF8574_TYPE_CPUVOLTAGE: {
		if (unitp->envctrl_kstat != NULL) {
			kmem_free(unitp->envctrl_kstat,
				sizeof (envctrl_cpuvoltage_t));
		}
		break;
	}
	case PCF8574_TYPE_PWRSUPP: {
		if (unitp->envctrl_kstat != NULL) {
			kmem_free(unitp->envctrl_kstat,
				sizeof (envctrl_pwrsupp_t));
		}

		break;
	}
	case PCF8574_TYPE_FANTRAY: {
		if (unitp->envctrl_kstat != NULL) {
			kmem_free(unitp->envctrl_kstat,
				sizeof (envctrl_fantray_t));
		}
		break;
	}
	default:
		break;
	}

	unitp->envctrl_kstat = NULL;
}

static int
pcf8574_read_props(struct pcf8574_unit *unitp)
{
	dev_info_t *dip = unitp->dip;
	int retval = 0, prop_len;
	uint32_t  *prop_value = NULL;
	uint8_t i2c_address;
	char *function;

	/*
	 * read the pcf8574_function property. If this property is not
	 * found, return ERROR. Else, make sure it's either powersupply
	 * or fantray.
	 */

	if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
		"pcf8574_function", &function) != DDI_SUCCESS) {
		dbg_print(CE_WARN, "Couldn't find pcf8574_function property");

		return (DDI_FAILURE);
	}

	if (strcmp(function, "fantray") == 0) {
		unitp->pcf8574_type = PCF8574_TYPE_FANTRAY;
		/*
		 * Will fail the fantray attach if patch - 1.
		 */
		if (nct_p10fan_patch) {
#ifdef DEBUG
		cmn_err(CE_WARN, "nct_p10fan_patch set: will not load "
				"fantary:address %x,%x", unitp->props.i2c_bus,
				unitp->props.slave_address);
#endif
			ddi_prop_free(function);
			return (DDI_FAILURE);
		}
	} else
	if (strcmp(function, "powersupply") == 0) {
		unitp->pcf8574_type = PCF8574_TYPE_PWRSUPP;
	} else {
		dbg_print(CE_WARN, "Neither powersupply nor fantray");
		ddi_prop_free(function);

		return (DDI_FAILURE);
	}

	ddi_prop_free(function);

	retval = ddi_getlongprop(DDI_DEV_T_ANY, dip,
		    DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP,
		    "reg", (caddr_t)&prop_value, &prop_len);
	if (retval == DDI_PROP_SUCCESS) {
		unitp->props.i2c_bus		= (uint16_t)prop_value[0];
		unitp->props.slave_address	= i2c_address =
			(uint8_t)prop_value[1];
		kmem_free(prop_value, prop_len);

		if (i2c_address>>4 == 7)
			unitp->sensor_type = PCF8574A;
		else if (i2c_address>>4 == 4)
			unitp->sensor_type = PCF8574;
		else {
			unitp->sensor_type = PCF8574A;
			dbg_print(CE_WARN, "Not a pcf8574/a device");
		}

	} else {
		unitp->props.i2c_bus		= (uint16_t)-1;
		unitp->props.slave_address	= (uint16_t)-1;
	}

	/*
	 * Get the Property information that the driver will be using
	 * see typedef struct pcf8574_properties_t;
	 */

	unitp->pcf8574_canintr = 0;
	retval = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
			    "interrupts", -1);
	if (retval >= 0) {
		int prop_len, intr_pri = 4;
		unitp->pcf8574_canintr |= PCF8574_INTR_ON;
		if (ddi_getproplen(DDI_DEV_T_ANY, dip,
			DDI_PROP_DONTPASS, "interrupt-priorities",
			&prop_len) == DDI_PROP_NOT_FOUND) {
			retval = ddi_prop_create(DDI_DEV_T_NONE, dip,
				DDI_PROP_CANSLEEP, "interrupt-priorities",
				(caddr_t)&intr_pri, sizeof (int));
#ifdef DEBUG
			if (retval != DDI_PROP_SUCCESS) {
				cmn_err(CE_WARN, "Failed to create interrupt- \
				priorities property, retval %d", retval);
			}
#endif /* DEBUG */
		}
	}

	/*
	 * No channels-in-use property for the fan and powersupplies.
	 */
	unitp->props.num_chans_used = 0;
	if (i2c_address == PCF8574_ADR_CPUVOLTAGE) {
		if (ddi_getproplen(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
			"channels-in-use", &prop_len) == DDI_PROP_SUCCESS) {
			retval = ddi_prop_lookup_byte_array(DDI_DEV_T_ANY,
				dip, DDI_PROP_DONTPASS,
				"channels-in-use",
				(uchar_t **)&unitp->props.channels_in_use,
				&unitp->props.num_chans_used);
			if (retval != DDI_PROP_SUCCESS) {
				unitp->props.num_chans_used = 0;
			} else {
				unitp->props.num_chans_used /=
					sizeof (pcf8574_channel_t);
			}
		}
	}

	return (DDI_PROP_SUCCESS);
}

/*
 * callback function to register with the SCSB driver in order to be
 * informed about changes in device instance presence.
 */
/*ARGSUSED*/
void
pcf8574_callback(void *softstate, scsb_fru_event_t cb_event,
		scsb_fru_status_t dev_presence)
{
	struct pcf8574_unit *unitp = (struct pcf8574_unit *)softstate;
#ifdef DEBUG
		if (pcf8574_debug & 0x00800001)
			cmn_err(CE_NOTE, "pcf8574_callback(unitp,%d,%d)",
					(int)cb_event, (int)dev_presence);
#endif /* DEBUG */

	switch (unitp->pcf8574_type) {
		case PCF8574_TYPE_CPUVOLTAGE: {
			/*
			 * This Unit is not Field Replacable and will not
			 * generate any events at the SCB.
			 */
			break;
		}
		case PCF8574_TYPE_PWRSUPP: {
			envctrl_pwrsupp_t *envp;

			envp = (envctrl_pwrsupp_t *)unitp->envctrl_kstat;
			if (dev_presence == FRU_NOT_PRESENT) {
				envp->ps_ok = 0;
				envp->temp_ok = 0;
				envp->psfan_ok = 0;
				envp->on_state = 0;
				envp->ps_ver = 0;
			} else
			if (dev_presence == FRU_PRESENT &&
					envp->ps_present == FRU_NOT_PRESENT) {
				pcf8574_init_chip(unitp, 0);
			}
			envp->ps_present = dev_presence;
			unitp->poll_event = POLLIN;
			pollwakeup(&unitp->poll, POLLIN);
			break;
		}
		case PCF8574_TYPE_FANTRAY: {
			envctrl_fantray_t *envp;

			envp = (envctrl_fantray_t *)unitp->envctrl_kstat;

			if (dev_presence == FRU_NOT_PRESENT) {
				envp->fan_ok = 0;
				envp->fanspeed =  PCF8574_FAN_SPEED60;
				envp->fan_ver = 0;
			} else
			if (dev_presence == FRU_PRESENT &&
					envp->fan_present == FRU_NOT_PRESENT) {
				pcf8574_init_chip(unitp, 0);
			}
			envp->fan_present = dev_presence;
			unitp->poll_event = POLLIN;
			pollwakeup(&unitp->poll, POLLIN);
			break;
		}
	}
}

/*
 * Initializes the chip after attach or after being inserted.
 * intron = 0 => disable interrupt.
 * intron = 1 => read register, enable interrupt if no fault.
 */

static int
pcf8574_init_chip(struct pcf8574_unit *unitp, int intron)
{
	int ret = I2C_SUCCESS;
	i2c_transfer_t *tp = unitp->i2c_tran;
	uint8_t value = 0;
	boolean_t device_faulty = B_FALSE; /* true is faulty */

	if (unitp->pcf8574_type != PCF8574_TYPE_PWRSUPP &&
		unitp->pcf8574_type != PCF8574_TYPE_FANTRAY) {
		return (ret);
	}
	switch (unitp->pcf8574_type) {
	case PCF8574_TYPE_PWRSUPP:
		tp->i2c_wbuf[0] = PCF8574_PS_DEFAULT;

		break;
	case PCF8574_TYPE_FANTRAY:
			tp->i2c_wbuf[0] = PCF8574_FAN_DEFAULT;

		break;
	default:
		break;
	}

	/*
	 * First, read the device. If the device is faulty, it does
	 * not make sense to enable the interrupt, so in this case
	 * keep interrupt maskked inspite of what "intron" says.
	 */

	tp->i2c_wlen = 0;
	tp->i2c_rlen = 1;
	tp->i2c_flags = I2C_RD;

	unitp->i2c_status = ret = nct_i2c_transfer(unitp->pcf8574_hdl, tp);

	if (ret != I2C_SUCCESS) {
		return (ret);
	}

	value = tp->i2c_rbuf[0];

	switch (unitp->pcf8574_type) {
	case PCF8574_TYPE_PWRSUPP:
	{
		envctrl_pwrsupp_t *envp =
			(envctrl_pwrsupp_t *)unitp->envctrl_kstat;

		envp->ps_ok    = PCF8574_PS_FAULT(value);
		envp->temp_ok  = PCF8574_PS_TEMPOK(value);
		envp->psfan_ok = PCF8574_PS_FANOK(value);
		envp->on_state = PCF8574_PS_ONOFF(value);
		envp->ps_ver   = PCF8574_PS_TYPE(value);

		if (envp->ps_ok || envp->temp_ok ||
			envp->psfan_ok || envp->on_state)
			device_faulty = B_TRUE;

		break;
	}
	case PCF8574_TYPE_FANTRAY:
	{
		envctrl_fantray_t *envp =
			(envctrl_fantray_t *)unitp->envctrl_kstat;

		envp->fan_ver  = PCF8574_FAN_TYPE(value);
		envp->fan_ok   = PCF8574_FAN_FAULT(value);
		envp->fanspeed = PCF8574_FAN_FANSPD(value);

		if (!envp->fan_ok)
			device_faulty = B_TRUE; /* remember, 0 is faulty */

		break;
	}
	default:
		break;
	}
	/*
	 * Mask interrupt, if intron = 0.
	 */
	if (!intron || device_faulty == B_TRUE) {
		tp->i2c_wbuf[0] |= PCF8574_INTRMASK_BIT;
	}

	tp->i2c_wlen = 1;
	tp->i2c_rlen = 0;
	tp->i2c_flags = I2C_WR;

	unitp->i2c_status = nct_i2c_transfer(unitp->pcf8574_hdl, tp);

	return (unitp->i2c_status);
}