root/usr/src/uts/sun4u/io/i2c/clients/adm1031.c
/*
 * 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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


#include <sys/stat.h>
#include <sys/file.h>
#include <sys/uio.h>
#include <sys/modctl.h>
#include <sys/open.h>
#include <sys/types.h>
#include <sys/kmem.h>
#include <sys/systm.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/conf.h>
#include <sys/mode.h>
#include <sys/promif.h>
#include <sys/note.h>
#include <sys/i2c/misc/i2c_svc.h>
#include <sys/i2c/clients/i2c_client.h>
#include <sys/i2c/clients/adm1031.h>
#include <sys/i2c/clients/adm1031_impl.h>

/*
 * ADM1031 is an Intelligent Temperature Monitor and Dual PWM Fan Controller.
 * The functions supported by the driver are:
 *      Reading sensed temperatures.
 *      Setting temperature limits which control fan speeds.
 *      Reading fan speeds.
 *      Setting fan outputs.
 *      Reading internal registers.
 *      Setting internal registers.
 */

/*
 * A pointer to an int16_t is expected as an ioctl argument for all temperature
 * related commands and a pointer to a uint8_t is expected for all other
 * commands.  If the  parameter is to be read the value is copied into it and
 * if it is to be written, the integer referred to should have the appropriate
 * value.
 *
 * For all temperature related commands, a temperature minor node should be
 * passed as the argument to open(2) and correspondingly, a fan minor node
 * should be used for all fan related commands. Commands which do not fall in
 * either of the two categories are control commands and involve
 * reading/writing to the internal registers of the device or switching from
 * automatic monitoring mode to manual mode and vice-versa. A control minor
 * node is created by the driver which has to be used for control commands.
 *
 * Fan Speed in RPM = (frequency * 60)/Count * N, where Count is the value
 * received in the fan speed register and N is Speed Range.
 */

/*
 * cb ops
 */
static int adm1031_open(dev_t *, int, int, cred_t *);
static int adm1031_close(dev_t, int, int, cred_t *);
static int adm1031_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);

/*
 * dev ops
 */
static int adm1031_s_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int adm1031_s_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);

static struct cb_ops adm1031_cb_ops = {
        adm1031_open,                   /* open */
        adm1031_close,                  /* close */
        nodev,                          /* strategy */
        nodev,                          /* print */
        nodev,                          /* dump */
        nodev,                          /* read */
        nodev,                          /* write */
        adm1031_ioctl,                  /* ioctl */
        nodev,                          /* devmap */
        nodev,                          /* mmap */
        nodev,                          /* segmap */
        nochpoll,                       /* poll */
        ddi_prop_op,                    /* cb_prop_op */
        NULL,                           /* streamtab */
        D_NEW | D_MP | D_HOTPLUG,       /* Driver compatibility flag */
};

static struct dev_ops adm1031_dev_ops = {
        DEVO_REV,
        0,
        ddi_no_info,
        nulldev,
        nulldev,
        adm1031_s_attach,
        adm1031_s_detach,
        nodev,
        &adm1031_cb_ops,
        NULL,
        NULL,
        ddi_quiesce_not_supported,      /* devo_quiesce */
};

static uint8_t adm1031_control_regs[] = {
        0x00,
        ADM1031_STAT_1_REG,
        ADM1031_STAT_2_REG,
        ADM1031_DEVICE_ID_REG,
        ADM1031_CONFIG_REG_1,
        ADM1031_CONFIG_REG_2,
        ADM1031_FAN_CHAR_1_REG,
        ADM1031_FAN_CHAR_2_REG,
        ADM1031_FAN_SPEED_CONFIG_REG,
        ADM1031_FAN_HIGH_LIMIT_1_REG,
        ADM1031_FAN_HIGH_LIMIT_2_REG,
        ADM1031_LOCAL_TEMP_RANGE_REG,
        ADM1031_REMOTE_TEMP_RANGE_1_REG,
        ADM1031_REMOTE_TEMP_RANGE_2_REG,
        ADM1031_EXTD_TEMP_RESL_REG,
        ADM1031_LOCAL_TEMP_OFFSET_REG,
        ADM1031_REMOTE_TEMP_OFFSET_1_REG,
        ADM1031_REMOTE_TEMP_OFFSET_2_REG,
        ADM1031_LOCAL_TEMP_HIGH_LIMIT_REG,
        ADM1031_REMOTE_TEMP_HIGH_LIMIT_1_REG,
        ADM1031_REMOTE_TEMP_HIGH_LIMIT_2_REG,
        ADM1031_LOCAL_TEMP_LOW_LIMIT_REG,
        ADM1031_REMOTE_TEMP_LOW_LIMIT_1_REG,
        ADM1031_REMOTE_TEMP_LOW_LIMIT_2_REG,
        ADM1031_LOCAL_TEMP_THERM_LIMIT_REG,
        ADM1031_REMOTE_TEMP_THERM_LIMIT_1_REG,
        ADM1031_REMOTE_TEMP_THERM_LIMIT_2_REG
};

