/*
* 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
* 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.
*/
/*
* 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
* 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
*/
/*
* dev 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 */
};
0,
NULL,
NULL,
ddi_quiesce_not_supported, /* devo_quiesce */
};
0x00,
};
};
{"fan_1", ADM1031_FAN_SPEED_INST_REG_1},
{"fan_2", ADM1031_FAN_SPEED_INST_REG_2}
};
&mod_driverops, /* type of module - driver */
"adm1031 device driver",
};
0
};
static void *adm1031_soft_statep;
int
_init(void)
{
int err;
if (err == 0) {
(void) ddi_soft_state_init(&adm1031_soft_statep,
sizeof (adm1031_unit_t), 1);
}
return (err);
}
int
_fini(void)
{
int err;
if (err == 0) {
}
return (err);
}
int
{
}
static int
{
admp = (adm1031_unit_t *)
return (DDI_FAILURE);
}
/*
* Restore registers to state existing before cpr
*/
DDI_SUCCESS) {
err = DDI_FAILURE;
goto done;
}
DDI_SUCCESS) {
err = DDI_FAILURE;
goto done;
}
DDI_SUCCESS) {
err = DDI_FAILURE;
goto done;
}
/*
* Clear busy flag so that transactions may continue
*/
done:
if (err != DDI_SUCCESS) {
}
return (err);
}
static void
{
}
}
}
(void) ddi_prop_remove_all(dip);
}
static uint_t
{
if (admp->adm1031_cvwaiting == 0)
return (DDI_INTR_CLAIMED);
admp->adm1031_cvwaiting = 0;
return (DDI_INTR_CLAIMED);
}
static int
{
int i;
char *minor_name;
int err = 0;
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
/*
* Create minor node for all temperature functions.
*/
for (i = 0; i < ADM1031_TEMP_CHANS; i++) {
return (DDI_FAILURE);
}
}
/*
* Create minor node for all fan functions.
*/
for (i = 0; i < ADM1031_FAN_SPEED_CHANS; i++) {
return (DDI_FAILURE);
}
}
/*
* Create minor node for all control functions.
*/
return (DDI_FAILURE);
}
/*
* preallocate a single buffer for all reads and writes
*/
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
"interrupt-priorities") != 1) {
DDI_PROP_CANSLEEP, "interrupt-priorities",
(void *)&adm1031_pil, sizeof (adm1031_pil));
}
if (err == DDI_SUCCESS) {
(void *)admp->adm1031_icookie);
} else {
}
}
/*
* The system comes up in Automatic Monitor Mode.
*/
return (DDI_SUCCESS);
}
static int
{
switch (cmd) {
case DDI_ATTACH:
return (adm1031_attach(dip));
case DDI_RESUME:
return (adm1031_resume(dip));
default:
return (DDI_FAILURE);
}
}
static int
{
/*
* Set the busy flag so that future transactions block
* until resume.
*/
&admp->adm1031_mutex) <= 0) {
return (DDI_FAILURE);
}
}
/*
* Save the state of the threshold registers.
*/
DDI_SUCCESS) {
err = DDI_FAILURE;
goto done;
}
DDI_SUCCESS) {
err = DDI_FAILURE;
goto done;
}
DDI_SUCCESS) {
err = DDI_FAILURE;
goto done;
}
done:
if (err != DDI_SUCCESS) {
}
return (err);
}
static int
{
switch (cmd) {
case DDI_DETACH:
return (DDI_SUCCESS);
case DDI_SUSPEND:
return (adm1031_suspend(dip));
default:
return (DDI_FAILURE);
}
}
static int
{
int instance;
/* must be root to access this device */
return (EPERM);
}
/*
* Make sure the open is for the right file type
*/
return (EINVAL);
}
admp = (adm1031_unit_t *)
return (ENXIO);
}
/*
* Enforce exclusive access if required.
*/
if (admp->adm1031_oflag == 0) {
err = 0;
}
err = 0;
}
return (err);
}
static int
{
int instance;
admp = (adm1031_unit_t *)
return (ENXIO);
}
admp->adm1031_oflag = 0;
return (0);
}
static int
{
admp = (adm1031_unit_t *)
/*
* We serialize here and block pending transactions.
*/
&admp->adm1031_mutex) <= 0) {
return (EINTR);
}
}
switch (fcn) {
case ADM1031_TEMPERATURES:
if (cmd == I2C_GET_TEMPERATURE) {
goto copyout;
} else {
if (!ADM1031_CHECK_TEMPERATURE_CMD(cmd_c)) {
goto done;
}
if (ADM1031_CHECK_FOR_WRITES(cmd))
goto writes;
else
goto copyout;
}
case ADM1031_FANS:
if (cmd == I2C_GET_FAN_SPEED) {
goto copyout;
} else if (cmd == ADM1031_GET_FAN_CONFIG) {
goto copyout;
} else if (cmd == I2C_SET_FAN_SPEED) {
goto done;
}
speed = write_value;
goto done;
}
if (ADM1031_CHECK_INVALID_SPEED(speed)) {
goto done;
}
goto done;
}
}
goto done;
}
if (!ADM1031_CHECK_FAN_CMD(cmd_c)) {
goto done;
}
if (ADM1031_CHECK_FOR_WRITES(cmd))
goto writes;
else
goto copyout;
case ADM1031_CONTROL:
/*
* Read the primary configuration register in advance.
*/
goto done;
}
switch (cmd) {
case ADM1031_GET_MONITOR_MODE:
temp = ADM1031_AUTOFLAG &
}
goto done;
case ADM1031_SET_MONITOR_MODE:
goto done;
}
if (write_value == ADM1031_AUTO_MODE) {
temp = ADM1031_AUTOFLAG |
} else if (write_value == ADM1031_MANUAL_MODE) {
(~ADM1031_AUTOFLAG);
} else {
goto done;
}
}
goto done;
default:
goto control;
}
default:
goto done;
}
if (ADM1031_CHECK_FOR_WRITES(cmd)) {
goto writes;
}
goto copyout;
if (fcn == ADM1031_TEMPERATURES) {
goto done;
}
} else {
goto done;
}
}
I2C_SUCCESS) {
}
goto done;
I2C_SUCCESS) {
goto done;
}
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;
}
mode) != DDI_SUCCESS)
} else {
mode) != DDI_SUCCESS)
}
done:
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
{
uint8_t i = 0;
int err = 0;
if (fcn != ADM1031_CONTROL)
return (EINVAL);
admp = (adm1031_unit_t *)
return (EBUSY);
}
/*
* The register has to be read to clear the previous status.
*/
for (i = 0; i < 2; i++) {
!= I2C_SUCCESS) {
return (EIO);
}
!= I2C_SUCCESS) {
return (EIO);
}
}
goto copyout;
}
/*
* Enable the interrupt and fan fault alert.
*/
&admp->adm1031_mutex) <= 0) {
return (EINTR);
}
}
I2C_SUCCESS) {
goto err;
}
I2C_SUCCESS) {
goto err;
}
/*
* Disable the interrupt and fan fault alert.
*/
&admp->adm1031_mutex) <= 0) {
return (EINTR);
}
}
I2C_SUCCESS) {
goto err;
}
I2C_SUCCESS) {
goto err;
}
I2C_SUCCESS) {
return (EIO);
}
I2C_SUCCESS) {
return (EIO);
}
mode) != DDI_SUCCESS) {
return (EFAULT);
}
return (0);
err:
return (err);
}
static int
int *rvalp)
{
if (cmd == ADM1031_INTERRUPT_WAIT) {
} else {
}
}