adm1031.c revision 193974072f41a843678abf5f61979c748687e66b
/*
* 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, 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));
}
}