/* * 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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * 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 }; 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 v1.4", &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, NULL) == 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, NULL) == 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, NULL) == 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)); } }