static  minor_info      temperatures[ADM1031_TEMP_CHANS] = {
        {"local", ADM1031_LOCAL_TEMP_INST_REG }, /* Local Temperature */
        {"remote_1", ADM1031_REMOTE_TEMP_INST_REG_1 }, /* Remote 1 */
        {"remote_2", ADM1031_REMOTE_TEMP_INST_REG_2 }  /* Remote 2 */
};

static  minor_info      fans[ADM1031_FAN_SPEED_CHANS] = {
        {"fan_1", ADM1031_FAN_SPEED_INST_REG_1},
        {"fan_2", ADM1031_FAN_SPEED_INST_REG_2}
};

static struct modldrv adm1031_modldrv = {
        &mod_driverops,         /* type of module - driver */
        "adm1031 device driver",
        &adm1031_dev_ops,
};

static struct modlinkage adm1031_modlinkage = {
        MODREV_1,
        &adm1031_modldrv,
        0
};

static void *adm1031_soft_statep;
int     adm1031_pil = ADM1031_PIL;


int
_init(void)
{
        int    err;

        err = mod_install(&adm1031_modlinkage);
        if (err == 0) {
                (void) ddi_soft_state_init(&adm1031_soft_statep,
                    sizeof (adm1031_unit_t), 1);
        }
        return (err);
}

int
_fini(void)
{
        int    err;

        err = mod_remove(&adm1031_modlinkage);
        if (err == 0) {
                ddi_soft_state_fini(&adm1031_soft_statep);
        }
        return (err);
}

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

static int
adm1031_resume(dev_info_t *dip)
{
        int             instance = ddi_get_instance(dip);
        adm1031_unit_t  *admp;
        int             err = DDI_SUCCESS;

        admp = (adm1031_unit_t *)
            ddi_get_soft_state(adm1031_soft_statep, instance);

        if (admp == NULL) {
                return (DDI_FAILURE);
        }

        /*
         * Restore registers to state existing before cpr
         */
        admp->adm1031_transfer->i2c_flags = I2C_WR;
        admp->adm1031_transfer->i2c_wlen = 2;
        admp->adm1031_transfer->i2c_rlen = 0;

        admp->adm1031_transfer->i2c_wbuf[0] = ADM1031_CONFIG_REG_1;
        admp->adm1031_transfer->i2c_wbuf[1] =
            admp->adm1031_cpr_state.config_reg_1;
        if (i2c_transfer(admp->adm1031_hdl, admp->adm1031_transfer) !=
            DDI_SUCCESS) {
                err = DDI_FAILURE;
                goto done;
        }
        admp->adm1031_transfer->i2c_wbuf[0] = ADM1031_CONFIG_REG_2;
        admp->adm1031_transfer->i2c_wbuf[1] =
            admp->adm1031_cpr_state.config_reg_2;
        if (i2c_transfer(admp->adm1031_hdl, admp->adm1031_transfer) !=
            DDI_SUCCESS) {
                err = DDI_FAILURE;
                goto done;
        }
        admp->adm1031_transfer->i2c_wbuf[0] = ADM1031_FAN_SPEED_CONFIG_REG;
        admp->adm1031_transfer->i2c_wbuf[1] =
            admp->adm1031_cpr_state.fan_speed_reg;
        if (i2c_transfer(admp->adm1031_hdl, admp->adm1031_transfer) !=
            DDI_SUCCESS) {
                err = DDI_FAILURE;
                goto done;
        }

        /*
         * Clear busy flag so that transactions may continue
         */
        mutex_enter(&admp->adm1031_mutex);
        admp->adm1031_flags = admp->adm1031_flags & ~ADM1031_BUSYFLAG;
        cv_signal(&admp->adm1031_cv);
        mutex_exit(&admp->adm1031_mutex);

done:
        if (err != DDI_SUCCESS) {
                cmn_err(CE_WARN, "%s:%d Registers not restored correctly",
                    admp->adm1031_name, instance);
        }
        return (err);
}

static void
adm1031_detach(dev_info_t *dip)
{
        adm1031_unit_t  *admp;
        int             instance = ddi_get_instance(dip);

        admp = ddi_get_soft_state(adm1031_soft_statep, instance);

        if (admp->adm1031_flags & ADM1031_REGFLAG) {
                i2c_client_unregister(admp->adm1031_hdl);
        }
        if (admp->adm1031_flags & ADM1031_TBUFFLAG) {
                i2c_transfer_free(admp->adm1031_hdl, admp->adm1031_transfer);
        }
        if (admp->adm1031_flags & ADM1031_INTRFLAG) {
                ddi_remove_intr(dip, 0, admp->adm1031_icookie);
                cv_destroy(&admp->adm1031_icv);
                mutex_destroy(&admp->adm1031_imutex);
        }

        (void) ddi_prop_remove_all(dip);
        ddi_remove_minor_node(dip, NULL);
        cv_destroy(&admp->adm1031_cv);
        mutex_destroy(&admp->adm1031_mutex);
        ddi_soft_state_free(adm1031_soft_statep, instance);

}

