/*
* 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.
*/
/*
* ENVCTRLTWO_ Environment Monitoring driver for i2c on Javelin
*
*/
#include <sys/param.h>
#include <sys/types.h>
#include <sys/signal.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/termio.h>
#include <sys/termios.h>
#include <sys/cmn_err.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/stropts.h>
#include <sys/strtty.h>
#include <sys/debug.h>
#include <sys/eucioctl.h>
#include <sys/cred.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <sys/kmem.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/obpdefs.h>
#include <sys/conf.h> /* req. by dev_ops flags MTSAFE etc. */
#include <sys/modctl.h> /* for modldrv */
#include <sys/stat.h> /* ddi_create_minor_node S_IFCHR */
#include <sys/open.h> /* for open params. */
#include <sys/uio.h> /* for read/write */
#include <sys/envctrl_gen.h> /* user level generic visible definitions */
#include <sys/envctrl_ue250.h> /* user level UE250 visible definitions */
#include <javelin/sys/envctrltwo.h> /* definitions for Javelin */
#include <io/envctrl_targets.c>
#include <sys/priv_names.h>
/* driver entry point fn definitions */
static int envctrl_open(dev_t *, int, int, cred_t *);
static int envctrl_close(dev_t, int, int, cred_t *);
static int envctrl_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
static uint_t envctrl_bus_isr(caddr_t);
static uint_t envctrl_dev_isr(caddr_t);
/* configuration entry point fn definitions */
static int envctrl_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int envctrl_attach(dev_info_t *, ddi_attach_cmd_t);
static int envctrl_detach(dev_info_t *, ddi_detach_cmd_t);
/* Driver private routines */
#ifdef GET_CPU_TEMP
static int envctrl_get_cpu_temp(struct envctrlunit *, int);
#endif
static void envctrl_fan_fail_service(struct envctrlunit *);
static void envctrl_PS_intr_service(struct envctrlunit *);
static void envctrl_ps_probe(struct envctrlunit *);
static void envctrl_tempr_poll(void *);
static void envctrl_pshotplug_poll(void *);
static void envctrl_led_blink(void *);
static void envctrl_init_bus(struct envctrlunit *);
static void envctrl_reset_dflop(struct envctrlunit *);
static void envctrl_enable_devintrs(struct envctrlunit *);
static void envctrl_intr_latch_clr(struct envctrlunit *);
static void envctrl_abort_seq_handler(char *msg);
static int envctrl_get_fpm_status(struct envctrlunit *, uint8_t *);
static int envctrl_set_fsp(struct envctrlunit *, uint8_t *);
static int envctrl_set_dskled(struct envctrlunit *,
struct envctrl_chip *);
static int envctrl_get_dskled(struct envctrlunit *,
struct envctrl_chip *);
static int envctrl_set_fanspeed(struct envctrlunit *,
struct envctrl_chip *);
static void envctrl_probe_cpus(struct envctrlunit *);
static int envctrl_match_cpu(dev_info_t *, void *);
static int envctrl_isother_fault_led(struct envctrlunit *,
uint8_t, uint8_t);
static int envctrl_check_sys_temperatures(struct envctrlunit *);
static void envctrl_check_disk_kstats(struct envctrlunit *);
static void envctrl_update_disk_kstats(struct envctrlunit *,
uint8_t, uint8_t);
static int envctrl_read_chip(struct envctrlunit *, int, int, int,
uint8_t *, int);
static int envctrl_write_chip(struct envctrlunit *, int, int, int,
uint8_t *, int);
static int envctrl_check_tempr_levels(struct envctrlunit *,
int, uint8_t *, int);
static void envctrl_update_fanspeed(struct envctrlunit *);
/* Kstat routines */
static void envctrl_add_kstats(struct envctrlunit *);
static int envctrl_ps_kstat_update(kstat_t *, int);
static int envctrl_fanstat_kstat_update(kstat_t *, int);
static int envctrl_encl_kstat_update(kstat_t *, int);
static int envctrl_temp_kstat_update(kstat_t *, int);
static int envctrl_disk_kstat_update(kstat_t *, int);
static void envctrl_init_encl_kstats(struct envctrlunit *);
extern void power_down(const char *);
extern int prom_getprop();
extern int prom_getproplen();
extern void prom_printf(const char *fmt, ...);
extern void (*abort_seq_handler)();
static void *envctrlsoft_statep;
static char driver_name[] = "envctrltwo";
static uchar_t _cpu_temps[256];
static uchar_t _cpu_fan_speeds[256];
static int psok[2] = {-1, -1};
static int pspr[2] = {-1, -1};
static uint8_t idle_fanspeed;
static int power_flt_led_lit = 0;
extern void pci_thermal_rem_intr(dev_info_t *, uint_t);
/* Local Variables */
/* Indicates whether or not the overtemp thread has been started */
static int envctrl_debug_flags = 0;
static int envctrl_power_off_overide = 0;
static int envctrl_max_retries = 200;
static int envctrl_allow_detach = 0;
static int envctrl_numcpus = 1;
static int envctrl_handler = 1; /* 1 is the default */
static clock_t overtemp_timeout_hz;
static clock_t blink_timeout_hz;
static clock_t pshotplug_timeout_hz;
static clock_t warning_timeout_hz;
/*
* Temperature levels :
* green = OK - no action needed
* yellow = warning - display warning message and poll faster
* red = critical - shutdown system
*/
enum levels {green, yellow, red};
#define DPRINTF1 if (envctrl_debug_flags && (envctrl_debug_flags & 0x1)) printf
#define DPRINTF2 if (envctrl_debug_flags && (envctrl_debug_flags & 0x2)) printf
#define DPRINTF3 if (envctrl_debug_flags && (envctrl_debug_flags & 0x4)) printf
#define JAV_FAN_SPEED_SF_NUM 107
#define JAV_FAN_SPEED_SF_DEN 100
#define JAV_MAX_TEMP_SENSORS 6
#define JAV_FSP_MASK 0xC0
#define FAN_DRIFT 25
#define MAX_FAN_SPEED 255
#define MAX_DEVS 16
#define ENVCTRL_UE250_INTR_LATCH_INIT0 0xFE
#define ENVCTRL_UE250_INTR_LATCH_INIT1 0xFF
static int t_scale_num[8];
static int t_scale_den[8];
static uint8_t t_addr[8];
static uint8_t t_port[8];
static int sensor_types[] = { ENVCTRL_UE250_CPU0_SENSOR,
ENVCTRL_UE250_CPU1_SENSOR, ENVCTRL_UE250_MB0_SENSOR,
ENVCTRL_UE250_MB1_SENSOR, ENVCTRL_UE250_PDB_SENSOR,
ENVCTRL_UE250_SCSI_SENSOR };
static struct cb_ops envctrl_cb_ops = {
envctrl_open, /* cb_open */
envctrl_close, /* cb_close */
nodev, /* cb_strategy */
nodev, /* cb_print */
nodev, /* cb_dump */
nodev, /* cb_read */
nodev, /* cb_write */
envctrl_ioctl, /* cb_ioctl */
nodev, /* cb_devmap */
nodev, /* cb_mmap */
nodev, /* cb_segmap */
nochpoll, /* cb_chpoll */
ddi_prop_op, /* cb_prop_op */
NULL, /* cb_stream */
(int)(D_NEW | D_MP) /* cb_flag */
};
/*
* Declare ops vectors for auto configuration.
*/
struct dev_ops envctrltwo_ops = {
DEVO_REV, /* devo_rev */
0, /* devo_refcnt */
envctrl_getinfo, /* devo_getinfo */
nulldev, /* devo_identify */
nulldev, /* devo_probe */
envctrl_attach, /* devo_attach */
envctrl_detach, /* devo_detach */
nodev, /* devo_reset */
&envctrl_cb_ops, /* devo_cb_ops */
(struct bus_ops *)NULL, /* devo_bus_ops */
nulldev, /* devo_power */
ddi_quiesce_not_supported, /* devo_quiesce */
};
extern struct mod_ops mod_driverops;
static struct modldrv envctrlmodldrv = {
&mod_driverops, /* type of module - driver */
"I2C ENVCTRLTWO_driver",
&envctrltwo_ops,
};
static struct modlinkage envctrlmodlinkage = {
MODREV_1,
&envctrlmodldrv,
0
};
int
_init(void)
{
register int error;
if ((error = mod_install(&envctrlmodlinkage)) == 0) {
(void) ddi_soft_state_init(&envctrlsoft_statep,
sizeof (struct envctrlunit), 1);
}
return (error);
}
int
_fini(void)
{
register int error;
if ((error = mod_remove(&envctrlmodlinkage)) == 0)
ddi_soft_state_fini(&envctrlsoft_statep);
return (error);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&envctrlmodlinkage, modinfop));
}
static int
envctrl_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
register int instance;
char name[16];
uint8_t fspval;
register struct envctrlunit *unitp;
struct ddi_device_acc_attr attr;
uchar_t *creg_prop;
uint_t len, tblsz;
int i, j, k, status;
uint8_t fanspeed;
status = len = tblsz = 0;
attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
instance = ddi_get_instance(dip);
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
if (!(unitp = ddi_get_soft_state(envctrlsoft_statep, instance)))
return (DDI_FAILURE);
mutex_enter(&unitp->umutex);
if (!unitp->suspended) {
mutex_exit(&unitp->umutex);
return (DDI_FAILURE);
}
unitp->suspended = 0;
unitp->initting = B_TRUE;
envctrl_init_bus(unitp);
unitp->initting = B_FALSE;
envctrl_ps_probe(unitp);
envctrl_probe_cpus(unitp);
mutex_exit(&unitp->umutex);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
/* Set up timer values */
overtemp_timeout_hz = drv_usectohz(ENVCTRL_UE250_OVERTEMP_TIMEOUT_USEC);
blink_timeout_hz = drv_usectohz(ENVCTRL_UE250_BLINK_TIMEOUT_USEC);
pshotplug_timeout_hz =
drv_usectohz(ENVCTRL_UE250_BLINK_TIMEOUT_USEC * 2);
/*
* On a cooling failure, either a fan failure or temperature
* exceeding a WARNING level, the temperature poll thread
* will run every 6 seconds.
*/
warning_timeout_hz =
drv_usectohz(ENVCTRL_UE250_OVERTEMP_TIMEOUT_USEC / 6);
if (ddi_soft_state_zalloc(envctrlsoft_statep, instance) != 0) {
cmn_err(CE_WARN, "%s%d: failed to zalloc softstate\n",
ddi_get_name(dip), instance);
goto failed;
}
unitp = ddi_get_soft_state(envctrlsoft_statep, instance);
if (ddi_regs_map_setup(dip, 0, (caddr_t *)&unitp->bus_ctl_regs, 0,
sizeof (struct ehc_pcd8584_regs), &attr,
&unitp->ctlr_handle) != DDI_SUCCESS) {
cmn_err(CE_WARN, "%s%d: failed to map in bus_control regs\n",
ddi_get_name(dip), instance);
return (DDI_FAILURE);
}
/*
* If the PCI nexus has added a thermal interrupt, we first need
* to remove that interrupt handler.
*
* WARNING: Removing another driver's interrupt handler is not
* allowed. The pci_thermal_rem_intr() call below is needed to retain
* the legacy behavior on Javelin systems.
*/
pci_thermal_rem_intr(dip, (uint_t)0);
/* add interrupts */
if (ddi_get_iblock_cookie(dip, 1,
&unitp->ic_trap_cookie) != DDI_SUCCESS) {
cmn_err(CE_WARN, "%s%d: ddi_get_iblock_cookie FAILED \n",
ddi_get_name(dip), instance);
goto failed;
}
mutex_init(&unitp->umutex, NULL, MUTEX_DRIVER,
(void *)unitp->ic_trap_cookie);
if (ddi_add_intr(dip, 0, &unitp->ic_trap_cookie, NULL, envctrl_bus_isr,
(caddr_t)unitp) != DDI_SUCCESS) {
cmn_err(CE_WARN, "%s%d: failed to add hard intr \n",
ddi_get_name(dip), instance);
goto remlock;
}
if (ddi_add_intr(dip, 1, &unitp->ic_trap_cookie, NULL, envctrl_dev_isr,
(caddr_t)unitp) != DDI_SUCCESS) {
cmn_err(CE_WARN, "%s%d: failed to add hard intr \n",
ddi_get_name(dip), instance);
goto remhardintr;
}
(void) sprintf(name, "envctrltwo%d", instance);
if (ddi_create_priv_minor_node(dip, name, S_IFCHR, instance,
DDI_PSEUDO, 0, PRIV_SYS_CONFIG, PRIV_SYS_CONFIG, 0666) ==
DDI_FAILURE) {
goto remhardintr1;
}
mutex_enter(&unitp->umutex);
/*
* Javelin will not have a workstation configuration so activity
* LED will always blink.
*/
unitp->activity_led_blink = B_TRUE;
unitp->shutdown = B_FALSE;
unitp->num_ps_present = 0;
unitp->num_encl_present = 1;
unitp->current_mode = ENVCTRL_NORMAL_MODE;
if (envctrl_numcpus > 1) {
unitp->num_cpus_present = envctrl_numcpus;
}
envctrl_probe_cpus(unitp);
if ((unitp->cpu_pr_location[ENVCTRL_CPU0] == B_FALSE) ||
(unitp->cpu_pr_location[ENVCTRL_CPU1] == B_FALSE))
/* Only one CPU in the system */
unitp->num_temps_present = 5;
else
unitp->num_temps_present = 6;
unitp->num_fans_present = 1;
unitp->dip = dip;
mutex_exit(&unitp->umutex);
if (ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"cpu-temp-factors", &creg_prop, &len) != DDI_PROP_SUCCESS) {
cmn_err(CE_WARN,
"%s%d: Unable to read cpu-temp-factors property",
ddi_get_name(dip), instance);
return (DDI_NOT_WELL_FORMED);
}
tblsz = (sizeof (_cpu_temps) / sizeof (uchar_t));
if (len <= tblsz && status == DDI_PROP_SUCCESS) {
for (i = 0; i < len; i++) {
_cpu_temps[i+2] = creg_prop[i];
}
}
_cpu_temps[0] = _cpu_temps[1] = _cpu_temps[2];
ddi_prop_free((void *)creg_prop);
if (ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"cpu-fan-speeds", &creg_prop, &len) != DDI_PROP_SUCCESS) {
cmn_err(CE_WARN,
"%s%d: Unable to read cpu-fan-speeds property",
ddi_get_name(dip), instance);
return (DDI_NOT_WELL_FORMED);
}
tblsz = (sizeof (_cpu_fan_speeds) / sizeof (uchar_t));
if (len <= tblsz && status == DDI_PROP_SUCCESS) {
for (i = 0; i < len; i++) {
_cpu_fan_speeds[i+2] = creg_prop[i];
}
}
_cpu_fan_speeds[0] = _cpu_fan_speeds[1] = _cpu_fan_speeds[2];
ddi_prop_free((void *)creg_prop);
if (ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"thermisters", &creg_prop, &len) != DDI_PROP_SUCCESS) {
cmn_err(CE_WARN,
"%s%d: Unable to read thermisters property",
ddi_get_name(dip), instance);
return (DDI_NOT_WELL_FORMED);
}
mutex_enter(&unitp->umutex);
j = 0; k = 0;
for (i = 0; i < JAV_MAX_TEMP_SENSORS; i++) {
/* Type */
unitp->temp_kstats[k].type = sensor_types[i];
/* Address */
t_addr[k] = creg_prop[j] << 24 | creg_prop[j+1] << 16 |
creg_prop[j+2] << 8 | creg_prop[j+3];
j += 4;
/* Port */
t_port[k] = creg_prop[j] << 24 | creg_prop[j+1] << 16 |
creg_prop[j+2] << 8 | creg_prop[j+3];
j += 4;
/* Min */
unitp->temp_kstats[k].min =
creg_prop[j] << 24 | creg_prop[j+1] << 16 |
creg_prop[j+2] << 8 | creg_prop[j+3];
j += 4;
/* Warning threshold */
unitp->temp_kstats[k].warning_threshold =
creg_prop[j] << 24 | creg_prop[j+1] << 16 |
creg_prop[j+2] << 8 | creg_prop[j+3];
j += 4;
/* Shutdown threshold */
unitp->temp_kstats[k].shutdown_threshold =
creg_prop[j] << 24 | creg_prop[j+1] << 16 |
creg_prop[j+2] << 8 | creg_prop[j+3];
j += 4;
/* Numerator of scale factor */
t_scale_num[k] = creg_prop[j] << 24 | creg_prop[j+1] << 16 |
creg_prop[j+2] << 8 | creg_prop[j+3];
j += 4;
/* Denominator of scale factor */
t_scale_den[k] = creg_prop[j] << 24 | creg_prop[j+1] << 16 |
creg_prop[j+2] << 8 | creg_prop[j+3];
j += 4;
bcopy((caddr_t)&creg_prop[j], unitp->temp_kstats[k].label,
(size_t)sizeof (&creg_prop[j]));
while (creg_prop[j] != '\0') j++;
j++;
if (t_addr[k] == ENVCTRL_UE250_CPU_TEMP_DEV) {
if (((t_port[k] == ENVCTRL_UE250_CPU0_PORT) &&
(unitp->cpu_pr_location[ENVCTRL_CPU0] ==
B_FALSE)) ||
((t_port[k] == ENVCTRL_UE250_CPU1_PORT) &&
(unitp->cpu_pr_location[ENVCTRL_CPU1] == B_FALSE)))
/* Don't increment the kstat line count */
#ifdef lint
k = k;
#else
;
#endif
else
k++;
} else
k++;
}
ddi_prop_free((void *)creg_prop);
/* initialize the envctrl bus controller */
unitp->initting = B_TRUE;
envctrl_init_bus(unitp);
DPRINTF1("envctrl_attach(): Completed initialization of PCF8584");
unitp->initting = B_FALSE;
drv_usecwait(1000);
unitp->timeout_id = 0;
unitp->blink_timeout_id = 0;
unitp->fan_failed = 0;
unitp->fan_kstats.fans_ok = B_TRUE;
unitp->tempr_warning = 0;
envctrl_ps_probe(unitp);
unitp->initting = B_TRUE;
envctrl_fan_fail_service(unitp);
unitp->initting = B_FALSE;
/*
* Fans could be blasting, turn them down.
*/
fanspeed = 0x0;
status = envctrl_write_chip(unitp, ENVCTRL_PCF8591, EHC_DEV2, 0,
&fanspeed, 1);
if (status == DDI_FAILURE)
cmn_err(CE_WARN, "%s%d: Write to PCF8591 (SETFAN) failed\n",
ddi_get_name(dip), instance);
/*
* we need to init the fan kstats before the tempr_poll
*/
envctrl_add_kstats(unitp);
envctrl_init_encl_kstats(unitp);
envctrl_check_disk_kstats(unitp);
envctrl_update_fanspeed(unitp);
idle_fanspeed = unitp->fan_kstats.fanspeed;
if (unitp->activity_led_blink == B_TRUE) {
unitp->present_led_state = B_FALSE;
mutex_exit(&unitp->umutex);
envctrl_led_blink((void *)unitp);
mutex_enter(&unitp->umutex);
} else {
fspval = ENVCTRL_UE250_FSP_ACTIVE;
(void) envctrl_set_fsp(unitp, &fspval);
}
mutex_exit(&unitp->umutex);
envctrl_tempr_poll((void *)unitp);
/*
* interpose envctrl's abort sequence handler
*/
if (envctrl_handler) {
abort_seq_handler = envctrl_abort_seq_handler;
}
ddi_report_dev(dip);
return (DDI_SUCCESS);
remhardintr1:
ddi_remove_intr(dip, (uint_t)1, unitp->ic_trap_cookie);
remhardintr:
ddi_remove_intr(dip, (uint_t)0, unitp->ic_trap_cookie);
remlock:
mutex_destroy(&unitp->umutex);
failed:
if (unitp->ctlr_handle)
ddi_regs_map_free(&unitp->ctlr_handle);
cmn_err(CE_WARN, "%s%d: attach failed\n", ddi_get_name(dip), instance);
return (DDI_FAILURE);
}
static int
envctrl_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
int instance;
register struct envctrlunit *unitp;
instance = ddi_get_instance(dip);
unitp = ddi_get_soft_state(envctrlsoft_statep, instance);
switch (cmd) {
case DDI_DETACH:
if (envctrl_allow_detach) {
if (unitp->psksp != NULL) {
kstat_delete(unitp->psksp);
}
if (unitp->fanksp != NULL) {
kstat_delete(unitp->fanksp);
}
if (unitp->enclksp != NULL) {
kstat_delete(unitp->enclksp);
}
if (unitp->tempksp != NULL) {
kstat_delete(unitp->tempksp);
}
if (unitp->diskksp != NULL) {
kstat_delete(unitp->diskksp);
}
if (unitp->timeout_id != 0) {
(void) untimeout(unitp->timeout_id);
unitp->timeout_id = 0;
}
if (unitp->blink_timeout_id != 0) {
(void) untimeout(unitp->blink_timeout_id);
unitp->blink_timeout_id = 0;
}
ddi_remove_minor_node(dip, NULL);
ddi_remove_intr(dip, (uint_t)0, unitp->ic_trap_cookie);
ddi_remove_intr(dip, (uint_t)1, unitp->ic_trap_cookie);
ddi_regs_map_free(&unitp->ctlr_handle);
mutex_destroy(&unitp->umutex);
return (DDI_SUCCESS);
} else {
return (DDI_FAILURE);
}
case DDI_SUSPEND:
if (!(unitp = ddi_get_soft_state(envctrlsoft_statep, instance)))
return (DDI_FAILURE);
mutex_enter(&unitp->umutex);
if (unitp->suspended) {
cmn_err(CE_WARN, "%s%d: envctrltwo already suspended\n",
ddi_get_name(dip), instance);
mutex_exit(&unitp->umutex);
return (DDI_FAILURE);
}
unitp->suspended = 1;
mutex_exit(&unitp->umutex);
return (DDI_SUCCESS);
default:
cmn_err(CE_WARN, "%s%d: suspend general fault\n",
ddi_get_name(dip), instance);
return (DDI_FAILURE);
}
}
int
envctrl_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
void **result)
{
dev_t dev = (dev_t)arg;
struct envctrlunit *unitp;
int instance, ret;
instance = getminor(dev);
#ifdef lint
dip = dip;
#endif
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
if ((unitp = (struct envctrlunit *)
ddi_get_soft_state(envctrlsoft_statep,
instance)) != NULL) {
*result = unitp->dip;
ret = DDI_SUCCESS;
} else {
*result = NULL;
ret = DDI_FAILURE;
}
break;
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)(uintptr_t)instance;
ret = DDI_SUCCESS;
break;
default:
ret = DDI_FAILURE;
break;
}
return (ret);
}
/* ARGSUSED1 */
static int
envctrl_open(dev_t *dev, int flag, int otyp, cred_t *cred_p)
{
struct envctrlunit *unitp;
int status = 0;
register int instance;
instance = getminor(*dev);
if (instance < 0)
return (ENXIO);
unitp = (struct envctrlunit *)
ddi_get_soft_state(envctrlsoft_statep, instance);
if (unitp == NULL)
return (ENXIO);
if (otyp != OTYP_CHR)
return (EINVAL);
mutex_enter(&unitp->umutex);
if (flag & FWRITE) {
if ((unitp->oflag & FWRITE)) {
mutex_exit(&unitp->umutex);
return (EBUSY);
} else {
unitp->oflag |= FWRITE;
}
}
mutex_exit(&unitp->umutex);
return (status);
}
/*ARGSUSED1*/
static int
envctrl_close(dev_t dev, int flag, int otyp, cred_t *cred_p)
{
struct envctrlunit *unitp;
register int instance;
instance = getminor(dev);
if (instance < 0)
return (ENXIO);
unitp = (struct envctrlunit *)
ddi_get_soft_state(envctrlsoft_statep, instance);
if (unitp == NULL)
return (ENXIO);
mutex_enter(&unitp->umutex);
unitp->oflag = B_FALSE;
unitp->current_mode = ENVCTRL_NORMAL_MODE;
mutex_exit(&unitp->umutex);
return (DDI_SUCCESS);
}
/*
* standard put procedure for envctrl
*/
static int
envctrl_ioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *cred_p,
int *rvalp)
{
struct envctrlunit *unitp;
register int instance;
uint8_t wdval, tempr;
struct envctrl_chip fanspeed;
struct envctrl_chip ledchip, envcchip;
struct envctrl_chip temp, a_fanspeed;
int rval = 0, status, tfanspeed;
#ifdef lint
cred_p = cred_p;
rvalp = rvalp;
#endif
instance = getminor(dev);
unitp = (struct envctrlunit *)
ddi_get_soft_state(envctrlsoft_statep, instance);
if ((cmd == ENVCTRL_IOC_SETFAN2) ||
(cmd == ENVCTRL_IOC_GETFAN2) ||
(cmd == ENVCTRL_IOC_SETMODE) ||
(cmd == ENVCTRL_IOC_GETMODE) ||
(cmd == ENVCTRL_IOC_GETTEMP2) ||
(cmd == ENVCTRL_IOC_SETFSP2) ||
(cmd == ENVCTRL_IOC_GETFSP2) ||
(cmd == ENVCTRL_IOC_RESETTMPR) ||
(cmd == ENVCTRL_IOC_SETDSKLED2) ||
(cmd == ENVCTRL_IOC_GETDSKLED2))
if ((caddr_t)arg == NULL)
return (EFAULT);
switch (cmd) {
case ENVCTRL_IOC_SETMODE:
/* Set mode */
if (ddi_copyin((caddr_t)arg, (caddr_t)&wdval, sizeof (uint8_t),
flag)) {
rval = EFAULT;
break;
}
if (wdval == ENVCTRL_DIAG_MODE ||
wdval == ENVCTRL_NORMAL_MODE) {
mutex_enter(&unitp->umutex);
unitp->current_mode = wdval;
if (unitp->timeout_id != 0 &&
wdval == ENVCTRL_DIAG_MODE) {
(void) untimeout(unitp->timeout_id);
unitp->timeout_id =
(timeout(envctrl_tempr_poll,
(caddr_t)unitp, overtemp_timeout_hz));
}
if (wdval == ENVCTRL_NORMAL_MODE) {
/*
* Fans could be blasting, turn them down.
*/
tempr = 0x0;
status = envctrl_write_chip(unitp,
ENVCTRL_PCF8591, EHC_DEV2, 0,
&tempr, 1);
if (status == DDI_FAILURE)
cmn_err(CE_WARN,
"%s%d: Write to PCF8591 "
"(SETMODE) failed\n",
driver_name, unitp->instance);
/*
* This delay allows the fans to time to
* change speed
*/
drv_usecwait(100000);
(void) envctrl_check_sys_temperatures(unitp);
unitp->current_mode = ENVCTRL_DIAG_MODE;
envctrl_fan_fail_service(unitp);
unitp->current_mode = ENVCTRL_NORMAL_MODE;
}
mutex_exit(&unitp->umutex);
} else {
rval = EINVAL;
}
break;
case ENVCTRL_IOC_GETMODE:
wdval = unitp->current_mode;
if (ddi_copyout((caddr_t)&wdval, (caddr_t)arg,
sizeof (uint8_t), flag)) {
rval = EFAULT;
}
break;
case ENVCTRL_IOC_RESETTMPR:
/*
* For diags, cancel the curent temp poll
* and reset it for a new one.
*/
if (unitp->current_mode == ENVCTRL_DIAG_MODE) {
if (unitp->timeout_id != 0) {
(void) untimeout(unitp->timeout_id);
unitp->timeout_id = 0;
}
envctrl_tempr_poll((void *)unitp);
} else {
rval = EINVAL;
}
break;
case ENVCTRL_IOC_GETTEMP2:
/* Get the user buffer address */
if (ddi_copyin((caddr_t)arg, (caddr_t)&temp,
sizeof (struct envctrl_chip), flag)) {
rval = EFAULT;
break;
}
if (((temp.chip_num != ENVCTRL_DEV2) &&
(temp.chip_num != ENVCTRL_DEV7)) ||
(temp.index > EHC_PCF8591_CH_3)) {
rval = EINVAL;
break;
}
mutex_enter(&unitp->umutex);
status = envctrl_read_chip(unitp, ENVCTRL_PCF8591,
temp.chip_num, temp.index, &temp.val, 1);
mutex_exit(&unitp->umutex);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN,
"%s%d: Read from PCF8591 (IOC_GETTEMP) failed",
driver_name, unitp->instance);
rval = EINVAL;
break;
}
if (ddi_copyout((caddr_t)&temp, (caddr_t)arg,
sizeof (struct envctrl_chip), flag)) {
rval = EFAULT;
}
break;
case ENVCTRL_IOC_SETTEMP:
rval = EINVAL;
break;
case ENVCTRL_IOC_SETWDT:
rval = EINVAL;
break;
case ENVCTRL_IOC_SETFAN2:
/* NOTE: need to sanity check values coming from userland */
if (unitp->current_mode == ENVCTRL_DIAG_MODE) {
if (ddi_copyin((caddr_t)arg, (caddr_t)&fanspeed,
sizeof (struct envctrl_chip), flag)) {
rval = EFAULT;
break;
}
if ((fanspeed.type != ENVCTRL_PCF8591) ||
(fanspeed.chip_num != ENVCTRL_DEV2) ||
(fanspeed.index > EHC_PCF8591_CH_3)) {
rval = EINVAL;
break;
}
mutex_enter(&unitp->umutex);
status = envctrl_set_fanspeed(unitp, &fanspeed);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN,
"%s%d: Write to PCF8591 "
"(IOC_SETFAN) failed",
driver_name, unitp->instance);
rval = EINVAL;
}
mutex_exit(&unitp->umutex);
} else {
rval = EINVAL;
}
break;
case ENVCTRL_IOC_GETFAN2:
if (ddi_copyin((caddr_t)arg, (caddr_t)&a_fanspeed,
sizeof (struct envctrl_chip), flag)) {
rval = EFAULT;
break;
}
if ((a_fanspeed.type != ENVCTRL_PCF8591) ||
(a_fanspeed.chip_num != ENVCTRL_DEV2) ||
(a_fanspeed.index != EHC_PCF8591_CH_1)) {
rval = EINVAL;
break;
}
mutex_enter(&unitp->umutex);
status = envctrl_read_chip(unitp, ENVCTRL_PCF8591,
a_fanspeed.chip_num, a_fanspeed.index,
&a_fanspeed.val, 1);
mutex_exit(&unitp->umutex);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN,
"%s%d: Read of PCF8591 (IOC_GETFAN) failed",
driver_name, unitp->instance);
rval = EINVAL;
break;
}
/*
* Due to hardware limitation, the actual fan speed
* is always a little less than what it was set to by
* software. Hence, we scale up the read fan speed value
* to more closely match the set value.
*/
if ((tfanspeed = ((int)a_fanspeed.val * JAV_FAN_SPEED_SF_NUM) /
JAV_FAN_SPEED_SF_DEN) > 255)
a_fanspeed.val = 255;
else
a_fanspeed.val = tfanspeed & 0xFF;
unitp->fan_kstats.fanspeed = a_fanspeed.val;
if (ddi_copyout((caddr_t)&a_fanspeed, (caddr_t)arg,
sizeof (struct envctrl_chip), flag)) {
rval = EFAULT;
}
break;
case ENVCTRL_IOC_SETFSP2:
if (ddi_copyin((caddr_t)arg, (caddr_t)&envcchip,
sizeof (struct envctrl_chip), flag)) {
rval = EFAULT;
break;
}
if ((envcchip.type != ENVCTRL_PCF8574A) ||
(envcchip.chip_num != ENVCTRL_DEV6)) {
rval = EINVAL;
break;
}
wdval = envcchip.val;
mutex_enter(&unitp->umutex);
/*
* If a user is in normal mode and they try
* to set anything other than a disk fault or
* a gen fault it is an invalid operation.
* in diag mode we allow everything to be
* twiddled.
*/
if (unitp->current_mode == ENVCTRL_NORMAL_MODE) {
if (wdval & ~ENVCTRL_UE250_FSP_USRMASK) {
mutex_exit(&unitp->umutex);
rval = EINVAL;
break;
}
}
if (wdval & ENVCTRL_UE250_FSP_PS_ERR)
power_flt_led_lit = 1;
status = envctrl_set_fsp(unitp, &wdval);
mutex_exit(&unitp->umutex);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN,
"%s%d: Read of PCF8574A (IOC_SETFSP) failed",
driver_name, unitp->instance);
rval = EINVAL;
}
break;
case ENVCTRL_IOC_GETFSP2:
if (ddi_copyin((caddr_t)arg, (caddr_t)&envcchip,
sizeof (struct envctrl_chip), flag)) {
rval = EFAULT;
break;
}
if ((envcchip.type != ENVCTRL_PCF8574A) ||
(envcchip.chip_num != ENVCTRL_DEV6)) {
rval = EINVAL;
break;
}
mutex_enter(&unitp->umutex);
status = envctrl_get_fpm_status(unitp, &wdval);
mutex_exit(&unitp->umutex);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN,
"%s%d: Read of PCF8574A (IOC_GETFSP) failed",
driver_name, unitp->instance);
rval = EINVAL;
} else {
envcchip.val = wdval;
if (ddi_copyout((caddr_t)&envcchip, (caddr_t)arg,
sizeof (struct envctrl_chip), flag)) {
rval = EFAULT;
}
}
break;
case ENVCTRL_IOC_SETDSKLED2:
if (ddi_copyin((caddr_t)arg, (caddr_t)&ledchip,
sizeof (struct envctrl_chip), flag)) {
rval = EFAULT;
break;
}
if ((ledchip.type != ENVCTRL_PCF8574A) ||
(ledchip.chip_num != ENVCTRL_DEV7)) {
rval = EINVAL;
break;
}
mutex_enter(&unitp->umutex);
if (envctrl_set_dskled(unitp, &ledchip)) {
rval = EINVAL;
}
mutex_exit(&unitp->umutex);
break;
case ENVCTRL_IOC_GETDSKLED2:
if (ddi_copyin((caddr_t)arg, (caddr_t)&ledchip,
sizeof (struct envctrl_chip), flag)) {
rval = EFAULT;
break;
}
if ((ledchip.type != ENVCTRL_PCF8574A) ||
(ledchip.chip_num != ENVCTRL_DEV7)) {
rval = EINVAL;
break;
}
mutex_enter(&unitp->umutex);
if (envctrl_get_dskled(unitp, &ledchip)) {
rval = EINVAL;
} else {
if (ddi_copyout((caddr_t)&ledchip, (caddr_t)arg,
sizeof (struct envctrl_chip), flag)) {
rval = EFAULT;
}
}
mutex_exit(&unitp->umutex);
break;
case ENVCTRL_IOC_SETRAW:
if (unitp->current_mode != ENVCTRL_DIAG_MODE) {
rval = EINVAL;
break;
}
if (ddi_copyin((caddr_t)arg, (caddr_t)&temp,
sizeof (struct envctrl_chip), flag)) {
rval = EFAULT;
break;
}
mutex_enter(&unitp->umutex);
status = envctrl_write_chip(unitp, temp.type, temp.chip_num,
temp.index, &temp.val, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN,
"%s%d: Write to chip (IOC_SETRAW) failed",
driver_name, unitp->instance);
rval = EINVAL;
}
mutex_exit(&unitp->umutex);
break;
case ENVCTRL_IOC_GETRAW:
if (ddi_copyin((caddr_t)arg, (caddr_t)&temp,
sizeof (struct envctrl_chip), flag)) {
rval = EFAULT;
break;
}
mutex_enter(&unitp->umutex);
status = envctrl_read_chip(unitp, temp.type, temp.chip_num,
temp.index, &temp.val, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN,
"%s%d: Read of chip (IOC_GETRAW) failed",
driver_name, unitp->instance);
rval = EINVAL;
}
mutex_exit(&unitp->umutex);
if (ddi_copyout((caddr_t)&temp, (caddr_t)arg,
sizeof (struct envctrl_chip), flag)) {
rval = EFAULT;
}
break;
default:
rval = EINVAL;
}
return (rval);
}
uint_t
envctrl_bus_isr(caddr_t arg)
{
struct envctrlunit *unitp = (struct envctrlunit *)(void *)arg;
int ic = DDI_INTR_UNCLAIMED;
mutex_enter(&unitp->umutex);
/*
* NOT USED
*/
mutex_exit(&unitp->umutex);
return (ic);
}
uint_t
envctrl_dev_isr(caddr_t arg)
{
struct envctrlunit *unitp = (struct envctrlunit *)(void *)arg;
uint8_t recv_data;
int ic;
int retrys = 0;
int status;
static int spurious_intr_count = 0;
ic = DDI_INTR_UNCLAIMED;
mutex_enter(&unitp->umutex);
/*
* First check to see if it is an interrupt for us by
* looking at the "ganged" interrupt and vector
* according to the major type
* 0x70 is the addr of the ganged interrupt controller.
* Address map for the port byte read is as follows
* MSB
* -------------------------
* | | | | | | | | |
* -------------------------
* P7 P6 P5 P4 P3 P2 P1 P0
* P0 = Spare
* P1 = Thermal Interrupt
* P2 = Disk Interrupt
* P3 = Interrupt clock enable
* P4 = Fan Fail Interrupt
* P5 = Front Panel Interrupt
* P6 = Power Supply Interrupt
* P7 = Enable Interrupts
*/
do {
status = ehc_read_pcf8574a((struct ehc_envcunit *)unitp,
ENVCTRL_UE250_PCF8574A_BASE_ADDR | EHC_DEV0,
&recv_data, 1);
/*
* This extra read is needed since the first read is discarded
* and the second read seems to return 0xFF.
*/
if (recv_data == 0xFF) {
status = ehc_read_pcf8574a((struct ehc_envcunit *)unitp,
ENVCTRL_UE250_PCF8574A_BASE_ADDR | EHC_DEV0,
&recv_data, 1);
}
/*
* if the i2c bus is hung it is imperative that this
* be cleared on an interrupt or else it will
* hang the system with continuous interrupts
*/
if (status == DDI_FAILURE) {
drv_usecwait(1000);
if (retrys < envctrl_max_retries) {
retrys++;
} else {
cmn_err(CE_WARN,
"%s%d: Read of PCF8574A (INT) failed\n",
driver_name, unitp->instance);
ehc_init_pcf8584((struct ehc_envcunit *)unitp);
mutex_exit(&unitp->umutex);
ic = DDI_INTR_CLAIMED;
return (ic);
}
}
} while (status != DDI_SUCCESS);
DPRINTF1("Interrupt routine called, interrupt = %X\n", recv_data);
if (!(recv_data & EHC_PCF8574_PORT0)) {
ic = DDI_INTR_CLAIMED;
}
if (!(recv_data & EHC_PCF8574_PORT1)) {
DPRINTF1("Temperature interrupt detected\n");
(void) envctrl_check_sys_temperatures(unitp);
/*
* Clear the interrupt latches
*/
envctrl_intr_latch_clr(unitp);
ic = DDI_INTR_CLAIMED;
}
if (!(recv_data & EHC_PCF8574_PORT2)) {
DPRINTF1("Disk interrupt detected\n");
envctrl_check_disk_kstats(unitp);
ic = DDI_INTR_CLAIMED;
}
if (!(recv_data & EHC_PCF8574_PORT3)) {
ic = DDI_INTR_CLAIMED;
}
if (!(recv_data & EHC_PCF8574_PORT4)) {
/*
* Check for a fan fail
*/
DPRINTF1("Fan interrupt detected\n");
envctrl_fan_fail_service(unitp);
/*
* Clear the interrupt latches
*/
envctrl_intr_latch_clr(unitp);
ic = DDI_INTR_CLAIMED;
}
if (!(recv_data & EHC_PCF8574_PORT5)) {
DPRINTF1("Keyswitch interrupt detected\n");
(void) envctrl_get_fpm_status(unitp, (uint8_t *)NULL);
ic = DDI_INTR_CLAIMED;
}
if (!(recv_data & EHC_PCF8574_PORT6)) {
DPRINTF1("Power supply interrupt detected\n");
envctrl_PS_intr_service(unitp);
ic = DDI_INTR_CLAIMED;
}
if (!(recv_data & EHC_PCF8574_PORT7)) {
ic = DDI_INTR_CLAIMED;
}
/*
* The interrupt routine got called but the interrupt chip
* shows no interrupt present. If this happens more than 256
* times in a row, there is probably some hardware problem so
* send a warning message to the console.
*/
if ((recv_data == 0xFF)) {
if (spurious_intr_count == 255)
cmn_err(CE_WARN,
"%s%d: Received 256 spurious interrupts\n",
driver_name, unitp->instance);
spurious_intr_count++;
ic = DDI_INTR_CLAIMED;
} else
spurious_intr_count = 0;
mutex_exit(&unitp->umutex);
return (ic);
}
static int
envctrl_read_chip(struct envctrlunit *unitp, int type, int chip_num, int port,
uint8_t *data, int num)
{
int retrys = 0, autoincr = 0;
int status;
/*
* If more than one read is requested, set auto-increment bit
*/
if (num > 1)
autoincr = 1;
do {
if (type == ENVCTRL_PCF8574A) {
status = ehc_read_pcf8574a((struct ehc_envcunit *)unitp,
ENVCTRL_UE250_PCF8574A_BASE_ADDR | chip_num,
data, num);
} else if (type == ENVCTRL_PCF8574) {
status = ehc_read_pcf8574((struct ehc_envcunit *)unitp,
ENVCTRL_UE250_PCF8574_BASE_ADDR | chip_num,
data, num);
} else if (type == ENVCTRL_PCF8591) {
status = ehc_read_pcf8591((struct ehc_envcunit *)unitp,
ENVCTRL_UE250_PCF8591_BASE_ADDR | chip_num,
port, autoincr, 0, 1, data, num);
}
/*
* If the bus hangs, attempt a recovery
*/
if (status == DDI_FAILURE) {
drv_usecwait(1000);
if (retrys < envctrl_max_retries) {
retrys++;
} else {
ehc_init_pcf8584((struct ehc_envcunit *)unitp);
break;
}
}
} while (status != DDI_SUCCESS);
return (status);
}
static int
envctrl_write_chip(struct envctrlunit *unitp, int type, int chip_num, int port,
uint8_t *data, int num)
{
int retrys = 0, autoincr = 0;
int status;
/*
* Incase some applications mistakenly include the chips base addr
*/
chip_num = chip_num & 0xF;
/*
* If more than one write is requested, set auto-increment bit
*/
if (num > 1)
autoincr = 1;
do {
if (type == ENVCTRL_PCF8574A) {
status = ehc_write_pcf8574a(
(struct ehc_envcunit *)unitp,
ENVCTRL_UE250_PCF8574A_BASE_ADDR | chip_num,
data, num);
} else if (type == ENVCTRL_PCF8574) {
status = ehc_write_pcf8574((struct ehc_envcunit *)unitp,
ENVCTRL_UE250_PCF8574_BASE_ADDR | chip_num,
data, num);
} else if (type == ENVCTRL_PCF8591) {
status = ehc_write_pcf8591((struct ehc_envcunit *)unitp,
ENVCTRL_UE250_PCF8591_BASE_ADDR | chip_num,
port, autoincr, 0, 1, data, num);
}
/*
* If the bus hangs, attempt a recovery
*/
if (status == DDI_FAILURE) {
drv_usecwait(1000);
if (retrys < envctrl_max_retries) {
retrys++;
} else {
ehc_init_pcf8584((struct ehc_envcunit *)unitp);
break;
}
}
} while (status != DDI_SUCCESS);
return (status);
}
#ifdef GET_CPU_TEMP
static int
envctrl_get_cpu_temp(struct envctrlunit *unitp, int cpunum)
{
uint8_t recv_data;
int status;
ASSERT(MUTEX_HELD(&unitp->umutex));
/*
* This routine takes in the number of the port that
* we want to read in the 8591. This should be the
* location of the CPU thermistor for one of the 2
* cpu's. It will return a normalized value
* to the caller.
*/
status = envctrl_read_chip(unitp, ENVCTRL_PCF8591, EHC_DEV7, cpunum,
&recv_data, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: CPU TEMP read failed\n",
driver_name, unitp->instance);
return (ENVCTRL_UE250_MAX_CPU_TEMP - 10);
}
return (_cpu_temps[recv_data]);
}
#endif
static void
envctrl_tempr_poll(void *arg)
{
int diag_flag = 0, status;
struct envctrlunit *unitp = (struct envctrlunit *)arg;
mutex_enter(&unitp->umutex);
if (unitp->shutdown == B_TRUE) {
(void) power_down("Fatal System Environmental Control Error");
}
/*
* Clear the interrupt latches
*/
envctrl_intr_latch_clr(unitp);
envctrl_reset_dflop(unitp);
envctrl_enable_devintrs(unitp);
/*
* if we are in diag mode and the temp poll thread goes off,
* this means that the system is too heavily loaded and the 60 second
* window to execute the test is failing.
*/
if (unitp->current_mode == ENVCTRL_DIAG_MODE) {
diag_flag++;
if (envctrl_debug_flags) {
cmn_err(CE_WARN, "%s%d: "
"Tempr poll went off while in DIAG MODE\n",
driver_name, unitp->instance);
}
}
unitp->current_mode = ENVCTRL_NORMAL_MODE;
DPRINTF1("envctrl_tempr_poll(): Checking system temps\n");
status = envctrl_check_sys_temperatures(unitp);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN,
"%s%d: Failure detected during temperature poll",
driver_name, unitp->instance);
}
if (diag_flag == 0) {
envctrl_fan_fail_service(unitp);
}
/* Turn of the power fault LED if ps_ok is asserted */
envctrl_ps_probe(unitp);
/* now have this thread sleep for a while */
if ((unitp->fan_failed == B_TRUE) || (unitp->tempr_warning == B_TRUE)) {
/*
* A thermal warning or fan failure condition exists.
* Temperature poll thread will run every 10 seconds.
*/
if (unitp->timeout_id != 0)
(void) untimeout(unitp->timeout_id);
unitp->timeout_id = (timeout(envctrl_tempr_poll,
(caddr_t)unitp, warning_timeout_hz));
} else {
/*
* No thermal warning or fan failure condition exists.
* This thread is set to run every 60 seconds.
*/
unitp->timeout_id = (timeout(envctrl_tempr_poll,
(caddr_t)unitp, overtemp_timeout_hz));
}
mutex_exit(&unitp->umutex);
}
static void
envctrl_led_blink(void *arg)
{
uint8_t val, tmpval;
int status;
struct envctrlunit *unitp = (struct envctrlunit *)arg;
mutex_enter(&unitp->umutex);
status = envctrl_read_chip(unitp, ENVCTRL_PCF8574A, EHC_DEV6,
0, &val, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Failed to read FSP LEDs",
driver_name, unitp->instance);
/* now have this thread sleep for a while */
unitp->blink_timeout_id = (timeout(envctrl_led_blink,
(caddr_t)unitp, blink_timeout_hz));
mutex_exit(&unitp->umutex);
return;
}
if (unitp->present_led_state == B_TRUE) {
/*
* Now we need to "or" in fault bits of the FSP
* module for the mass storage fault led.
* and set it.
*/
val = (val & ~(EHC_PCF8574_PORT4) | JAV_FSP_MASK);
unitp->present_led_state = B_FALSE;
} else {
val = (val | EHC_PCF8574_PORT4 | JAV_FSP_MASK);
unitp->present_led_state = B_TRUE;
}
/*
* A static global variable, power_flt_led_lit, is used to keep
* track of periods when the software has lit the power fault LED.
* Whenever the power fault LED is lit and this variable is not set,
* then the power fault LED has been lit by hardware. In this case
* mask out the power fault LED in the byte. This is a fix for
* bug 4144872.
*/
tmpval = ~val;
if (tmpval & ENVCTRL_UE250_FSP_PS_ERR) {
if (power_flt_led_lit == 0) {
/*
* Turn off power fault bit in the FSP byte.
*/
tmpval &= ~(ENVCTRL_UE250_FSP_PS_ERR);
}
}
val = ~tmpval;
status = envctrl_write_chip(unitp, ENVCTRL_PCF8574A, EHC_DEV6,
0, &val, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Failed to blink activity LED",
driver_name, unitp->instance);
/* now have this thread sleep for a while */
unitp->blink_timeout_id = (timeout(envctrl_led_blink,
(caddr_t)unitp, blink_timeout_hz));
mutex_exit(&unitp->umutex);
return;
}
/* now have this thread sleep for a while */
unitp->blink_timeout_id = (timeout(envctrl_led_blink,
(caddr_t)unitp, blink_timeout_hz));
mutex_exit(&unitp->umutex);
}
static int
envctrl_check_sys_temperatures(struct envctrlunit *unitp)
{
uint8_t buf[8];
enum levels warning_level, level;
uint8_t fspval;
int status, warning_count = 0;
retrytemp1:
status = envctrl_read_chip(unitp, ENVCTRL_PCF8591, EHC_DEV2,
0, buf, 4);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Temperature read failed (PDB)",
driver_name, unitp->instance);
return (status);
}
warning_level = envctrl_check_tempr_levels(unitp, EHC_DEV2,
buf, warning_count);
level = warning_level;
if (warning_level != green) {
if (warning_count == 0) {
warning_count++;
drv_usecwait(1000);
goto retrytemp1;
}
if (warning_level == yellow)
unitp->tempr_warning = B_TRUE;
else if (warning_level == red) {
unitp->tempr_warning = B_TRUE;
if (!envctrl_power_off_overide)
unitp->shutdown = B_TRUE;
}
}
warning_count = 0;
retrytemp2:
status = envctrl_read_chip(unitp, ENVCTRL_PCF8591, EHC_DEV7,
0, buf+4, 4);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Temperature read failed (MBD)",
driver_name, unitp->instance);
return (status);
}
warning_level = envctrl_check_tempr_levels(unitp, EHC_DEV7,
buf+4, warning_count);
if (warning_level != green) {
if (warning_count == 0) {
warning_count++;
drv_usecwait(1000);
goto retrytemp2;
}
if ((warning_level == yellow) && (unitp->shutdown == B_FALSE))
unitp->tempr_warning = B_TRUE;
else if (warning_level == red) {
unitp->tempr_warning = B_TRUE;
if (!envctrl_power_off_overide)
unitp->shutdown = B_TRUE;
}
} else if ((level == green) && (unitp->tempr_warning == B_TRUE)) {
/*
* Current tempr. poll shows all levels normal.
* If the previous poll showed warning levels, we need
* to clear that status
*/
cmn_err(CE_NOTE,
"TEMPERATURE NORMAL: all sensors back to normal readings");
unitp->tempr_warning = B_FALSE;
}
status = envctrl_get_fpm_status(unitp, &fspval);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN,
"%s%d: Read of Front Status Panel LEDs failed",
driver_name, unitp->instance);
}
if ((unitp->tempr_warning == B_TRUE) || (unitp->shutdown == B_TRUE))
fspval |= (ENVCTRL_UE250_FSP_TEMP_ERR |
ENVCTRL_UE250_FSP_GEN_ERR);
else {
if (envctrl_isother_fault_led(unitp, fspval,
ENVCTRL_UE250_FSP_TEMP_ERR)) {
fspval &= ~(ENVCTRL_UE250_FSP_TEMP_ERR);
} else {
fspval &= ~(ENVCTRL_UE250_FSP_TEMP_ERR |
ENVCTRL_UE250_FSP_GEN_ERR);
}
}
status = envctrl_set_fsp(unitp, &fspval);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN,
"%s%d: Setting of Front Status Panel LEDs failed",
driver_name, unitp->instance);
}
/*
* Have this thread run again in about 10 seconds
*/
if (unitp->tempr_warning == B_TRUE) {
if (unitp->timeout_id != 0) {
(void) untimeout(unitp->timeout_id);
unitp->timeout_id = (timeout(envctrl_tempr_poll,
(caddr_t)unitp, warning_timeout_hz));
}
}
return (status);
}
static int
envctrl_check_tempr_levels(struct envctrlunit *unitp, int chip_num,
uint8_t *data, int count)
{
uint_t temp_degree_c;
uint8_t buf[8];
enum levels warning_level = green;
int i, j;
int status;
uint8_t fanspeed;
int tval;
for (i = 0; i < 4; i++) {
if (chip_num == EHC_DEV2) {
if (i == 1) {
tval = ((int)data[i] * JAV_FAN_SPEED_SF_NUM) /
JAV_FAN_SPEED_SF_DEN;
if (tval > 255)
unitp->fan_kstats.fanspeed = 255;
else
unitp->fan_kstats.fanspeed = tval;
DPRINTF1("device %X, fan = %d %d\n", chip_num,
unitp->fan_kstats.fanspeed, data[i]);
continue;
} else if (i == 2)
continue;
}
if ((chip_num == EHC_DEV7) && ((i == ENVCTRL_UE250_CPU0_PORT) ||
(i == ENVCTRL_UE250_CPU1_PORT)))
if (unitp->cpu_pr_location[i] == B_FALSE)
continue;
j = 0;
while ((((t_addr[j] & 0xF) != chip_num) || (t_port[j] != i)) &&
(j < unitp->num_temps_present))
j++;
if ((chip_num == EHC_DEV7) && ((i == ENVCTRL_UE250_CPU0_PORT) ||
(i == ENVCTRL_UE250_CPU1_PORT)))
temp_degree_c = _cpu_temps[data[i]];
else
temp_degree_c = ((int)data[i] * t_scale_num[j]) /
t_scale_den[j];
/*
* Javelin hardware will not control fan speeds based on
* cpu temperature values because the voltages corresponding
* to the cpu temperatures are based on an inverted scale
* compared to the ambient temperatures and thus can be
* fed to the same fan control circuit. As a result, it
* has been decided that software will control fan speed
* if cpu temperatures rise.
*/
if ((chip_num == EHC_DEV7) && ((i == ENVCTRL_UE250_CPU0_PORT) ||
(i == ENVCTRL_UE250_CPU1_PORT)) &&
(unitp->current_mode == ENVCTRL_NORMAL_MODE)) {
if (_cpu_fan_speeds[data[ENVCTRL_UE250_CPU0_PORT]] >
_cpu_fan_speeds[data[ENVCTRL_UE250_CPU1_PORT]])
fanspeed =
_cpu_fan_speeds[
data[ENVCTRL_UE250_CPU0_PORT]];
else
fanspeed =
_cpu_fan_speeds[
data[ENVCTRL_UE250_CPU1_PORT]];
status = envctrl_write_chip(unitp, ENVCTRL_PCF8591,
EHC_DEV2, 0, &fanspeed, 1);
if (status == DDI_FAILURE)
cmn_err(CE_WARN,
"%s%d: Write to PCF8591 (SETFAN) failed\n",
driver_name, unitp->instance);
status = envctrl_read_chip(unitp, ENVCTRL_PCF8591,
EHC_DEV2, 0, buf, 4);
if (status == DDI_FAILURE)
cmn_err(CE_WARN,
"%s%d: Fan speed read failed (PDB)",
driver_name, unitp->instance);
tval = ((int)buf[1] * JAV_FAN_SPEED_SF_NUM) /
JAV_FAN_SPEED_SF_DEN;
if (tval > 255)
unitp->fan_kstats.fanspeed = 255;
else
unitp->fan_kstats.fanspeed = tval;
}
DPRINTF1("device %X, temp = %d %d loc = %s\n", chip_num,
temp_degree_c, data[i], unitp->temp_kstats[j].label);
unitp->temp_kstats[j].value = temp_degree_c;
if ((temp_degree_c >=
unitp->temp_kstats[j].warning_threshold) ||
(temp_degree_c < unitp->temp_kstats[j].min)) {
if (warning_level < yellow)
warning_level = yellow;
if (count != 0)
cmn_err(CE_WARN,
"TEMPERATURE WARNING: %d degrees "
"celsius at location %s",
temp_degree_c, unitp->temp_kstats[j].label);
}
if (temp_degree_c >=
unitp->temp_kstats[j].shutdown_threshold) {
if (warning_level < red)
warning_level = red;
if (count != 0) {
cmn_err(CE_WARN,
"TEMPERATURE CRITICAL: %d "
"degrees celsius at location %s",
temp_degree_c, unitp->temp_kstats[j].label);
if (!envctrl_power_off_overide)
cmn_err(CE_WARN,
"System shutdown in "
"10 seconds ...");
}
}
}
return (warning_level);
}
static void
envctrl_update_fanspeed(struct envctrlunit *unitp)
{
uint8_t buf[8];
int tval;
int status;
status = envctrl_read_chip(unitp, ENVCTRL_PCF8591, EHC_DEV2,
0, buf, 4);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Fan speed read failed ",
driver_name, unitp->instance);
}
tval = ((int)buf[ENVCTRL_PORT1] * JAV_FAN_SPEED_SF_NUM) /
JAV_FAN_SPEED_SF_DEN;
if (tval > 255)
unitp->fan_kstats.fanspeed = 255;
else
unitp->fan_kstats.fanspeed = tval;
}
/* called with mutex held */
static void
envctrl_fan_fail_service(struct envctrlunit *unitp)
{
uint8_t recv_data, fpmstat;
int retrys = 0;
int status;
/*
* The fan fail interrupt is read from address 0x70
* on the envctrl bus.
*/
ASSERT(MUTEX_HELD(&unitp->umutex));
/*
* Clear the interrupt latches to handle spurious interrupts
*/
envctrl_intr_latch_clr(unitp);
do {
status = ehc_read_pcf8574a((struct ehc_envcunit *)unitp,
ENVCTRL_UE250_PCF8574A_BASE_ADDR | EHC_DEV0,
&recv_data, 1);
/*
* This extra read is needed since the first read is discarded
* and the second read seems to return 0xFF.
*/
if (recv_data == 0xFF) {
status = ehc_read_pcf8574a((struct ehc_envcunit *)unitp,
ENVCTRL_UE250_PCF8574A_BASE_ADDR | EHC_DEV0,
&recv_data, 1);
}
if (status == DDI_FAILURE) {
drv_usecwait(1000);
if (retrys < envctrl_max_retries) {
retrys++;
} else {
cmn_err(CE_WARN,
"%s%d: Read of PCF8574A (INTFAN) failed",
driver_name, unitp->instance);
ehc_init_pcf8584((struct ehc_envcunit *)unitp);
return;
}
}
} while (status != DDI_SUCCESS);
/* If the fan fail interrupt is now absent */
if (recv_data & EHC_PCF8574_PORT4) {
if (unitp->fan_failed == B_TRUE) {
if (unitp->current_mode == ENVCTRL_NORMAL_MODE)
cmn_err(CE_CONT,
"Fan failure has been cleared\n");
unitp->fan_kstats.fans_ok = B_TRUE;
/*
* Clear general fault LED if no other faults
*/
status = envctrl_get_fpm_status(unitp, &fpmstat);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN,
"%s%d: Read of Front Status "
"Panel LEDs failed",
driver_name, unitp->instance);
}
if (!(envctrl_isother_fault_led(unitp, fpmstat, 0))) {
fpmstat &= ~(ENVCTRL_UE250_FSP_GEN_ERR);
}
if (unitp->shutdown != B_TRUE) {
status = envctrl_set_fsp(unitp, &fpmstat);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: "
"Setting of Front Status "
"Panel LEDs failed",
driver_name, unitp->instance);
}
}
/*
* This should be set after envctrl_isother_fault_led()
* is called
*/
unitp->fan_failed = B_FALSE;
}
} else {
if (unitp->fan_failed == B_FALSE) {
if (unitp->current_mode == ENVCTRL_NORMAL_MODE)
cmn_err(CE_WARN,
"Fan failure has been detected");
unitp->fan_failed = B_TRUE;
unitp->fan_kstats.fans_ok = B_FALSE;
/*
* Set general fault LED
*/
status = envctrl_get_fpm_status(unitp, &fpmstat);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN,
"%s%d: Read of Front Status "
"Panel LEDs failed",
driver_name, unitp->instance);
return;
}
fpmstat |= ENVCTRL_UE250_FSP_GEN_ERR;
status = envctrl_set_fsp(unitp, &fpmstat);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: "
"Setting of Front Status Panel LEDs failed",
driver_name, unitp->instance);
}
/*
* A fan failure condition exists.
* Temperature poll thread should run every 10 seconds.
*/
if (unitp->timeout_id != 0) {
(void) untimeout(unitp->timeout_id);
unitp->timeout_id =
(timeout(envctrl_tempr_poll,
(caddr_t)unitp, warning_timeout_hz));
}
}
}
}
/*
* Check for power supply insertion and failure.
* This is a bit tricky, because a power supply insertion will
* cause the ps_ok line to go active as well as PS present in the
* new supply. If we detect an insertion clear
* interrupts, disable interrupts, wait for a couple of seconds
* come back and see if the PSOK bit is set, PS_PRESENT is set
* and the share fail interrupts are gone. If not this is a
* real load share fail event.
* Called with mutex held
*/
static void
envctrl_PS_intr_service(struct envctrlunit *unitp)
{
ASSERT(MUTEX_HELD(&unitp->umutex));
if (unitp->current_mode == ENVCTRL_DIAG_MODE) {
return;
}
/*
* setup a timeout thread to poll the ps after a
* couple of seconds. This allows for the PS to settle
* and doesn't report false errors on a hotplug
*/
unitp->pshotplug_id = (timeout(envctrl_pshotplug_poll,
(caddr_t)unitp, pshotplug_timeout_hz));
}
static void
envctrl_init_bus(struct envctrlunit *unitp)
{
ehc_init_pcf8584((struct ehc_envcunit *)unitp);
/*
* Clear the interrupt latches
*/
envctrl_intr_latch_clr(unitp);
envctrl_reset_dflop(unitp);
envctrl_enable_devintrs(unitp);
}
/* called with mutex held */
static void
envctrl_reset_dflop(struct envctrlunit *unitp)
{
int status;
uint8_t value;
ASSERT(MUTEX_HELD(&unitp->umutex));
value = ENVCTRL_UE250_DFLOP_INIT0;
status = envctrl_write_chip(unitp, ENVCTRL_PCF8574A, EHC_DEV0,
0, &value, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Write to PCF8574A (DFLOP_INIT0) failed",
driver_name, unitp->instance);
}
value = ENVCTRL_UE250_DFLOP_INIT1;
status = envctrl_write_chip(unitp, ENVCTRL_PCF8574A, EHC_DEV0,
0, &value, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Write to PCF8574A (DFLOP_INIT1) failed",
driver_name, unitp->instance);
}
}
/* called with mutex held */
static void
envctrl_enable_devintrs(struct envctrlunit *unitp)
{
int status;
uint8_t value;
ASSERT(MUTEX_HELD(&unitp->umutex));
value = ENVCTRL_UE250_DEVINTR_INIT0;
status = envctrl_write_chip(unitp, ENVCTRL_PCF8574A, EHC_DEV0,
0, &value, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Write to PCF8574A (INTR_INIT0) failed",
driver_name, unitp->instance);
}
value = ENVCTRL_UE250_DEVINTR_INIT1;
status = envctrl_write_chip(unitp, ENVCTRL_PCF8574A, EHC_DEV0,
0, &value, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Write to PCF8574A (INTR_INIT1) failed",
driver_name, unitp->instance);
}
}
static void
envctrl_intr_latch_clr(struct envctrlunit *unitp)
{
int status;
uint8_t value;
ASSERT(MUTEX_HELD(&unitp->umutex));
value = ENVCTRL_UE250_INTR_LATCH_INIT0;
status = envctrl_write_chip(unitp, ENVCTRL_PCF8574A, EHC_DEV0,
0, &value, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Write to PCF8574A (INTR_LATCH0) failed",
driver_name, unitp->instance);
}
value = ENVCTRL_UE250_INTR_LATCH_INIT1;
status = envctrl_write_chip(unitp, ENVCTRL_PCF8574A, EHC_DEV0,
0, &value, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Write to PCF8574A (INTR_LATCH1) failed",
driver_name, unitp->instance);
}
}
/* Called with unitp mutex held */
static void
envctrl_ps_probe(struct envctrlunit *unitp)
{
uint8_t recv_data, fpmstat;
int i, j;
int ps_error = 0, ps_present_port, power_ok_port;
int status;
ASSERT(MUTEX_HELD(&unitp->umutex));
unitp->num_ps_present = 0;
status = envctrl_read_chip(unitp, ENVCTRL_PCF8574A, EHC_DEV1,
0, &recv_data, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Read of PCF8574 (PS) failed",
driver_name, unitp->instance);
return;
}
for (i = 0, j = 0; i < ENVCTRL_UE250_MAXPS; i++) {
unitp->ps_kstats[i].slot = -1;
/*
* Port 0 = PS0 Present
* Port 1 = PS1 Present
* Port 2 = SPARE
* Port 3 = SPARE
* Port 4 = PS0 OK
* Port 5 = PS1 OK
* Port 6 = SPARE
* Port 7 = SPARE
*/
/*
* Port 0 = PS Present
* Port is pulled LOW "0" to indicate
* present.
*/
switch (i) {
case 0:
ps_present_port = EHC_PCF8574_PORT0;
power_ok_port = EHC_PCF8574_PORT4;
break;
case 1:
ps_present_port = EHC_PCF8574_PORT1;
power_ok_port = EHC_PCF8574_PORT5;
break;
}
if (!(recv_data & ps_present_port)) {
/* update unit kstat array */
unitp->ps_kstats[j].slot = i;
++unitp->num_ps_present;
if (pspr[i] == 0) {
cmn_err(CE_NOTE,
"Power Supply %d inserted\n", i);
}
pspr[i] = 1;
if (!(recv_data & power_ok_port)) {
cmn_err(CE_WARN,
"Power Supply %d NOT okay\n", i);
unitp->ps_kstats[j].ps_ok = B_FALSE;
ps_error++;
psok[i] = 0;
} else {
unitp->ps_kstats[j].ps_ok = B_TRUE;
if (psok[i] == 0)
cmn_err(CE_NOTE,
"Power Supply %d okay\n", i);
psok[i] = 1;
}
if (!(recv_data & EHC_PCF8574_PORT2)) {
cmn_err(CE_WARN,
"PS %d Shouln't interrupt\n", i);
ps_error++;
}
if (!(recv_data & EHC_PCF8574_PORT3)) {
cmn_err(CE_WARN,
"PS %d Shouln't interrupt\n", i);
ps_error++;
}
if (!(recv_data & EHC_PCF8574_PORT6)) {
cmn_err(CE_WARN,
"PS %d Shouln't interrupt\n", i);
ps_error++;
}
if (!(recv_data & EHC_PCF8574_PORT7)) {
cmn_err(CE_WARN,
"PS %d Shouln't interrupt\n", i);
ps_error++;
}
j++;
} else {
if (pspr[i] == 1) {
cmn_err(CE_NOTE,
"Power Supply %d removed\n", i);
}
pspr[i] = 0;
}
}
status = envctrl_get_fpm_status(unitp, &fpmstat);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Read of Front Status Panel LEDs failed",
driver_name, unitp->instance);
}
if (ps_error) {
fpmstat |= (ENVCTRL_UE250_FSP_PS_ERR |
ENVCTRL_UE250_FSP_GEN_ERR);
} else {
if (envctrl_isother_fault_led(unitp, fpmstat,
ENVCTRL_UE250_FSP_PS_ERR)) {
fpmstat &= ~(ENVCTRL_UE250_FSP_PS_ERR);
} else {
fpmstat &= ~(ENVCTRL_UE250_FSP_PS_ERR |
ENVCTRL_UE250_FSP_GEN_ERR);
}
}
status = envctrl_set_fsp(unitp, &fpmstat);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN,
"%s%d: Setting of Front Status Panel LEDs failed",
driver_name, unitp->instance);
}
if (ps_error) {
power_flt_led_lit = 1;
} else {
power_flt_led_lit = 0;
}
}
/*
* consider key switch position when handling an abort sequence
*/
static void
envctrl_abort_seq_handler(char *msg)
{
struct envctrlunit *unitp;
int i;
uint8_t secure = 0;
/*
* Find the instance of the device available on this host.
* Note that there may be only one, but the instance may
* not be zero.
*/
for (i = 0; i < MAX_DEVS; i++) {
if (unitp = (struct envctrlunit *)
ddi_get_soft_state(envctrlsoft_statep, i))
break;
}
ASSERT(unitp);
secure = unitp->encl_kstats.value;
if ((secure & ENVCTRL_UE250_FSP_KEYMASK) ==
ENVCTRL_UE250_FSP_KEYLOCKED) {
cmn_err(CE_CONT,
"%s%d: ignoring debug enter sequence\n",
driver_name, unitp->instance);
} else {
if (envctrl_debug_flags) {
cmn_err(CE_CONT, "%s%d: allowing debug enter\n",
driver_name, unitp->instance);
}
debug_enter(msg);
}
}
/*
* get the front Panel module LED and keyswitch status.
* this part is addressed at 0x7C on the i2c bus.
* called with mutex held
*/
static int
envctrl_get_fpm_status(struct envctrlunit *unitp, uint8_t *val)
{
uint8_t recv_data;
int status;
ASSERT(MUTEX_HELD(&unitp->umutex));
status = envctrl_read_chip(unitp, ENVCTRL_PCF8574A, EHC_DEV6,
0, &recv_data, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Read from PCF8574A (FSP) failed",
driver_name, unitp->instance);
return (status);
}
recv_data = ~recv_data;
if (val != (uint8_t *)NULL)
*val = recv_data;
/* Update kstats */
unitp->encl_kstats.value = recv_data;
return (status);
}
static int
envctrl_set_fsp(struct envctrlunit *unitp, uint8_t *val)
{
uint8_t value;
int status = DDI_SUCCESS;
uint8_t confirm_val = 0, confirm_val_hold;
int confirm_count = 0, confirm_max = 20;
ASSERT(MUTEX_HELD(&unitp->umutex));
value = ENVCTRL_UE250_FSP_OFF; /* init all values to off */
/*
* strip off bits that are R/O
*/
value = (~(ENVCTRL_UE250_FSP_KEYMASK | ENVCTRL_UE250_FSP_POMASK) &
(*val));
confirm_val_hold = value;
value = ~value;
while (confirm_count < confirm_max) {
status = envctrl_write_chip(unitp, ENVCTRL_PCF8574A, EHC_DEV6,
0, &value, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Write to PCF8574A (FSP) failed",
driver_name, unitp->instance);
break;
} else {
/*
* Sometimes the i2c hardware status is not
* completely dependable as far as reporting
* a condition where the set does not take
* place. So we read back the set value to
* confirm what we set.
*/
status = envctrl_get_fpm_status(unitp, &confirm_val);
confirm_val = ~(ENVCTRL_UE250_FSP_KEYMASK |
ENVCTRL_UE250_FSP_POMASK) & confirm_val;
if (status == DDI_FAILURE) {
cmn_err(CE_WARN,
"%s%d: Read of PCF8574A (FSP) failed",
driver_name, unitp->instance);
break;
} else if (confirm_val != confirm_val_hold) {
confirm_count++;
drv_usecwait(1000);
continue;
} else
/*
* Set was confirmed.
*/
break;
}
}
if (confirm_count == confirm_max)
status = DDI_FAILURE;
return (status);
}
static int
envctrl_get_dskled(struct envctrlunit *unitp, struct envctrl_chip *chip)
{
int status;
ASSERT(MUTEX_HELD(&unitp->umutex));
if (chip->chip_num != EHC_DEV7 ||
chip->type != ENVCTRL_PCF8574A) {
return (DDI_FAILURE);
}
status = envctrl_read_chip(unitp, ENVCTRL_PCF8574A, EHC_DEV7,
0, &chip->val, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Read of PCF8574A (DISKFL) failed",
driver_name, unitp->instance);
}
chip->val = ~chip->val;
return (status);
}
static int
envctrl_set_dskled(struct envctrlunit *unitp, struct envctrl_chip *chip)
{
uint8_t val;
int status;
struct envctrl_chip confirm_chip;
uint8_t confirm_val_hold;
int confirm_count = 0, confirm_max = 20;
/*
* We need to check the type of disk led being set. If it
* is a 4 slot backplane then the upper 4 bits (7, 6, 5, 4) are
* invalid.
*/
ASSERT(MUTEX_HELD(&unitp->umutex));
if (chip->chip_num != EHC_DEV7)
return (DDI_FAILURE);
if (chip->type != ENVCTRL_PCF8574A)
return (DDI_FAILURE);
status = envctrl_read_chip(unitp, ENVCTRL_PCF8574A, EHC_DEV6,
0, &val, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Read of PCF8574A (FSP) failed",
driver_name, unitp->instance);
return (status);
}
val = ~val;
if ((chip->val & 0x3F) == 0) {
if (!(envctrl_isother_fault_led(unitp, val,
ENVCTRL_UE250_FSP_DISK_ERR))) {
val &= ~(ENVCTRL_UE250_FSP_DISK_ERR);
} else {
val &= ~(ENVCTRL_UE250_FSP_DISK_ERR |
ENVCTRL_UE250_FSP_GEN_ERR);
}
val = (val & ~(ENVCTRL_UE250_FSP_DISK_ERR |
ENVCTRL_UE250_FSP_GEN_ERR));
} else {
val = (val | (ENVCTRL_UE250_FSP_DISK_ERR |
ENVCTRL_UE250_FSP_GEN_ERR));
}
status = envctrl_set_fsp(unitp, &val);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Write to PCF8574A (FSP) failed",
driver_name, unitp->instance);
return (status);
}
status = envctrl_read_chip(unitp, ENVCTRL_PCF8574A, EHC_DEV5,
0, &val, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Read of PCF8574A (DISKFL) failed",
driver_name, unitp->instance);
return (status);
}
envctrl_update_disk_kstats(unitp, val, ~(chip->val));
/*
* we take the ones compliment of the val passed in
* because the hardware thinks that a "low" or "0"
* is the way to indicate a fault. of course software
* knows that a 1 is a TRUE state or fault. ;-)
*/
confirm_val_hold = chip->val;
chip->val = ~(chip->val);
while (confirm_count < confirm_max) {
status = envctrl_write_chip(unitp, ENVCTRL_PCF8574A, EHC_DEV7,
0, &chip->val, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Write PCF8574A (DISKFL) failed",
driver_name, unitp->instance);
return (status);
} else {
/*
* Sometimes the i2c hardware status is not
* completely dependable as far as reporting
* a condition where the set does not take
* place. So we read back the set value to
* confirm what we set.
*/
confirm_chip.type = chip->type;
confirm_chip.chip_num = chip->chip_num;
confirm_chip.index = chip->index;
status = envctrl_get_dskled(unitp, &confirm_chip);
if (status != DDI_SUCCESS) {
return (status);
} else if (confirm_chip.val != confirm_val_hold) {
confirm_count++;
drv_usecwait(1000);
continue;
} else
/*
* Set was confirmed.
*/
break;
}
}
if (confirm_count == confirm_max)
return (DDI_FAILURE);
return (DDI_SUCCESS);
}
/*
* After setting the fan speed, we read back the fan speed to confirm
* that the new value is within an acceptable range, else we retry.
* We do not confirm the fan speed if the set value is below the
* hardware determined speed (based on system temeratures).
*/
static int
envctrl_set_fanspeed(struct envctrlunit *unitp, struct envctrl_chip *fanspeed)
{
int readback_speed, max_speed;
int status;
int confirm_count = 0, confirm_max = 20;
uint8_t fanspeed_hold;
fanspeed_hold = fanspeed->val;
while (confirm_count < confirm_max) {
status = envctrl_write_chip(unitp, ENVCTRL_PCF8591,
EHC_DEV2, 0, &fanspeed->val, 1);
if (status == DDI_FAILURE) {
envctrl_fan_fail_service(unitp);
cmn_err(CE_WARN,
"%s%d: Set fanspeed failed", driver_name,
unitp->instance);
return (status);
} else {
drv_usecwait(100000);
envctrl_update_fanspeed(unitp);
readback_speed = unitp->fan_kstats.fanspeed;
if (fanspeed_hold > idle_fanspeed) {
max_speed =
(fanspeed->val + FAN_DRIFT >
MAX_FAN_SPEED) ? MAX_FAN_SPEED :
(fanspeed->val + FAN_DRIFT);
if ((readback_speed < fanspeed->val -
FAN_DRIFT) ||
(readback_speed > max_speed)) {
confirm_count++;
drv_usecwait(1000);
continue;
}
}
break;
}
}
if (confirm_count == confirm_max)
return (DDI_FAILURE);
return (DDI_SUCCESS);
}
static void
envctrl_add_kstats(struct envctrlunit *unitp)
{
ASSERT(MUTEX_HELD(&unitp->umutex));
if ((unitp->enclksp = kstat_create(ENVCTRL_MODULE_NAME, unitp->instance,
ENVCTRL_KSTAT_ENCL, "misc", KSTAT_TYPE_RAW,
sizeof (unitp->encl_kstats),
KSTAT_FLAG_PERSISTENT)) == NULL) {
cmn_err(CE_WARN, "%s%d: encl raw kstat_create failed",
driver_name, unitp->instance);
return;
}
unitp->enclksp->ks_update = envctrl_encl_kstat_update;
unitp->enclksp->ks_private = (void *)unitp;
kstat_install(unitp->enclksp);
if ((unitp->fanksp = kstat_create(ENVCTRL_MODULE_NAME, unitp->instance,
ENVCTRL_KSTAT_FANSTAT, "misc", KSTAT_TYPE_RAW,
sizeof (unitp->fan_kstats),
KSTAT_FLAG_PERSISTENT | KSTAT_FLAG_WRITABLE)) == NULL) {
cmn_err(CE_WARN, "%s%d: fans kstat_create failed",
driver_name, unitp->instance);
return;
}
unitp->fanksp->ks_update = envctrl_fanstat_kstat_update;
unitp->fanksp->ks_private = (void *)unitp;
kstat_install(unitp->fanksp);
if ((unitp->psksp = kstat_create(ENVCTRL_MODULE_NAME, unitp->instance,
ENVCTRL_KSTAT_PSNAME2, "misc", KSTAT_TYPE_RAW,
sizeof (unitp->ps_kstats),
KSTAT_FLAG_PERSISTENT)) == NULL) {
cmn_err(CE_WARN, "%s%d: ps name kstat_create failed",
driver_name, unitp->instance);
return;
}
unitp->psksp->ks_update = envctrl_ps_kstat_update;
unitp->psksp->ks_private = (void *)unitp;
kstat_install(unitp->psksp);
if ((unitp->tempksp = kstat_create(ENVCTRL_MODULE_NAME,
unitp->instance, ENVCTRL_KSTAT_TEMPERATURE, "misc", KSTAT_TYPE_RAW,
sizeof (unitp->temp_kstats),
KSTAT_FLAG_PERSISTENT)) == NULL) {
cmn_err(CE_WARN, "%s%d: temp name kstat_create failed",
driver_name, unitp->instance);
return;
}
unitp->tempksp->ks_update = envctrl_temp_kstat_update;
unitp->tempksp->ks_private = (void *)unitp;
kstat_install(unitp->tempksp);
if ((unitp->diskksp = kstat_create(ENVCTRL_MODULE_NAME,
unitp->instance, ENVCTRL_KSTAT_DISK, "misc", KSTAT_TYPE_RAW,
sizeof (unitp->disk_kstats),
KSTAT_FLAG_PERSISTENT)) == NULL) {
cmn_err(CE_WARN, "%s%d: disk name kstat_create failed",
driver_name, unitp->instance);
return;
}
unitp->diskksp->ks_update = envctrl_disk_kstat_update;
unitp->diskksp->ks_private = (void *)unitp;
kstat_install(unitp->diskksp);
}
static int
envctrl_ps_kstat_update(kstat_t *ksp, int rw)
{
struct envctrlunit *unitp;
char *kstatp;
unitp = (struct envctrlunit *)ksp->ks_private;
mutex_enter(&unitp->umutex);
ASSERT(MUTEX_HELD(&unitp->umutex));
kstatp = (char *)ksp->ks_data;
if (rw == KSTAT_WRITE) {
mutex_exit(&unitp->umutex);
return (EACCES);
} else {
unitp->psksp->ks_ndata = unitp->num_ps_present;
bcopy((caddr_t)&unitp->ps_kstats, kstatp,
sizeof (unitp->ps_kstats));
}
mutex_exit(&unitp->umutex);
return (DDI_SUCCESS);
}
static int
envctrl_fanstat_kstat_update(kstat_t *ksp, int rw)
{
struct envctrlunit *unitp;
char *kstatp;
kstatp = (char *)ksp->ks_data;
unitp = (struct envctrlunit *)ksp->ks_private;
mutex_enter(&unitp->umutex);
ASSERT(MUTEX_HELD(&unitp->umutex));
if (rw == KSTAT_WRITE) {
mutex_exit(&unitp->umutex);
return (EACCES);
} else {
unitp->fanksp->ks_ndata = unitp->num_fans_present;
bcopy((caddr_t)&unitp->fan_kstats, kstatp,
sizeof (unitp->fan_kstats));
}
mutex_exit(&unitp->umutex);
return (DDI_SUCCESS);
}
static int
envctrl_encl_kstat_update(kstat_t *ksp, int rw)
{
struct envctrlunit *unitp;
char *kstatp;
int status;
kstatp = (char *)ksp->ks_data;
unitp = (struct envctrlunit *)ksp->ks_private;
mutex_enter(&unitp->umutex);
ASSERT(MUTEX_HELD(&unitp->umutex));
if (rw == KSTAT_WRITE) {
mutex_exit(&unitp->umutex);
return (EACCES);
} else {
unitp->enclksp->ks_ndata = unitp->num_encl_present;
status = envctrl_get_fpm_status(unitp, (uint8_t *)NULL);
if (status == DDI_SUCCESS)
bcopy((caddr_t)&unitp->encl_kstats, kstatp,
sizeof (unitp->encl_kstats));
}
mutex_exit(&unitp->umutex);
return (DDI_SUCCESS);
}
static int
envctrl_temp_kstat_update(kstat_t *ksp, int rw)
{
struct envctrlunit *unitp;
char *kstatp;
kstatp = (char *)ksp->ks_data;
unitp = (struct envctrlunit *)ksp->ks_private;
mutex_enter(&unitp->umutex);
ASSERT(MUTEX_HELD(&unitp->umutex));
if (rw == KSTAT_WRITE) {
mutex_exit(&unitp->umutex);
return (EACCES);
} else {
unitp->tempksp->ks_ndata = unitp->num_temps_present;
bcopy((caddr_t)unitp->temp_kstats, kstatp,
sizeof (unitp->temp_kstats));
}
mutex_exit(&unitp->umutex);
return (DDI_SUCCESS);
}
static int
envctrl_disk_kstat_update(kstat_t *ksp, int rw)
{
struct envctrlunit *unitp;
char *kstatp;
kstatp = (char *)ksp->ks_data;
unitp = (struct envctrlunit *)ksp->ks_private;
mutex_enter(&unitp->umutex);
ASSERT(MUTEX_HELD(&unitp->umutex));
if (rw == KSTAT_WRITE) {
mutex_exit(&unitp->umutex);
return (EACCES);
} else {
unitp->diskksp->ks_ndata = unitp->num_disks_present;
bcopy((caddr_t)unitp->disk_kstats, kstatp,
sizeof (unitp->disk_kstats));
}
mutex_exit(&unitp->umutex);
return (DDI_SUCCESS);
}
static void
envctrl_init_encl_kstats(struct envctrlunit *unitp)
{
uint8_t val;
int status;
ASSERT(MUTEX_HELD(&unitp->umutex));
status = envctrl_read_chip(unitp, ENVCTRL_PCF8574A, EHC_DEV6,
0, &val, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Read of PCF8574A (FSP) failed",
driver_name, unitp->instance);
return;
}
unitp->encl_kstats.value = val;
}
static void
envctrl_check_disk_kstats(struct envctrlunit *unitp)
{
uint8_t diskpr, diskfl;
int status;
ASSERT(MUTEX_HELD(&unitp->umutex));
status = envctrl_read_chip(unitp, ENVCTRL_PCF8574A, EHC_DEV5,
0, &diskpr, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Read of PCF8574A (DISKPR) failed",
driver_name, unitp->instance);
}
status = envctrl_read_chip(unitp, ENVCTRL_PCF8574A, EHC_DEV7,
0, &diskfl, 1);
if (status == DDI_FAILURE) {
cmn_err(CE_WARN, "%s%d: Read of PCF8574A (DISKFL) failed",
driver_name, unitp->instance);
}
envctrl_update_disk_kstats(unitp, diskpr, diskfl);
}
static void
envctrl_update_disk_kstats(struct envctrlunit *unitp, uint8_t diskpr,
uint8_t diskfl)
{
int i, j, count = 0;
DPRINTF1("diskpr = %X, diskfl = %X\n", diskpr, diskfl);
for (i = 0, j = 1; i < ENVCTRL_UE250_MAX_DISKS; i++, j = j << 1) {
if (!(diskpr & j)) {
if (!(diskfl & j))
unitp->disk_kstats[count].disk_ok = 0;
else
unitp->disk_kstats[count].disk_ok = 1;
unitp->disk_kstats[count].slot = i;
count++;
}
}
unitp->num_disks_present = count;
}
static void
envctrl_probe_cpus(struct envctrlunit *unitp)
{
int instance;
/*
* The cpu search is as follows:
* If there is only 1 CPU module it is named as
* SUNW,UltraSPARC. If this is a match we still don't
* know what slot the cpu module is in therefore
* we need to check the "upa-portid" property.
* If we have more than 1 cpu, then they are appended by
* instance numbers and slot locations. e.g.
* SUNW,UltraSPARC@1,0 (slot 1). it would have been
* nice to have the naming consistent for one CPU e.g.
* SUNW,UltraSPARC@0,0...sigh
*/
for (instance = 0; instance < ENVCTRL_MAX_CPUS; instance++) {
unitp->cpu_pr_location[instance] = B_FALSE;
}
ddi_walk_devs(ddi_root_node(), envctrl_match_cpu, unitp);
}
static int
envctrl_match_cpu(dev_info_t *dip, void *arg)
{
int cpu_slot;
char name[32];
char name1[32];
struct envctrlunit *unitp = (struct envctrlunit *)arg;
(void) sprintf(name, "%s", ENVCTRL_ULTRA1CPU_STRING);
(void) sprintf(name1, "%s", ENVCTRL_ULTRA2CPU_STRING);
if ((strcmp(ddi_node_name(dip), name) == 0) ||
(strcmp(ddi_node_name(dip), name1) == 0)) {
if ((cpu_slot = (int)ddi_getprop(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS, "upa-portid",
-1)) == -1) {
cmn_err(CE_WARN, "%s%d: no cpu upa-portid",
driver_name, unitp->instance);
} else {
unitp->cpu_pr_location[cpu_slot] = B_TRUE;
unitp->num_cpus_present++;
}
}
return (DDI_WALK_CONTINUE);
}
/*
* This routine returns TRUE if some other error condition
* has set the GEN_ERR FAULT LED. Tp further complicate this
* LED panel we have overloaded the GEN_ERR LED to indicate
* that a fan fault has occurred without having a fan fault
* LED as does all other error conditions. So we just take the
* software state and return true. The whole purpose of this functon
* is to tell us wehther or not we can shut off the GEN_FAULT LED.
* NOTE: this ledval is usually one of the following FSP vals
* EXCEPT in the case of the fan fail.. we pass in a "0".
*/
static int
envctrl_isother_fault_led(struct envctrlunit *unitp, uint8_t fspval,
uint8_t thisled)
{
int status = B_FALSE;
if (fspval != 0) {
fspval = (fspval & ~(thisled));
}
if ((unitp->fan_failed == B_TRUE) && thisled != 0) {
status = B_TRUE;
} else if (fspval & ENVCTRL_UE250_FSP_DISK_ERR) {
status = B_TRUE;
} else if (fspval & ENVCTRL_UE250_FSP_PS_ERR) {
status = B_TRUE;
} else if (fspval & ENVCTRL_UE250_FSP_TEMP_ERR) {
status = B_TRUE;
}
return (status);
}
static void
envctrl_pshotplug_poll(void *arg)
{
struct envctrlunit *unitp = (struct envctrlunit *)arg;
mutex_enter(&unitp->umutex);
envctrl_ps_probe(unitp);
mutex_exit(&unitp->umutex);
}