static uint_t
adm1031_intr(caddr_t arg)
{
        adm1031_unit_t  *admp = (adm1031_unit_t *)arg;


        if (admp->adm1031_cvwaiting == 0)
                return (DDI_INTR_CLAIMED);

        mutex_enter(&admp->adm1031_imutex);
        cv_broadcast(&admp->adm1031_icv);
        admp->adm1031_cvwaiting = 0;
        mutex_exit(&admp->adm1031_imutex);

        return (DDI_INTR_CLAIMED);
}

static int
adm1031_attach(dev_info_t *dip)
{
        adm1031_unit_t          *admp;
        int                     instance = ddi_get_instance(dip);
        minor_t                 minor;
        int                     i;
        char                    *minor_name;
        int                     err = 0;

        if (ddi_soft_state_zalloc(adm1031_soft_statep, instance) != 0) {
                cmn_err(CE_WARN, "%s:%d failed to zalloc softstate",
                    ddi_get_name(dip), instance);
                return (DDI_FAILURE);
        }
        admp = ddi_get_soft_state(adm1031_soft_statep, instance);
        if (admp == NULL) {
                return (DDI_FAILURE);
        }
        admp->adm1031_dip = dip;
        mutex_init(&admp->adm1031_mutex, NULL, MUTEX_DRIVER, NULL);
        cv_init(&admp->adm1031_cv, NULL, CV_DRIVER, NULL);

        (void) snprintf(admp->adm1031_name, sizeof (admp->adm1031_name),
            "%s_%d", ddi_driver_name(dip), instance);

        /*
         * Create minor node for all temperature functions.
         */
        for (i = 0; i < ADM1031_TEMP_CHANS; i++) {

                minor_name = temperatures[i].minor_name;
                minor = ADM1031_INST_TO_MINOR(instance) |
                    ADM1031_FCN_TO_MINOR(ADM1031_TEMPERATURES) |
                    ADM1031_FCNINST_TO_MINOR(i);

                if (ddi_create_minor_node(dip, minor_name, S_IFCHR, minor,
                    ADM1031_NODE_TYPE, 0) == DDI_FAILURE) {
                        cmn_err(CE_WARN, "%s:%d ddi_create_minor_node failed",
                            admp->adm1031_name, instance);
                        adm1031_detach(dip);
                        return (DDI_FAILURE);
                }
        }

        /*
         * Create minor node for all fan functions.
         */
        for (i = 0; i < ADM1031_FAN_SPEED_CHANS; i++) {

                minor_name = fans[i].minor_name;
                minor = ADM1031_INST_TO_MINOR(instance) |
                    ADM1031_FCN_TO_MINOR(ADM1031_FANS) |
                    ADM1031_FCNINST_TO_MINOR(i);

                if (ddi_create_minor_node(dip, minor_name, S_IFCHR, minor,
                    ADM1031_NODE_TYPE, 0) == DDI_FAILURE) {
                        cmn_err(CE_WARN, "%s:%d ddi_create_minor_node failed",
                            admp->adm1031_name, instance);
                        adm1031_detach(dip);
                        return (DDI_FAILURE);
                }
        }

        /*
         * Create minor node for all control functions.
         */
        minor = ADM1031_INST_TO_MINOR(instance) |
            ADM1031_FCN_TO_MINOR(ADM1031_CONTROL) |
            ADM1031_FCNINST_TO_MINOR(0);

        if (ddi_create_minor_node(dip, "control", S_IFCHR, minor,
            ADM1031_NODE_TYPE, 0) == DDI_FAILURE) {
                cmn_err(CE_WARN, "%s:%d ddi_create_minor_node failed",
                    admp->adm1031_name, instance);
                adm1031_detach(dip);
                return (DDI_FAILURE);
        }

        /*
         * preallocate a single buffer for all reads and writes
         */
        if (i2c_transfer_alloc(admp->adm1031_hdl, &admp->adm1031_transfer,
            ADM1031_MAX_XFER, ADM1031_MAX_XFER, I2C_SLEEP) != I2C_SUCCESS) {
                cmn_err(CE_WARN, "%s:%d i2c_transfer_alloc failed",
                    admp->adm1031_name, instance);
                adm1031_detach(dip);
                return (DDI_FAILURE);
        }
        admp->adm1031_flags |= ADM1031_TBUFFLAG;
        admp->adm1031_transfer->i2c_version = I2C_XFER_REV;

        if (i2c_client_register(dip, &admp->adm1031_hdl) != I2C_SUCCESS) {
                cmn_err(CE_WARN, "%s:%d i2c_client_register failed",
                    admp->adm1031_name, instance);
                adm1031_detach(dip);
                return (DDI_FAILURE);
        }
        admp->adm1031_flags |= ADM1031_REGFLAG;

        if (ddi_prop_exists(DDI_DEV_T_ANY, dip,
            DDI_PROP_NOTPROM | DDI_PROP_DONTPASS,
            "interrupt-priorities") != 1) {
                (void) ddi_prop_create(DDI_DEV_T_NONE, dip,
                    DDI_PROP_CANSLEEP, "interrupt-priorities",
                    (void *)&adm1031_pil, sizeof (adm1031_pil));
        }
        err = ddi_get_iblock_cookie(dip, 0, &admp->adm1031_icookie);
        if (err == DDI_SUCCESS) {
                mutex_init(&admp->adm1031_imutex, NULL, MUTEX_DRIVER,
                    (void *)admp->adm1031_icookie);
                cv_init(&admp->adm1031_icv, NULL, CV_DRIVER, NULL);
                if (ddi_add_intr(dip, 0, NULL, NULL, adm1031_intr,
                    (caddr_t)admp) == DDI_SUCCESS) {
                        admp->adm1031_flags |= ADM1031_INTRFLAG;
                } else {
                        cmn_err(CE_WARN, "%s:%d failed to add interrupt",
                            admp->adm1031_name, instance);
                }
        }

        /*
         * The system comes up in Automatic Monitor Mode.
         */
        admp->adm1031_flags |= ADM1031_AUTOFLAG;

        return (DDI_SUCCESS);
}

static int
adm1031_s_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
        switch (cmd) {
        case DDI_ATTACH:
                return (adm1031_attach(dip));
        case DDI_RESUME:
                return (adm1031_resume(dip));
        default:
                return (DDI_FAILURE);
        }
}

static int
adm1031_suspend(dev_info_t *dip)
{
        adm1031_unit_t  *admp;
        int             instance = ddi_get_instance(dip);
        int             err = DDI_SUCCESS;

        admp = ddi_get_soft_state(adm1031_soft_statep, instance);

        /*
         * Set the busy flag so that future transactions block
         * until resume.
         */
        mutex_enter(&admp->adm1031_mutex);
        while (admp->adm1031_flags & ADM1031_BUSYFLAG) {
                if (cv_wait_sig(&admp->adm1031_cv,
                    &admp->adm1031_mutex) <= 0) {
                        mutex_exit(&admp->adm1031_mutex);
                        return (DDI_FAILURE);
                }
        }
        admp->adm1031_flags |= ADM1031_BUSYFLAG;
        mutex_exit(&admp->adm1031_mutex);

        /*
         * Save the state of the threshold registers.
         */
        admp->adm1031_transfer->i2c_flags = I2C_WR_RD;
        admp->adm1031_transfer->i2c_wlen = 1;
        admp->adm1031_transfer->i2c_rlen = 1;

        admp->adm1031_transfer->i2c_wbuf[0] = ADM1031_CONFIG_REG_1;
        if (i2c_transfer(admp->adm1031_hdl, admp->adm1031_transfer) !=
            DDI_SUCCESS) {
                err = DDI_FAILURE;
                goto done;
        }
        admp->adm1031_cpr_state.config_reg_1 =
            admp->adm1031_transfer->i2c_rbuf[0];

        admp->adm1031_transfer->i2c_wbuf[0] = ADM1031_CONFIG_REG_2;
        if (i2c_transfer(admp->adm1031_hdl, admp->adm1031_transfer) !=
            DDI_SUCCESS) {
                err = DDI_FAILURE;
                goto done;
        }
        admp->adm1031_cpr_state.config_reg_2 =
            admp->adm1031_transfer->i2c_rbuf[0];

        admp->adm1031_transfer->i2c_wbuf[0] = ADM1031_FAN_SPEED_CONFIG_REG;
        if (i2c_transfer(admp->adm1031_hdl, admp->adm1031_transfer) !=
            DDI_SUCCESS) {
                err = DDI_FAILURE;
                goto done;
        }
        admp->adm1031_cpr_state.fan_speed_reg =
            admp->adm1031_transfer->i2c_rbuf[0];
done:
        if (err != DDI_SUCCESS) {
                mutex_enter(&admp->adm1031_mutex);
                admp->adm1031_flags = admp->adm1031_flags & ~ADM1031_BUSYFLAG;
                cv_broadcast(&admp->adm1031_cv);
                mutex_exit(&admp->adm1031_mutex);
                cmn_err(CE_WARN, "%s:%d Suspend failed,\
                    unable to save registers", admp->adm1031_name, instance);
        }
        return (err);

}

static int
adm1031_s_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
        switch (cmd) {
        case DDI_DETACH:
                adm1031_detach(dip);
                return (DDI_SUCCESS);
        case DDI_SUSPEND:
                return (adm1031_suspend(dip));
        default:
                return (DDI_FAILURE);
        }
}

static int
adm1031_open(dev_t *devp, int flags, int otyp, cred_t *credp)
{
        int                     instance;
        adm1031_unit_t          *admp;
        int                     err = EBUSY;

        /* must be root to access this device */
        if (drv_priv(credp) != 0) {
                return (EPERM);
        }

        /*
         * Make sure the open is for the right file type
         */
        if (otyp != OTYP_CHR) {
                return (EINVAL);
        }
        instance = ADM1031_MINOR_TO_INST(getminor(*devp));
        admp = (adm1031_unit_t *)
            ddi_get_soft_state(adm1031_soft_statep, instance);
        if (admp == NULL) {
                return (ENXIO);
        }

        /*
         * Enforce exclusive access if required.
         */
        mutex_enter(&admp->adm1031_mutex);
        if (flags & FEXCL) {
                if (admp->adm1031_oflag == 0) {
                        admp->adm1031_oflag = FEXCL;
                        err = 0;
                }
        } else if (admp->adm1031_oflag != FEXCL) {
                admp->adm1031_oflag = FOPEN;
                err = 0;
        }
        mutex_exit(&admp->adm1031_mutex);
        return (err);
}

static int
adm1031_close(dev_t dev, int flags, int otyp, cred_t *credp)
{
        int             instance;
        adm1031_unit_t  *admp;

        _NOTE(ARGUNUSED(flags, otyp, credp))

        instance = ADM1031_MINOR_TO_INST(getminor(dev));
        admp = (adm1031_unit_t *)
            ddi_get_soft_state(adm1031_soft_statep, instance);
        if (admp == NULL) {
                return (ENXIO);
        }

        mutex_enter(&admp->adm1031_mutex);
        admp->adm1031_oflag = 0;
        mutex_exit(&admp->adm1031_mutex);
        return (0);
}

static int
adm1031_s_ioctl(dev_t dev, int cmd, intptr_t arg, int mode)
{
        adm1031_unit_t  *admp;
        int             err = 0, cmd_c = 0;
        uint8_t         speed = 0, f_set = 0, temp = 0, write_value = 0;
        int16_t         temp16 = 0, write_value16 = 0;
        minor_t         minor = getminor(dev);
        int             instance = ADM1031_MINOR_TO_INST(minor);
        int             fcn = ADM1031_MINOR_TO_FCN(minor);
        int             fcn_inst = ADM1031_MINOR_TO_FCNINST(minor);

        admp = (adm1031_unit_t *)
            ddi_get_soft_state(adm1031_soft_statep, instance);

        /*
         * We serialize here and block pending transactions.
         */
        mutex_enter(&admp->adm1031_mutex);
        while (admp->adm1031_flags & ADM1031_BUSYFLAG) {
                if (cv_wait_sig(&admp->adm1031_cv,
                    &admp->adm1031_mutex) <= 0) {
                        mutex_exit(&admp->adm1031_mutex);
                        return (EINTR);
                }
        }
        admp->adm1031_flags |= ADM1031_BUSYFLAG;
        mutex_exit(&admp->adm1031_mutex);

        switch (fcn) {
        case ADM1031_TEMPERATURES:
                if (cmd == I2C_GET_TEMPERATURE) {
                        admp->adm1031_transfer->i2c_wbuf[0] =
                            temperatures[fcn_inst].reg;
                        goto copyout;
                } else {
                        cmd = cmd - ADM1031_PVT_BASE_IOCTL;
                        cmd_c = ADM1031_CHECK_FOR_WRITES(cmd) ?
                            (cmd - ADM1031_WRITE_COMMAND_BASE) + fcn_inst :
                            cmd + fcn_inst;
                        if (!ADM1031_CHECK_TEMPERATURE_CMD(cmd_c)) {
                                err = EINVAL;
                                goto done;
                        }
                        admp->adm1031_transfer->i2c_wbuf[0] =
                            adm1031_control_regs[cmd_c];
                        if (ADM1031_CHECK_FOR_WRITES(cmd))
                                goto writes;
                        else
                                goto copyout;
                }
        case ADM1031_FANS:
                if (cmd == I2C_GET_FAN_SPEED) {
                        admp->adm1031_transfer->i2c_wbuf[0] =
                            fans[fcn_inst].reg;
                        goto copyout;
                } else if (cmd == ADM1031_GET_FAN_CONFIG) {
                        admp->adm1031_transfer->i2c_wbuf[0] =
                            ADM1031_FAN_SPEED_CONFIG_REG;
                        goto copyout;
                } else if (cmd == I2C_SET_FAN_SPEED) {
                        if (ddi_copyin((void *)arg, &write_value,
                            sizeof (write_value), mode) != DDI_SUCCESS) {

                                err = EFAULT;
                                goto done;
                        }
                        speed = write_value;
                        if ((admp->adm1031_flags & ADM1031_AUTOFLAG)) {
                                err = EBUSY;
                                goto done;
                        }
                        if (ADM1031_CHECK_INVALID_SPEED(speed)) {
                                err = EINVAL;
                                goto done;
                        }
                        admp->adm1031_transfer->i2c_wbuf[0] =
                            ADM1031_FAN_SPEED_CONFIG_REG;
                        admp->adm1031_transfer->i2c_flags = I2C_WR_RD;
                        admp->adm1031_transfer->i2c_wlen = 1;
                        admp->adm1031_transfer->i2c_rlen = 1;
                        if (i2c_transfer(admp->adm1031_hdl,
                            admp->adm1031_transfer) != I2C_SUCCESS) {
                                err = EIO;
                                goto done;
                        }
                        f_set = admp->adm1031_transfer->i2c_rbuf[0];
                        f_set = (fcn_inst == 0) ? (MLSN(f_set) | speed):
                            (MMSN(f_set) | (speed << 4));

                        admp->adm1031_transfer->i2c_wbuf[1] = f_set;
                        admp->adm1031_transfer->i2c_flags = I2C_WR;
                        admp->adm1031_transfer->i2c_wlen = 2;
                        if (i2c_transfer(admp->adm1031_hdl,
                            admp->adm1031_transfer) != I2C_SUCCESS) {
                                err = EIO;
                        }
                        goto done;
                }
                cmd = cmd - ADM1031_PVT_BASE_IOCTL;
                cmd_c = ADM1031_CHECK_FOR_WRITES(cmd) ?
                    (cmd - ADM1031_WRITE_COMMAND_BASE) + fcn_inst :
                    cmd + fcn_inst;
                if (!ADM1031_CHECK_FAN_CMD(cmd_c)) {
                        err = EINVAL;
                        goto done;
                }
                admp->adm1031_transfer->i2c_wbuf[0] =
                    adm1031_control_regs[cmd_c];
                if (ADM1031_CHECK_FOR_WRITES(cmd))
                        goto writes;
                else
                        goto copyout;
        case ADM1031_CONTROL:

                /*
                 * Read the primary configuration register in advance.
                 */
                admp->adm1031_transfer->i2c_wbuf[0] =
                    ADM1031_CONFIG_REG_1;
                admp->adm1031_transfer->i2c_flags = I2C_WR_RD;
                admp->adm1031_transfer->i2c_wlen = 1;
                admp->adm1031_transfer->i2c_rlen = 1;
                if (i2c_transfer(admp->adm1031_hdl,
                    admp->adm1031_transfer) != I2C_SUCCESS) {
                        err = EIO;
                        goto done;
                }
                switch (cmd) {
                case ADM1031_GET_MONITOR_MODE:
                        temp = ADM1031_AUTOFLAG &
                            admp->adm1031_transfer->i2c_rbuf[0];
                        temp = temp >> 7;
                        if (ddi_copyout((void *)&temp, (void *)arg,
                            sizeof (temp), mode) != DDI_SUCCESS) {
                                err = EFAULT;
                        }
                        goto done;
                case ADM1031_SET_MONITOR_MODE:
                        if (ddi_copyin((void *)arg, &write_value,
                            sizeof (write_value), mode) != DDI_SUCCESS) {
                                err = EFAULT;
                                goto done;
                        }
                        if (write_value == ADM1031_AUTO_MODE) {
                                temp = ADM1031_AUTOFLAG |
                                    admp->adm1031_transfer->i2c_rbuf[0];
                                admp->adm1031_flags |= ADM1031_AUTOFLAG;
                        } else if (write_value == ADM1031_MANUAL_MODE) {
                                temp = admp->adm1031_transfer->i2c_rbuf[0] &
                                    (~ADM1031_AUTOFLAG);
                                admp->adm1031_flags &= ~ADM1031_AUTOFLAG;
                        } else {
                                err = EINVAL;
                                goto done;
                        }
                        admp->adm1031_transfer->i2c_wbuf[1] = temp;
                        admp->adm1031_transfer->i2c_flags = I2C_WR;
                        admp->adm1031_transfer->i2c_wlen = 2;
                        if (i2c_transfer(admp->adm1031_hdl,
                            admp->adm1031_transfer) != I2C_SUCCESS) {
                                err = EIO;
                        }
                        goto done;
                default:
                        goto control;
                }
        default:
                err = EINVAL;
                goto done;
        }

control:
        cmd = cmd - ADM1031_PVT_BASE_IOCTL;

        if (ADM1031_CHECK_FOR_WRITES(cmd)) {
                cmd_c = (cmd - ADM1031_WRITE_COMMAND_BASE) + fcn_inst;
                admp->adm1031_transfer->i2c_wbuf[0] =
                    adm1031_control_regs[cmd_c];

                goto writes;
        }
        cmd_c = cmd  + fcn_inst;
        admp->adm1031_transfer->i2c_wbuf[0] = adm1031_control_regs[cmd_c];
        goto copyout;

writes:
        if (fcn == ADM1031_TEMPERATURES) {
                if (ddi_copyin((void *)arg, &write_value16,
                    sizeof (write_value16), mode) != DDI_SUCCESS) {

                        err = EFAULT;
                        goto done;
                }
                write_value = (uint8_t)((int8_t)(write_value16));
        } else {
                if (ddi_copyin((void *)arg, &write_value,
                    sizeof (write_value), mode) != DDI_SUCCESS) {

                        err = EFAULT;
                        goto done;
                }
        }
        admp->adm1031_transfer->i2c_flags = I2C_WR;
        admp->adm1031_transfer->i2c_wlen = 2;
        admp->adm1031_transfer->i2c_rlen = 0;
        admp->adm1031_transfer->i2c_wbuf[1] = write_value;
        if (i2c_transfer(admp->adm1031_hdl, admp->adm1031_transfer) !=
            I2C_SUCCESS) {

                err = EIO;
        }
        goto done;

copyout:
        admp->adm1031_transfer->i2c_flags = I2C_WR_RD;
        admp->adm1031_transfer->i2c_wlen = 1;
        admp->adm1031_transfer->i2c_rlen = 1;
        if (i2c_transfer(admp->adm1031_hdl, admp->adm1031_transfer) !=
            I2C_SUCCESS) {

                err = EIO;
                goto done;
        }
        temp = admp->adm1031_transfer->i2c_rbuf[0];
        if (fcn == ADM1031_TEMPERATURES) {
                /*
                 * Workaround for bug in ADM1031 which reports -128 (0x80)
                 * when the temperature transitions from 0C to -1C.
                 * All other -ve temperatures are not affected. We map
                 * 0x80 to 0xFF(-1) since we don't ever expect to see -128C on a
                 * sensor.
                 */
                if (temp == 0x80) {
                        temp = 0xFF;
                }
                temp16 = (int16_t)((int8_t)temp);
                if (ddi_copyout((void *)&temp16, (void *)arg, sizeof (temp16),
                    mode) != DDI_SUCCESS)
                        err = EFAULT;
        } else {
                if (ddi_copyout((void *)&temp, (void *)arg, sizeof (temp),
                    mode) != DDI_SUCCESS)
                        err = EFAULT;
        }

done:
        mutex_enter(&admp->adm1031_mutex);
        admp->adm1031_flags = admp->adm1031_flags & (~ADM1031_BUSYFLAG);
        cv_signal(&admp->adm1031_cv);
        mutex_exit(&admp->adm1031_mutex);
        return (err);
}

/*
 * The interrupt ioctl is a private handshake between the user and the driver
 * and is a mechanism to asynchronously inform the user of a system event such
 * as a fan fault or a temperature limit being exceeded.
 *
 * Step 1):
 *      User(or environmental monitoring software) calls the ioctl routine
 *      which blocks as it waits on a condition. The open(2) call has to be
 *      called with the _control minor node. The ioctl routine requires
 *      ADM1031_INTERRUPT_WAIT as the command and a pointer to an array of
 *      uint8_t as the third argument.
 * Step 2):
 *      A system event occurs which unblocks the ioctl and returns the call
 *      to the user.
 * Step 3):
 *      User reads the contents of the array (which actually contains the values
 *      of the devices' status registers) to determine the exact nature of the
 *      event.
 */
static int
adm1031_i_ioctl(dev_t dev, int cmd, intptr_t arg, int mode)
{
        _NOTE(ARGUNUSED(cmd))
        adm1031_unit_t  *admp;
        uint8_t         i = 0;
        minor_t         minor = getminor(dev);
        int             fcn = ADM1031_MINOR_TO_FCN(minor);
        int             instance = ADM1031_MINOR_TO_INST(minor);
        int             err = 0;
        uint8_t         temp[2];
        uint8_t         temp1;


        if (fcn != ADM1031_CONTROL)
                return (EINVAL);

        admp = (adm1031_unit_t *)
            ddi_get_soft_state(adm1031_soft_statep, instance);

        if (!(admp->adm1031_flags & ADM1031_INTRFLAG)) {
                cmn_err(CE_WARN, "%s:%d No interrupt handler registered\n",
                    admp->adm1031_name, instance);
                return (EBUSY);
        }

        admp->adm1031_transfer->i2c_flags = I2C_WR_RD;
        admp->adm1031_transfer->i2c_wlen = 1;
        admp->adm1031_transfer->i2c_rlen = 1;

        /*
         * The register has to be read to clear the previous status.
         */

        for (i = 0; i < 2; i++) {
                admp->adm1031_transfer->i2c_wbuf[0] = ADM1031_STAT_1_REG;
                if (i2c_transfer(admp->adm1031_hdl, admp->adm1031_transfer)
                    != I2C_SUCCESS) {
                        return (EIO);
                }
                temp[0] = admp->adm1031_transfer->i2c_rbuf[0];
                admp->adm1031_transfer->i2c_wbuf[0] = ADM1031_STAT_2_REG;
                if (i2c_transfer(admp->adm1031_hdl, admp->adm1031_transfer)
                    != I2C_SUCCESS) {
                        return (EIO);
                }
        }
        temp[1] = admp->adm1031_transfer->i2c_rbuf[0];

        if ((temp[0] != 0) || (temp[1] != 0)) {
                goto copyout;
        }

        /*
         * Enable the interrupt and fan fault alert.
         */
        mutex_enter(&admp->adm1031_mutex);
        while (admp->adm1031_flags & ADM1031_BUSYFLAG) {
                if (cv_wait_sig(&admp->adm1031_cv,
                    &admp->adm1031_mutex) <= 0) {
                        mutex_exit(&admp->adm1031_mutex);
                        return (EINTR);
                }
        }
        admp->adm1031_flags |= ADM1031_BUSYFLAG;

        mutex_exit(&admp->adm1031_mutex);

        admp->adm1031_transfer->i2c_flags = I2C_WR_RD;
        admp->adm1031_transfer->i2c_wlen = 1;
        admp->adm1031_transfer->i2c_rlen = 1;
        admp->adm1031_transfer->i2c_wbuf[0] = ADM1031_CONFIG_REG_1;
        if (i2c_transfer(admp->adm1031_hdl, admp->adm1031_transfer) !=
            I2C_SUCCESS) {
                err = EIO;
                goto err;
        }

        temp1 = admp->adm1031_transfer->i2c_rbuf[0];

        admp->adm1031_transfer->i2c_flags = I2C_WR;
        admp->adm1031_transfer->i2c_wlen = 2;
        admp->adm1031_transfer->i2c_wbuf[1] = (temp1 | 0x12);

        if (i2c_transfer(admp->adm1031_hdl, admp->adm1031_transfer) !=
            I2C_SUCCESS) {
                err = EIO;
                goto err;
        }


        mutex_enter(&admp->adm1031_mutex);
        admp->adm1031_flags = admp->adm1031_flags & (~ADM1031_BUSYFLAG);
        cv_signal(&admp->adm1031_cv);
        mutex_exit(&admp->adm1031_mutex);



        mutex_enter(&admp->adm1031_imutex);
        admp->adm1031_cvwaiting = 1;
        (void) cv_wait_sig(&admp->adm1031_icv, &admp->adm1031_imutex);
        mutex_exit(&admp->adm1031_imutex);


        /*
         * Disable the interrupt and fan fault alert.
         */
        mutex_enter(&admp->adm1031_mutex);

        while (admp->adm1031_flags & ADM1031_BUSYFLAG) {
                if (cv_wait_sig(&admp->adm1031_cv,
                    &admp->adm1031_mutex) <= 0) {
                        mutex_exit(&admp->adm1031_mutex);
                        return (EINTR);
                }
        }
        admp->adm1031_flags |= ADM1031_BUSYFLAG;

        admp->adm1031_transfer->i2c_flags = I2C_WR_RD;
        admp->adm1031_transfer->i2c_wlen = 1;
        admp->adm1031_transfer->i2c_rlen = 1;
        admp->adm1031_transfer->i2c_wbuf[0] = ADM1031_CONFIG_REG_1;

        if (i2c_transfer(admp->adm1031_hdl, admp->adm1031_transfer) !=
            I2C_SUCCESS) {
                err = EIO;
                goto err;
        }


        temp1 = admp->adm1031_transfer->i2c_rbuf[0];
        admp->adm1031_transfer->i2c_flags = I2C_WR;
        admp->adm1031_transfer->i2c_wlen = 2;
        admp->adm1031_transfer->i2c_wbuf[1] = (temp1 & (~0x12));

        if (i2c_transfer(admp->adm1031_hdl, admp->adm1031_transfer) !=
            I2C_SUCCESS) {
                err = (EIO);
                goto err;
        }

        admp->adm1031_flags = admp->adm1031_flags & (~ADM1031_BUSYFLAG);
        cv_signal(&admp->adm1031_cv);
        mutex_exit(&admp->adm1031_mutex);

        admp->adm1031_transfer->i2c_flags = I2C_WR_RD;
        admp->adm1031_transfer->i2c_wlen = 1;
        admp->adm1031_transfer->i2c_rlen = 1;
        admp->adm1031_transfer->i2c_wbuf[0] = ADM1031_STAT_1_REG;
        if (i2c_transfer(admp->adm1031_hdl, admp->adm1031_transfer) !=
            I2C_SUCCESS) {

                return (EIO);
        }
        temp[0] = admp->adm1031_transfer->i2c_rbuf[0];

        admp->adm1031_transfer->i2c_wbuf[0] = ADM1031_STAT_2_REG;
        if (i2c_transfer(admp->adm1031_hdl, admp->adm1031_transfer) !=
            I2C_SUCCESS) {

                return (EIO);
        }
        temp[1] = admp->adm1031_transfer->i2c_rbuf[0];

copyout:
        if (ddi_copyout((void *)&temp, (void *)arg, sizeof (temp),
            mode) != DDI_SUCCESS) {

                return (EFAULT);
        }

        return (0);

err:
        mutex_enter(&admp->adm1031_mutex);
        admp->adm1031_flags = admp->adm1031_flags & (~ADM1031_BUSYFLAG);
        cv_signal(&admp->adm1031_cv);
        mutex_exit(&admp->adm1031_mutex);

        return (err);
}

static int
adm1031_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
    int *rvalp)
{
        _NOTE(ARGUNUSED(credp, rvalp))

        if (cmd == ADM1031_INTERRUPT_WAIT) {
                return (adm1031_i_ioctl(dev, cmd, arg, mode));
        } else {
                return (adm1031_s_ioctl(dev, cmd, arg, mode));
        }
}