/* * 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. */ /* * ENVCTRL_ Environment Monitoring driver for i2c * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* req. by dev_ops flags MTSAFE etc. */ #include /* for modldrv */ #include /* ddi_create_minor_node S_IFCHR */ #include /* for open params. */ #include /* for read/write */ #include /* Environment header */ /* driver entry point fn definitions */ static int envctrl_open(queue_t *, dev_t *, int, int, cred_t *); static int envctrl_close(queue_t *, int, cred_t *); 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 */ static void envctrl_init_bus(struct envctrlunit *); static int envctrl_xmit(struct envctrlunit *, caddr_t *, int); static void envctrl_recv(struct envctrlunit *, caddr_t *, int); static void envctrl_get_sys_temperatures(struct envctrlunit *, uint8_t *); static int envctrl_get_lm75_temp(struct envctrlunit *); static int envctrl_get_ps_temp(struct envctrlunit *, uint8_t); static int envctrl_get_cpu_temp(struct envctrlunit *, int); static void envctrl_fan_fail_service(struct envctrlunit *); static void envctrl_PS_intr_service(struct envctrlunit *, uint8_t); 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_reset_dflop(struct envctrlunit *); static void envctrl_enable_devintrs(struct envctrlunit *); static void envctrl_stop_clock(struct envctrlunit *); static void envctrl_reset_watchdog(struct envctrlunit *, uint8_t *); static void envctrl_abort_seq_handler(char *msg); static uint8_t envctrl_get_fpm_status(struct envctrlunit *); static void envctrl_set_fsp(struct envctrlunit *, uint8_t *); static int envctrl_set_dskled(struct envctrlunit *, struct envctrl_pcf8574_chip *); static int envctrl_get_dskled(struct envctrlunit *, struct envctrl_pcf8574_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); /* 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 void envctrl_init_fan_kstats(struct envctrlunit *); static void envctrl_init_encl_kstats(struct envctrlunit *); static void envctrl_add_encl_kstats(struct envctrlunit *, int, int, uint8_t); static void envctrl_mod_encl_kstats(struct envctrlunit *, int, int, uint8_t); /* Streams Routines */ static int envctrl_wput(queue_t *, mblk_t *); /* External routines */ 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; /* Local Variables */ /* Indicates whether or not the overtemp thread has been started */ static int envctrl_debug_flags = 0; static int envctrl_afb_present = 0; static int envctrl_power_off_overide = 0; static int envctrl_max_retries = 100; static int envctrl_allow_detach = 0; static int envctrl_numcpus = 1; static int envctrl_p0_enclosure = 0; /* set to 1 if it is a P0 */ 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 int controller_present[] = {-1, -1, -1}; #ifdef MULTIFAN static int envctrl_fan_debug = 0; #endif static int eHc_debug = 0; static int power_supply_previous_state[] = {-1, -1, -1}; extern void pci_thermal_rem_intr(dev_info_t *, uint_t); #define LOOP_TIMEOUT 25 #define INIT_FAN_VAL 35 #define DCMNERR if (eHc_debug & 0x1) cmn_err #define DCMN2ERR if (eHc_debug & 0x2) cmn_err #define MAX_FAN_FAIL_RETRY 3 uint8_t backaddrs[] = {ENVCTRL_PCF8574_DEV0, ENVCTRL_PCF8574_DEV1, ENVCTRL_PCF8574_DEV2}; struct module_info envctrlinfo = { /* id, name, min pkt siz, max pkt siz, hi water, low water */ 42, "envctrl", 0, 2048, (1024 * 20), (1024 * 1) }; static struct qinit envctrl_rinit = { putq, NULL, envctrl_open, envctrl_close, NULL, &envctrlinfo, NULL }; static struct qinit envctrl_wint = { envctrl_wput, NULL, envctrl_open, envctrl_close, NULL, &envctrlinfo, NULL }; struct streamtab envctrl_str_info = { &envctrl_rinit, &envctrl_wint, NULL, NULL }; static struct cb_ops envctrl_cb_ops = { nodev, /* cb_open */ nodev, /* cb_close */ nodev, /* cb_strategy */ nodev, /* cb_print */ nodev, /* cb_dump */ nodev, /* cb_read */ nodev, /* cb_write */ nodev, /* cb_ioctl */ nodev, /* cb_devmap */ nodev, /* cb_mmap */ nodev, /* cb_segmap */ nochpoll, /* cb_chpoll */ ddi_prop_op, /* cb_prop_op */ &envctrl_str_info, /* cb_stream */ D_MP /* cb_flag */ }; /* * Declare ops vectors for auto configuration. */ struct dev_ops envctrl_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 ENVCTRL_driver", &envctrl_ops, }; static struct modlinkage envctrlmodlinkage = { MODREV_1, &envctrlmodldrv, 0 }; /* * The following defines are for the i2c protocol routines. * This section of defines should be removed once the envctrl_targets.c * file is included. */ #define EHC_SUCCESS 0 #define EHC_FAILURE (-1) #define EHC_NO_SLAVE_ACK 3 #define EHC_MAX_WAIT 7 /* decimal */ #define EHC_S1_PIN 0x80 #define EHC_S1_ES1 0x20 #define EHC_S1_ES0 0x40 #define EHC_S1_NBB 0x01 #define EHC_S1_ACK 0x01 #define EHC_S1_STA 0x04 #define EHC_S1_STO 0x02 #define EHC_S1_LRB 0x08 #define EHC_S1_BER 0x10 #define EHC_S1_LAB 0x02 #define EHC_S0_OWN 0x55 #define EHC_S0_CLK 0x1c #define EHC_BYTE_READ 0x01 #define EHC_LONGEST_MSG 1000 /* decimal */ /* * PCF8591 Chip Used for temperature sensors * * Addressing Register definition. * A0-A2 valid range is 0-7 * * 7 6 5 4 3 2 1 0 * ------------------------------------------------ * | 1 | 0 | 0 | 1 | A2 | A1 | A0 | R/W | * ------------------------------------------------ */ #define EHC_PCF8591_MAX_DEVS 0x08 #define EHC_DEV0 0x00 #define EHC_DEV1 0x02 #define EHC_DEV2 0x04 #define EHC_DEV3 0x06 #define EHC_DEV4 0x08 #define EHC_DEV5 0x0A #define EHC_DEV6 0x0C #define EHC_DEV7 0x0E /* * CONTROL OF CHIP * PCF8591 Temp sensing control register definitions * * 7 6 5 4 3 2 1 0 * --------------------------------------------- * | 0 | AOE | X | X | 0 | AIF | X | X | * --------------------------------------------- * AOE = Analog out enable.. not used on out implementation * 5 & 4 = Analog Input Programming.. see data sheet for bits.. * * AIF = Auto increment flag * bits 1 & 0 are for the Chennel number. */ #define EHC_PCF8591_ANALOG_OUTPUT_EN 0x40 #define EHC_PCF8591_ANALOG_INPUT_EN 0x00 #define EHC_PCF8591_READ_BIT 0x01 #define EHC_PCF8591_AUTO_INCR 0x04 #define EHC_PCF8591_OSCILATOR 0x40 #define EHC_PCF8591_MAX_PORTS 0x04 #define EHC_PCF8591_CH_0 0x00 #define EHC_PCF8591_CH_1 0x01 #define EHC_PCF8591_CH_2 0x02 #define EHC_PCF8591_CH_3 0x03 /* * PCF8574 Fan Fail, Power Supply Fail Detector * This device is driven by interrupts. Each time it interrupts * you must look at the CSR to see which ports caused the interrupt * they are indicated by a 1. * * Address map of this chip * * ------------------------------------------- * | 0 | 1 | 1 | 1 | A2 | A1 | A0 | 0 | * ------------------------------------------- * */ #define EHC_PCF8574_PORT0 0x01 #define EHC_PCF8574_PORT1 0x02 #define EHC_PCF8574_PORT2 0x04 #define EHC_PCF8574_PORT3 0x08 #define EHC_PCF8574_PORT4 0x10 #define EHC_PCF8574_PORT5 0x20 #define EHC_PCF8574_PORT6 0x40 #define EHC_PCF8574_PORT7 0x80 /* * Defines for the PCF8583 Clock Calendar Chip. */ #define EHC_PCF8583_READ_BIT 0x01 #define ALARM_CTR_REG_MINS 0x03 #define ALARM_REG_MINS 0x0B #define ALARM_TIMER_REG 0x0F struct eHc_pcd8584_regs { uint8_t s0; /* Own Address S0' */ uint8_t s1; /* Control Status register */ uint8_t clock_s2; /* Clock programming register */ }; struct eHc_envcunit { struct eHc_pcd8584_regs *bus_ctl_regs; ddi_acc_handle_t ctlr_handle; kmutex_t umutex; }; /* * Prototypes for static routines */ static int eHc_write_tda8444(struct eHc_envcunit *, int, int, int, uint8_t *, int); static int eHc_read_pcf8591(struct eHc_envcunit *, int, int, int, int, int, uint8_t *, int); static int eHc_read_pcf8574a(struct eHc_envcunit *, int, uint8_t *, int); static int eHc_write_pcf8574a(struct eHc_envcunit *, int, uint8_t *, int); static int eHc_read_pcf8574(struct eHc_envcunit *, int, uint8_t *, int); static int eHc_write_pcf8574(struct eHc_envcunit *, int, uint8_t *, int); static int eHc_read_lm75(struct eHc_envcunit *, int, uint8_t *, int); static int eHc_write_pcf8583(struct eHc_envcunit *, int, uint8_t *, int); static int eHc_start_pcf8584(struct eHc_envcunit *, uint8_t); static void eHc_stop_pcf8584(struct eHc_envcunit *); static int eHc_read_pcf8584(struct eHc_envcunit *, uint8_t *); static int eHc_write_pcf8584(struct eHc_envcunit *, uint8_t); static int eHc_after_read_pcf8584(struct eHc_envcunit *, uint8_t *); /* * End of i2c protocol definitions section */ int _init(void) { int error; if ((error = mod_install(&envctrlmodlinkage)) == 0) { (void) ddi_soft_state_init(&envctrlsoft_statep, sizeof (struct envctrlunit), 1); } return (error); } int _fini(void) { 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) { int instance; char name[16]; uint8_t fspval; struct envctrlunit *unitp; struct ddi_device_acc_attr attr; int *reg_prop; uchar_t *creg_prop; uint_t len, tblsz; int i, cputemp, status; uint8_t buf[3]; 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; mutex_exit(&unitp->umutex); unitp->initting = B_TRUE; envctrl_init_bus(unitp); unitp->initting = B_FALSE; mutex_enter(&unitp->umutex); 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(OVERTEMP_TIMEOUT_USEC); blink_timeout_hz = drv_usectohz(BLINK_TIMEOUT_USEC); pshotplug_timeout_hz = drv_usectohz(BLINK_TIMEOUT_USEC * 6); if (ddi_soft_state_zalloc(envctrlsoft_statep, instance) != 0) { cmn_err(CE_WARN, "envctrl failed to zalloc softstate\n"); 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 envctrl_pcd8584_regs), &attr, &unitp->ctlr_handle) != DDI_SUCCESS) { cmn_err(CE_WARN, "I2c failed to map in bus_control regs\n"); 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 Tazmo 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, "ddi_get_iblock_cookie FAILED \n"); 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, "envctrl_attach failed to add hard intr %d\n", 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, "envctrl_attach failed to add hard intr %d\n", instance); goto remhardintr; } (void) sprintf(name, "envctrl%d", instance); if (ddi_create_minor_node(dip, name, S_IFCHR, instance, DDI_PSEUDO, 0) == DDI_FAILURE) { ddi_remove_minor_node(dip, NULL); goto remhardintr1; } mutex_enter(&unitp->umutex); switch (ddi_getprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, ENVCTRL_LED_BLINK, -1)) { case 1: unitp->activity_led_blink = B_TRUE; break; case 0: default: unitp->activity_led_blink = B_FALSE; break; } unitp->shutdown = B_FALSE; unitp->num_ps_present = unitp->num_encl_present = 0; unitp->num_fans_present = MIN_FAN_BANKS; unitp->num_fans_failed = ENVCTRL_CHAR_ZERO; unitp->AFB_present = B_TRUE; unitp->dip = dip; #ifdef DEBUG if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, ENVCTRL_PANEL_LEDS_PR, ®_prop, &len) == DDI_PROP_SUCCESS) ddi_prop_free((void *)reg_prop); ASSERT(len != 0); len = 0; if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, ENVCTRL_PANEL_LEDS_STA, ®_prop, &len) == DDI_PROP_SUCCESS) ddi_prop_free((void *)reg_prop); ASSERT(len != 0); len = 0; if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, ENVCTRL_DISK_LEDS_STA, ®_prop, &len) == DDI_PROP_SUCCESS) ddi_prop_free((void *)reg_prop); ASSERT(len != 0); #endif /* DEBUG */ /* * if we have prom fan tables, overide the static tables in * header file. */ if (ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "cpu-fan-speeds", &creg_prop, &len) == DDI_PROP_SUCCESS) { tblsz = (sizeof (acme_cpu_fanspd) / sizeof (short)); if (len <= tblsz) { for (i = 0; i < len; i++) { acme_cpu_fanspd[i] = creg_prop[i]; } } ddi_prop_free((void *)creg_prop); } len = 0; if (ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "ps-fan-speeds", &creg_prop, &len) == DDI_PROP_SUCCESS) { tblsz = (sizeof (acme_ps_fanspd) / sizeof (short)); if (len <= tblsz) { for (i = 0; i < len; i++) { acme_ps_fanspd[i] = creg_prop[i]; } } ddi_prop_free((void *)creg_prop); } switch (ddi_getprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "fan-override", -1)) { case 1: case 2: unitp->AFB_present = B_TRUE; break; case 0: default: unitp->AFB_present = B_FALSE; break; } /* For debug */ if (envctrl_afb_present) { unitp->AFB_present = B_TRUE; } if (unitp->AFB_present == B_TRUE) unitp->num_fans_present++; /* initialize the envctrl bus controller */ mutex_exit(&unitp->umutex); unitp->initting = B_TRUE; envctrl_init_bus(unitp); unitp->initting = B_FALSE; drv_usecwait(1000); mutex_enter(&unitp->umutex); /* Initialize the PCF8583 eggtimer registers */ buf[0] = ALARM_CTR_REG_MINS; buf[1] = 0x0; status = eHc_write_pcf8583((struct eHc_envcunit *)unitp, PCF8583_BASE_ADDR | 0, buf, 2); if (status != DDI_SUCCESS) cmn_err(CE_WARN, "write to PCF8583 failed\n"); buf[0] = ALARM_REG_MINS; buf[1] = 0x58; status = eHc_write_pcf8583((struct eHc_envcunit *)unitp, PCF8583_BASE_ADDR | 0, buf, 2); if (status != DDI_SUCCESS) cmn_err(CE_WARN, "write to PCF8583 failed\n"); buf[0] = ALARM_TIMER_REG; buf[1] = 0x80; status = eHc_write_pcf8583((struct eHc_envcunit *)unitp, PCF8583_BASE_ADDR | 0, buf, 2); if (status != DDI_SUCCESS) cmn_err(CE_WARN, "write to PCF8583 failed\n"); unitp->timeout_id = 0; unitp->blink_timeout_id = 0; if (envctrl_numcpus > 1) { unitp->num_cpus_present = envctrl_numcpus; } envctrl_probe_cpus(unitp); envctrl_ps_probe(unitp); /* * clear the fan failures, if any before we do * real work */ unitp->initting = B_TRUE; envctrl_fan_fail_service(unitp); unitp->initting = B_FALSE; /* * we need to init the fan kstats before the tempr_poll */ envctrl_add_kstats(unitp); envctrl_init_fan_kstats(unitp); envctrl_init_encl_kstats(unitp); 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_FSP_ACTIVE; envctrl_set_fsp(unitp, &fspval); } #ifndef TESTBED for (i = 0; i < ENVCTRL_MAX_CPUS; i++) { if (unitp->cpu_pr_location[i] == B_TRUE) { cputemp = envctrl_get_cpu_temp(unitp, i); envctrl_add_encl_kstats(unitp, ENVCTRL_ENCL_CPUTEMPR, i, cputemp); if (cputemp >= MAX_CPU_TEMP) { if (!(envctrl_power_off_overide)) { cmn_err(CE_WARN, "CPU %d OVERHEATING!!", i); unitp->shutdown = B_TRUE; } else { cmn_err(CE_WARN, "CPU %d OVERHEATING!!", i); } } } } #else cputemp = envctrl_get_cpu_temp(unitp, 0); envctrl_add_encl_kstats(unitp, ENVCTRL_ENCL_CPUTEMPR, INSTANCE_0, cputemp); #endif 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, "envctrl_attach:failed.\n"); return (DDI_FAILURE); } static int envctrl_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { int instance; 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->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, "envctrl already suspended\n"); mutex_exit(&unitp->umutex); return (DDI_FAILURE); } unitp->suspended = 1; mutex_exit(&unitp->umutex); return (DDI_SUCCESS); default: cmn_err(CE_WARN, "envctrl suspend general fault\n"); return (DDI_FAILURE); } } /* ARGSUSED */ 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 ret; minor_t instance = getminor(dev); 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); } /* ARGSUSED */ static int envctrl_open(queue_t *q, dev_t *dev, int flag, int sflag, cred_t *credp) { struct envctrlunit *unitp; int status = 0; 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); if (flag & FWRITE) { if ((unitp->oflag & FWRITE)) { mutex_exit(&unitp->umutex); return (EBUSY); } else { unitp->oflag |= FWRITE; } } q->q_ptr = WR(q)->q_ptr = (caddr_t)unitp; /* * if device is open with O_NONBLOCK flag set, let read(2) return 0 * if no data waiting to be read. Writes will block on flow control. */ /* enable the stream */ qprocson(q); unitp->readq = RD(q); unitp->writeq = WR(q); unitp->msg = (mblk_t *)NULL; mutex_exit(&unitp->umutex); return (status); } /* ARGSUSED */ static int envctrl_close(queue_t *q, int flag, cred_t *cred_p) { struct envctrlunit *unitp; unitp = (struct envctrlunit *)q->q_ptr; mutex_enter(&unitp->umutex); unitp->oflag = B_FALSE; unitp->current_mode = ENVCTRL_NORMAL_MODE; /* disable the stream */ q->q_ptr = WR(q)->q_ptr = NULL; qprocsoff(q); mutex_exit(&unitp->umutex); return (DDI_SUCCESS); } /* * standard put procedure for envctrl */ static int envctrl_wput(queue_t *q, mblk_t *mp) { struct msgb *mp1; struct envctrlunit *unitp; struct iocblk *iocp; struct copyresp *csp; struct envctrl_tda8444t_chip *fanspeed; struct envctrl_pcf8574_chip *ledchip; struct envctrl_pcf8591_chip *temp, *a_fanspeed; struct copyreq *cqp; int cmd; unitp = (struct envctrlunit *)q->q_ptr; switch (DB_TYPE(mp)) { case M_DATA: while (mp) { DB_TYPE(mp) = M_DATA; mp1 = unlinkb(mp); mp->b_cont = NULL; if ((mp->b_wptr - mp->b_rptr) <= 0) { freemsg(mp); } else { (void) putq(q, mp); } mp = mp1; } break; case M_IOCTL: { iocp = (struct iocblk *)(void *)mp->b_rptr; cmd = iocp->ioc_cmd; switch (cmd) { case ENVCTRL_IOC_SETMODE: case ENVCTRL_IOC_GETMODE: if (iocp->ioc_count == TRANSPARENT) { mcopyin(mp, *(caddr_t *)mp->b_cont->b_rptr, sizeof (uchar_t), NULL); qreply(q, mp); } else { miocnak(q, mp, 0, EINVAL); } break; case ENVCTRL_IOC_RESETTMPR: /* * For diags, cancel the current 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); miocack(q, mp, 0, 0); } else { miocnak(q, mp, 0, EINVAL); } break; case ENVCTRL_IOC_GETTEMP: if (iocp->ioc_count == TRANSPARENT) { mcopyin(mp, *(caddr_t *)mp->b_cont->b_rptr, sizeof (struct envctrl_pcf8591_chip), NULL); qreply(q, mp); } else { miocnak(q, mp, 0, EINVAL); } break; case ENVCTRL_IOC_SETTEMP: if (unitp->current_mode == ENVCTRL_DIAG_MODE && iocp->ioc_count == TRANSPARENT) { mcopyin(mp, *(caddr_t *)mp->b_cont->b_rptr, sizeof (uint8_t), NULL); qreply(q, mp); } else { miocnak(q, mp, 0, EINVAL); } break; case ENVCTRL_IOC_SETWDT: if (unitp->current_mode == ENVCTRL_DIAG_MODE && iocp->ioc_count == TRANSPARENT) { mcopyin(mp, *(caddr_t *)mp->b_cont->b_rptr, sizeof (uint8_t), NULL); qreply(q, mp); } else { miocnak(q, mp, 0, EINVAL); } break; case ENVCTRL_IOC_SETFAN: /* * we must be in diag mode before we can * set any fan speeds. */ if (unitp->current_mode == ENVCTRL_DIAG_MODE && iocp->ioc_count == TRANSPARENT) { mcopyin(mp, *(caddr_t *)mp->b_cont->b_rptr, sizeof (struct envctrl_tda8444t_chip), NULL); qreply(q, mp); } else { miocnak(q, mp, 0, EINVAL); } break; case ENVCTRL_IOC_GETFAN: if (iocp->ioc_count == TRANSPARENT) { mcopyin(mp, *(caddr_t *)mp->b_cont->b_rptr, sizeof (struct envctrl_pcf8591_chip), NULL); qreply(q, mp); } else { miocnak(q, mp, 0, EINVAL); } break; case ENVCTRL_IOC_SETFSP: if (iocp->ioc_count == TRANSPARENT) { mcopyin(mp, *(caddr_t *)mp->b_cont->b_rptr, sizeof (uint8_t), NULL); qreply(q, mp); } else { miocnak(q, mp, 0, EINVAL); } break; case ENVCTRL_IOC_SETDSKLED: case ENVCTRL_IOC_GETDSKLED: if (iocp->ioc_count == TRANSPARENT) { mcopyin(mp, *(caddr_t *)mp->b_cont->b_rptr, sizeof (struct envctrl_pcf8574_chip), NULL); qreply(q, mp); } else { miocnak(q, mp, 0, EINVAL); } break; default: miocnak(q, mp, 0, EINVAL); break; } break; } case M_IOCDATA: { uint8_t *tempr, *wdval; long state; csp = (struct copyresp *)(void *)mp->b_rptr; /* * If copy request failed, quit now */ if (csp->cp_rval != 0) { miocnak(q, mp, 0, EINVAL); return (0); } cqp = (struct copyreq *)(void *)mp->b_rptr; cmd = csp->cp_cmd; state = (long)cqp->cq_private; switch (cmd) { case ENVCTRL_IOC_SETFAN: fanspeed = (struct envctrl_tda8444t_chip *) (void *)mp->b_cont->b_rptr; mutex_enter(&unitp->umutex); if (envctrl_xmit(unitp, (caddr_t *)(void *)fanspeed, fanspeed->type) == DDI_FAILURE) { /* * Fix for a ADF bug * move mutex to after fan fail call * bugid 4016121 */ envctrl_fan_fail_service(unitp); mutex_exit(&unitp->umutex); miocnak(q, mp, 0, EINVAL); } else { mutex_exit(&unitp->umutex); miocack(q, mp, 0, 0); } break; case ENVCTRL_IOC_SETFSP: wdval = (uint8_t *)(void *)mp->b_cont->b_rptr; 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_FSP_USRMASK) { mutex_exit(&unitp->umutex); miocnak(q, mp, 0, EINVAL); break; } } envctrl_set_fsp(unitp, wdval); mutex_exit(&unitp->umutex); miocack(q, mp, 0, 0); break; case ENVCTRL_IOC_SETDSKLED: ledchip = (struct envctrl_pcf8574_chip *) (void *)mp->b_cont->b_rptr; mutex_enter(&unitp->umutex); if (envctrl_set_dskled(unitp, ledchip)) { miocnak(q, mp, 0, EINVAL); } else { miocack(q, mp, 0, 0); } mutex_exit(&unitp->umutex); break; case ENVCTRL_IOC_GETDSKLED: if (state == -1) { miocack(q, mp, 0, 0); break; } ledchip = (struct envctrl_pcf8574_chip *) (void *)mp->b_cont->b_rptr; mutex_enter(&unitp->umutex); if (envctrl_get_dskled(unitp, ledchip)) { miocnak(q, mp, 0, EINVAL); } else { mcopyout(mp, (void *)-1, sizeof (struct envctrl_pcf8574_chip), csp->cp_private, NULL); qreply(q, mp); } mutex_exit(&unitp->umutex); break; case ENVCTRL_IOC_GETTEMP: /* Get the user buffer address */ if (state == -1) { miocack(q, mp, 0, 0); break; } temp = (struct envctrl_pcf8591_chip *) (void *)mp->b_cont->b_rptr; mutex_enter(&unitp->umutex); envctrl_recv(unitp, (caddr_t *)(void *)temp, PCF8591); mutex_exit(&unitp->umutex); mcopyout(mp, (void *)-1, sizeof (struct envctrl_pcf8591_chip), csp->cp_private, NULL); qreply(q, mp); break; case ENVCTRL_IOC_GETFAN: /* Get the user buffer address */ if (state == -1) { miocack(q, mp, 0, 0); break; } a_fanspeed = (struct envctrl_pcf8591_chip *) (void *)mp->b_cont->b_rptr; mutex_enter(&unitp->umutex); envctrl_recv(unitp, (caddr_t *)(void *)a_fanspeed, PCF8591); mutex_exit(&unitp->umutex); mcopyout(mp, (void *)-1, sizeof (struct envctrl_pcf8591_chip), csp->cp_private, NULL); qreply(q, mp); break; case ENVCTRL_IOC_SETTEMP: tempr = (uint8_t *)(void *)mp->b_cont->b_rptr; if (*tempr > MAX_DIAG_TEMPR) { miocnak(q, mp, 0, EINVAL); } else { mutex_enter(&unitp->umutex); envctrl_get_sys_temperatures(unitp, tempr); mutex_exit(&unitp->umutex); miocack(q, mp, 0, 0); } break; case ENVCTRL_IOC_SETWDT: /* reset watchdog timeout period */ wdval = (uint8_t *)(void *)mp->b_cont->b_rptr; if (*wdval > MAX_CL_VAL) { miocnak(q, mp, 0, EINVAL); } else { mutex_enter(&unitp->umutex); envctrl_reset_watchdog(unitp, wdval); mutex_exit(&unitp->umutex); miocack(q, mp, 0, 0); } break; case ENVCTRL_IOC_GETMODE: /* Get the user buffer address */ if (state == -1) { miocack(q, mp, 0, 0); break; } tempr = (uchar_t *)(void *)mp->b_cont->b_rptr; *tempr = unitp->current_mode; mcopyout(mp, (void *)-1, sizeof (uchar_t), csp->cp_private, NULL); qreply(q, mp); break; case ENVCTRL_IOC_SETMODE: /* Set mode */ wdval = (uint8_t *)(void *)mp->b_cont->b_rptr; 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) { envctrl_get_sys_temperatures(unitp, (uint8_t *)NULL); /* * going to normal mode we * need to go to diag mode * just in case we have * injected a fan fault. It * may not be cleared and if * we call fan_failsrvc it will * power off the ystem if we are * in NORMAL_MODE. Also we need * to delay 1 bit of time here * to allow the fans to rotate * back up and clear the intr * after we get the sys temps. */ unitp->current_mode = ENVCTRL_DIAG_MODE; envctrl_fan_fail_service(unitp); unitp->current_mode = ENVCTRL_NORMAL_MODE; } mutex_exit(&unitp->umutex); miocack(q, mp, 0, 0); } else { miocnak(q, mp, 0, EINVAL); } break; default: freemsg(mp); break; } break; } case M_FLUSH: if (*mp->b_rptr & FLUSHR) { *mp->b_rptr &= ~FLUSHW; qreply(q, mp); } else { freemsg(mp); } break; default: freemsg(mp); break; } return (0); } 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; ic = DDI_INTR_UNCLAIMED; mutex_enter(&unitp->umutex); /* * First check to see if it is an interrupt for us by * looking at the "ganged" interrrupt 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 = Power Supply 1 intr * P1 = Power Supply 2 intr * P2 = Power Supply 3 intr * P3 = Dlfop enable for fan sped set * P4 = ENVCTRL_ Fan Fail intr * P5 = Front Panel Interrupt * P6 = Power Fail Detect Low. * P7 = Enable Interrupts to system */ retry: status = eHc_read_pcf8574a((struct eHc_envcunit *)unitp, PCF8574A_BASE_ADDR | ENVCTRL_PCF8574_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, PCF8574A_BASE_ADDR | ENVCTRL_PCF8574_DEV0, &recv_data, 1); } if (envctrl_debug_flags) cmn_err(CE_WARN, "envctrl_dev_isr: status= %d, data = %x\n", status, recv_data); /* * 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++; goto retry; } else { if (envctrl_debug_flags) cmn_err(CE_WARN, "DEVISR FAILED received 0x%x\n", recv_data); mutex_exit(&unitp->umutex); envctrl_init_bus(unitp); mutex_enter(&unitp->umutex); envctrl_ps_probe(unitp); mutex_exit(&unitp->umutex); ic = DDI_INTR_CLAIMED; return (ic); } } /* * Port 0 = PS1 interrupt * Port 1 = PS2 Interrupt * Port 2 = PS3 Interrupt * Port 3 = SPARE * Port 4 = Fan Fail Intr * Port 5 = Front Panle Module intr * Port 6 = Keyswitch Intr * Port 7 = ESINTR ENABLE ??? */ if (!(recv_data & ENVCTRL_PCF8574_PORT0)) { envctrl_PS_intr_service(unitp, PS1); ic = DDI_INTR_CLAIMED; } if (!(recv_data & ENVCTRL_PCF8574_PORT1)) { envctrl_PS_intr_service(unitp, PS2); ic = DDI_INTR_CLAIMED; } if (!(recv_data & ENVCTRL_PCF8574_PORT2)) { envctrl_PS_intr_service(unitp, PS3); ic = DDI_INTR_CLAIMED; } if (!(recv_data & ENVCTRL_PCF8574_PORT3)) { ic = DDI_INTR_CLAIMED; } if (!(recv_data & ENVCTRL_PCF8574_PORT4)) { /* * Check for a fan fail * Single fan fail * shutdown system */ envctrl_fan_fail_service(unitp); ic = DDI_INTR_CLAIMED; } if (!(recv_data & ENVCTRL_PCF8574_PORT5)) { (void) envctrl_get_fpm_status(unitp); ic = DDI_INTR_CLAIMED; } if (!(recv_data & ENVCTRL_PCF8574_PORT6)) { ic = DDI_INTR_CLAIMED; } if (!(recv_data & ENVCTRL_PCF8574_PORT7)) { ic = DDI_INTR_CLAIMED; } if ((recv_data == 0xFF)) { ic = DDI_INTR_CLAIMED; } mutex_exit(&unitp->umutex); return (ic); } static void envctrl_init_bus(struct envctrlunit *unitp) { int i; uint8_t noval = 0; struct envctrl_tda8444t_chip fan; int fans[] = {ENVCTRL_CPU_FANS, ENVCTRL_PS_FANS, ENVCTRL_AFB_FANS}; mutex_enter(&unitp->umutex); /* Sets the Mode to 808x type bus */ ddi_put8(unitp->ctlr_handle, &unitp->bus_ctl_regs->s0, ENVCTRL_CHAR_ZERO); /* SET UP SLAVE ADDR XXX Required..send 0x80 */ ddi_put8(unitp->ctlr_handle, &unitp->bus_ctl_regs->s1, ENVCTRL_BUS_INIT0); (void) ddi_put8(unitp->ctlr_handle, &unitp->bus_ctl_regs->s0, ENVCTRL_BUS_INIT1); /* Set the clock now */ ddi_put8(unitp->ctlr_handle, &unitp->bus_ctl_regs->s1, ENVCTRL_BUS_CLOCK0); /* S0 is now S2 necause of the previous write to S1 */ /* clock= 12MHz, SCL=90KHz */ ddi_put8(unitp->ctlr_handle, &unitp->bus_ctl_regs->s0, ENVCTRL_BUS_CLOCK1); /* Enable serial interface */ ddi_put8(unitp->ctlr_handle, &unitp->bus_ctl_regs->s1, ENVCTRL_BUS_ESI); envctrl_stop_clock(unitp); /* * This has been added here because the DAC is powered * on at "0". When the reset_dflop routine is called * this switched the fans from blast to DAC control. * if the DAC is at "0", then the fans momentarily lose * power until the temp polling and fan set routine is * first called. If the fans lose power, then there is * a fan fault generated and the system will power off. * We only want to do this IF the bus is first being * initted. This will cause errors in Sunvts if we reset * the fan speed under normal operation. Sometimes we need * to be able to induce fan faults. Init bus is a common * routine to unwedge the i2c bus in some cases. */ if (unitp->initting == B_TRUE) { fan.chip_num = ENVCTRL_TDA8444T_DEV7; fan.val = INIT_FAN_VAL; for (i = 0; i < sizeof (fans)/sizeof (int); i++) { fan.fan_num = fans[i]; if ((fans[i] == ENVCTRL_AFB_FANS) && (unitp->AFB_present == B_FALSE)) continue; (void) envctrl_xmit(unitp, (caddr_t *)(void *)&fan, TDA8444T); } } envctrl_reset_dflop(unitp); envctrl_enable_devintrs(unitp); unitp->current_mode = ENVCTRL_NORMAL_MODE; envctrl_reset_watchdog(unitp, &noval); mutex_exit(&unitp->umutex); } static int envctrl_xmit(struct envctrlunit *unitp, caddr_t *data, int chip_type) { struct envctrl_tda8444t_chip *fanspeed; struct envctrl_pcf8574_chip *ioport; uint8_t slave_addr; uint8_t buf[2]; int retrys = 0; int status; ASSERT(MUTEX_HELD(&unitp->umutex)); switch (chip_type) { case TDA8444T: fanspeed = (struct envctrl_tda8444t_chip *)data; if (fanspeed->chip_num > ENVCTRL_FAN_ADDR_MAX) { return (DDI_FAILURE); } if (fanspeed->fan_num > ENVCTRL_PORT7) { return (DDI_FAILURE); } if (fanspeed->val > MAX_FAN_VAL) { return (DDI_FAILURE); } retry0: slave_addr = (TDA8444T_BASE_ADDR | fanspeed->chip_num); buf[0] = fanspeed->val; status = eHc_write_tda8444((struct eHc_envcunit *)unitp, TDA8444T_BASE_ADDR | fanspeed->chip_num, 0xF, fanspeed->fan_num, buf, 1); if (status != DDI_SUCCESS) { drv_usecwait(1000); if (retrys < envctrl_max_retries) { retrys++; goto retry0; } else { mutex_exit(&unitp->umutex); envctrl_init_bus(unitp); mutex_enter(&unitp->umutex); if (envctrl_debug_flags) cmn_err(CE_WARN, "envctrl_xmit: Write to TDA8444 " \ "failed\n"); return (DDI_FAILURE); } } /* * Update the kstats. */ switch (fanspeed->fan_num) { case ENVCTRL_CPU_FANS: unitp->fan_kstats[ENVCTRL_FAN_TYPE_CPU].fanspeed = fanspeed->val; break; case ENVCTRL_PS_FANS: unitp->fan_kstats[ENVCTRL_FAN_TYPE_PS].fanspeed = fanspeed->val; break; case ENVCTRL_AFB_FANS: unitp->fan_kstats[ENVCTRL_FAN_TYPE_AFB].fanspeed = fanspeed->val; break; default: break; } break; case PCF8574: ioport = (struct envctrl_pcf8574_chip *)data; buf[0] = ioport->val; if (ioport->chip_num > ENVCTRL_PCF8574_DEV7) return (DDI_FAILURE); retry: if (ioport->type == PCF8574A) { slave_addr = (PCF8574A_BASE_ADDR | ioport->chip_num); status = eHc_write_pcf8574a((struct eHc_envcunit *)unitp, PCF8574A_BASE_ADDR | ioport->chip_num, buf, 1); } else { slave_addr = (PCF8574_BASE_ADDR | ioport->chip_num); status = eHc_write_pcf8574((struct eHc_envcunit *)unitp, PCF8574_BASE_ADDR | ioport->chip_num, buf, 1); } if (status != DDI_SUCCESS) { drv_usecwait(1000); if (retrys < envctrl_max_retries) { retrys++; goto retry; } else { mutex_exit(&unitp->umutex); envctrl_init_bus(unitp); mutex_enter(&unitp->umutex); if (envctrl_debug_flags) cmn_err(CE_WARN, "Write to PCF8574 " \ "failed, addr = %X\n", slave_addr); if (envctrl_debug_flags) cmn_err(CE_WARN, "envctrl_xmit: PCF8574\ dev = %d, port = %d\n", ioport->chip_num, ioport->type); return (DDI_FAILURE); } } break; default: return (DDI_FAILURE); } return (DDI_SUCCESS); } static void envctrl_recv(struct envctrlunit *unitp, caddr_t *data, int chip_type) { struct envctrl_pcf8591_chip *temp; struct envctrl_pcf8574_chip *ioport; uint8_t slave_addr, recv_data; int retrys = 0; int status; uint8_t buf[1]; ASSERT(MUTEX_HELD(&unitp->umutex)); switch (chip_type) { case PCF8591: temp = (struct envctrl_pcf8591_chip *)data; slave_addr = (PCF8591_BASE_ADDR | temp->chip_num); retry: status = eHc_read_pcf8591((struct eHc_envcunit *)unitp, PCF8591_BASE_ADDR | temp->chip_num & 0xF, temp->sensor_num, 0, 0, 1, &recv_data, 1); /* * another place to catch the i2c bus hang on an 8591 read * In this instance we will just return the data that is read * after the max_retry because this could be a valid value. */ if (status != DDI_SUCCESS) { drv_usecwait(1000); if (retrys < envctrl_max_retries) { retrys++; goto retry; } else { mutex_exit(&unitp->umutex); envctrl_init_bus(unitp); mutex_enter(&unitp->umutex); if (envctrl_debug_flags) cmn_err(CE_WARN, "Read from PCF8591 " \ "failed, slave_addr = %x\n", slave_addr); } } temp->temp_val = recv_data; break; case TDA8444T: printf("envctrl_recv: attempting to read TDA8444T\n"); return; case PCF8574: ioport = (struct envctrl_pcf8574_chip *)data; retry1: if (ioport->chip_num > ENVCTRL_PCF8574_DEV7) cmn_err(CE_WARN, "envctrl: dev out of range 0x%x\n", ioport->chip_num); if (ioport->type == PCF8574A) { slave_addr = (PCF8574_READ_BIT | PCF8574A_BASE_ADDR | ioport->chip_num); status = eHc_read_pcf8574a((struct eHc_envcunit *)unitp, PCF8574A_BASE_ADDR | ioport->chip_num, buf, 1); } else { slave_addr = (PCF8574_READ_BIT | PCF8574_BASE_ADDR | ioport->chip_num); status = eHc_read_pcf8574((struct eHc_envcunit *)unitp, PCF8574_BASE_ADDR | ioport->chip_num, buf, 1); } if (status != DDI_SUCCESS) { drv_usecwait(1000); if (retrys < envctrl_max_retries) { retrys++; goto retry1; } else { mutex_exit(&unitp->umutex); envctrl_init_bus(unitp); mutex_enter(&unitp->umutex); if (envctrl_debug_flags) cmn_err(CE_WARN, "Read from PCF8574 "\ "failed, addr = %X\n", slave_addr); if (envctrl_debug_flags) cmn_err(CE_WARN, "envctrl_recv: PCF8574\ dev = %d, port = %d\n", ioport->chip_num, ioport->type); } } ioport->val = buf[0]; break; default: break; } } static int envctrl_get_ps_temp(struct envctrlunit *unitp, uint8_t psaddr) { uint8_t tempr; int i, retrys; int status; uint8_t buf[4]; ASSERT(MUTEX_HELD(&unitp->umutex)); tempr = 0; retrys = 0; retry: status = eHc_read_pcf8591((struct eHc_envcunit *)unitp, PCF8591_BASE_ADDR | psaddr & 0xF, 0, 1, 0, 1, buf, 4); tempr = 0; for (i = 0; i < PCF8591_MAX_PORTS; i++) { /* * The pcf8591 will return 0xff if no port * is there.. this is bogus for setting temps. * so just ignore it! */ if (envctrl_debug_flags) { cmn_err(CE_WARN, "PS addr 0x%x recvd 0x%x on port %d\n", psaddr, buf[i], i); } if (buf[i] > tempr && buf[i] < MAX_PS_ADVAL) { tempr = buf[i]; } } /* * This routine is a safeguard to make sure that if the * powersupply temps cannot be read that we do something * to make sure that the system will notify the user and * it will stay running with the fans at 100%. The calling * routine should take care of that. */ if (status != DDI_SUCCESS) { drv_usecwait(1000); if (retrys < envctrl_max_retries) { retrys++; goto retry; } else { mutex_exit(&unitp->umutex); envctrl_init_bus(unitp); mutex_enter(&unitp->umutex); if (envctrl_debug_flags) cmn_err(CE_WARN, "Cannot read Power Supply Temps addr = %X", psaddr); return (PS_DEFAULT_VAL); } } return (ps_temps[tempr]); } static int envctrl_get_cpu_temp(struct envctrlunit *unitp, int cpunum) { uint8_t recv_data; int retrys; 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 COU thermistor for one of the 4 * cpu's. It will return the temperature in degrees C * to the caller. */ retrys = 0; retry: status = eHc_read_pcf8591((struct eHc_envcunit *)unitp, PCF8591_BASE_ADDR | PCF8591_DEV7, cpunum, 0, 0, 0, &recv_data, 1); /* * We need to take a sledge hammer to the bus if we get back * value of the chip. This means that the i2c bus got wedged. * On the 1.4 systems this happens sometimes while running * sunvts. We will return the max cpu temp minus 10 to make * the fans run at full speed so that we don;t cook the * system. * At this point this is a workaround for hardware glitch. */ if (status == DDI_FAILURE) { drv_usecwait(1000); if (retrys < envctrl_max_retries) { retrys++; goto retry; } else { mutex_exit(&unitp->umutex); envctrl_init_bus(unitp); mutex_enter(&unitp->umutex); if (envctrl_debug_flags) cmn_err(CE_WARN, "envctrl CPU TEMP read " \ "failed\n"); /* we don't want to power off the system */ return (MAX_CPU_TEMP - 10); } } return (cpu_temps[recv_data]); } static int envctrl_get_lm75_temp(struct envctrlunit *unitp) { int k; ushort_t lmval; uint8_t tmp1; uint8_t tmp2; int status; uint8_t buf[2]; ASSERT(MUTEX_HELD(&unitp->umutex)); status = eHc_read_lm75((struct eHc_envcunit *)unitp, LM75_BASE_ADDR | LM75_CONFIG_ADDRA, buf, 2); if (status != DDI_SUCCESS) cmn_err(CE_WARN, "read of LM75 failed\n"); tmp1 = buf[0]; tmp2 = buf[1]; /* * Store the forst 8 bits in the upper nibble of the * short, then store the lower 8 bits in the lower nibble * of the short, shift 7 to the right to get the 9 bit value * that the lm75 is really sending. */ lmval = tmp1 << 8; lmval = (lmval | tmp2); lmval = (lmval >> 7); /* * Check the 9th bit to see if it is a negative * temperature. If so change into 2's compliment * and divide by 2 since each value is equal to a * half degree strp in degrees C */ if (lmval & LM75_COMP_MASK) { tmp1 = (lmval & LM75_COMP_MASK_UPPER); tmp1 = -tmp1; tmp1 = tmp1/2; k = 0 - tmp1; } else { k = lmval /2; } return (k); } static void envctrl_tempr_poll(void *arg) { int diag_flag = 0; struct envctrlunit *unitp = (struct envctrlunit *)arg; mutex_enter(&unitp->umutex); if (unitp->shutdown == B_TRUE) { (void) power_down("Fatal System Environmental Control Error"); } /* * 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. We will change the fanspeed * but will not check for a fanfault. This will cause a system shutdown * if the system has had a fanfault injected. */ if (unitp->current_mode == ENVCTRL_DIAG_MODE) { diag_flag++; if (envctrl_debug_flags) { cmn_err(CE_WARN, "Tempr poll went off while in DIAG MODE"); } } unitp->current_mode = ENVCTRL_NORMAL_MODE; envctrl_get_sys_temperatures(unitp, (uint8_t *)NULL); if (diag_flag == 0) { envctrl_fan_fail_service(unitp); } /* now have this thread sleep for a while */ unitp->timeout_id = (timeout(envctrl_tempr_poll, (caddr_t)unitp, overtemp_timeout_hz)); mutex_exit(&unitp->umutex); } static void envctrl_led_blink(void *arg) { struct envctrl_pcf8574_chip fspchip; struct envctrlunit *unitp = (struct envctrlunit *)arg; mutex_enter(&unitp->umutex); fspchip.type = PCF8574A; fspchip.chip_num = ENVCTRL_PCF8574_DEV6; /* 0x01 port 1 */ envctrl_recv(unitp, (caddr_t *)(void *)&fspchip, PCF8574); 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. */ fspchip.val = (fspchip.val & ~(ENVCTRL_PCF8574_PORT4) | 0xC0); unitp->present_led_state = B_FALSE; } else { fspchip.val = (fspchip.val | ENVCTRL_PCF8574_PORT4 | 0xC0); unitp->present_led_state = B_TRUE; } (void) envctrl_xmit(unitp, (caddr_t *)(void *)&fspchip, PCF8574); /* 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); } /* called with mutex held */ static void envctrl_get_sys_temperatures(struct envctrlunit *unitp, uint8_t *diag_tempr) { int temperature, tmptemp, cputemp, hicputemp, ambtemp; int i; struct envctrl_tda8444t_chip fan; uint8_t psaddr[] = {PSTEMP3, PSTEMP2, PSTEMP1, PSTEMP0}; uint8_t noval = 0; uint8_t fspval; ASSERT(MUTEX_HELD(&unitp->umutex)); fan.fan_num = ENVCTRL_CPU_FANS; fan.chip_num = ENVCTRL_TDA8444T_DEV7; tmptemp = 0; /* Right init value ?? */ /* * THis routine is caled once every minute * we wil re-se the watchdog timer each time * we poll the temps. The watchdog timer is * set up for 3 minutes. Should the kernel thread * wedge, for some reason the watchdog will go off * and blast the fans. */ if (unitp->current_mode == ENVCTRL_DIAG_MODE) { unitp->current_mode = ENVCTRL_NORMAL_MODE; envctrl_reset_watchdog(unitp, &noval); unitp->current_mode = ENVCTRL_DIAG_MODE; } else { envctrl_reset_watchdog(unitp, &noval); } /* * we need to reset the dflop to allow the fans to be * set if the watchdog goes of and the kernel resumes * resetting the dflop alos resets the device interrupts * we need to reenable them also. */ envctrl_reset_dflop(unitp); envctrl_enable_devintrs(unitp); /* * If we are in diag mode we allow the system to be * faked out as to what the temperature is * to see if the fans speed up. */ if (unitp->current_mode == ENVCTRL_DIAG_MODE && diag_tempr != NULL) { if (unitp->timeout_id != 0) { (void) untimeout(unitp->timeout_id); } ambtemp = *diag_tempr; unitp->timeout_id = (timeout(envctrl_tempr_poll, (caddr_t)unitp, overtemp_timeout_hz)); } else { ambtemp = envctrl_get_lm75_temp(unitp); /* * Sometimes when we read the temp it comes back bogus * to fix this we just need to reset the envctrl bus */ if (ambtemp == -100) { mutex_exit(&unitp->umutex); envctrl_init_bus(unitp); mutex_enter(&unitp->umutex); ambtemp = envctrl_get_lm75_temp(unitp); } } envctrl_mod_encl_kstats(unitp, ENVCTRL_ENCL_AMBTEMPR, INSTANCE_0, ambtemp); fspval = envctrl_get_fpm_status(unitp); if (ambtemp > MAX_AMB_TEMP) { fspval |= (ENVCTRL_FSP_TEMP_ERR | ENVCTRL_FSP_GEN_ERR); if (!(envctrl_power_off_overide) && unitp->current_mode == ENVCTRL_NORMAL_MODE) { unitp->shutdown = B_TRUE; } if (unitp->current_mode == ENVCTRL_NORMAL_MODE) { cmn_err(CE_WARN, "Ambient Temperature is %d C, shutdown now\n", ambtemp); } } else { if (envctrl_isother_fault_led(unitp, fspval, ENVCTRL_FSP_TEMP_ERR)) { fspval &= ~(ENVCTRL_FSP_TEMP_ERR); } else { fspval &= ~(ENVCTRL_FSP_TEMP_ERR | ENVCTRL_FSP_GEN_ERR); } } envctrl_set_fsp(unitp, &fspval); cputemp = hicputemp = 0; #ifndef TESTBED for (i = 0; i < ENVCTRL_MAX_CPUS; i++) { if (unitp->cpu_pr_location[i] == B_TRUE) { cputemp = envctrl_get_cpu_temp(unitp, i); envctrl_mod_encl_kstats(unitp, ENVCTRL_ENCL_CPUTEMPR, i, cputemp); if (cputemp >= MAX_CPU_TEMP) { if (!(envctrl_power_off_overide)) { unitp->shutdown = B_TRUE; } cmn_err(CE_WARN, "CPU %d OVERHEATING!!!", i); } if (cputemp > hicputemp) { hicputemp = cputemp; } } } #else cputemp = envctrl_get_cpu_temp(unitp, 0); envctrl_mod_encl_kstats(unitp, ENVCTRL_ENCL_CPUTEMPR, 0, cputemp); #endif fspval = envctrl_get_fpm_status(unitp); /* * We first look at the ambient temp. If the system is at idle * the cpu temps will be approx 20 degrees above ambient. * If the cpu's rise above 20, then the CPU fans are set * according to the cpu temp minus 20 degrees C. */ if (unitp->current_mode == ENVCTRL_DIAG_MODE && diag_tempr != NULL) { temperature = ambtemp; } else { temperature = hicputemp - CPU_AMB_RISE; } if (temperature < 0) { fan.val = MAX_FAN_SPEED; /* blast it is out of range */ } else if (temperature > MAX_AMB_TEMP) { fan.val = MAX_FAN_SPEED; fspval |= (ENVCTRL_FSP_TEMP_ERR | ENVCTRL_FSP_GEN_ERR); if (unitp->current_mode == ENVCTRL_NORMAL_MODE) { cmn_err(CE_WARN, "CPU Fans set to MAX. CPU Temp is %d C\n", hicputemp); } } else if (ambtemp < MAX_AMB_TEMP) { if (!envctrl_p0_enclosure) { fan.val = acme_cpu_fanspd[temperature]; } else { fan.val = fan_speed[temperature]; } if (envctrl_isother_fault_led(unitp, fspval, ENVCTRL_FSP_TEMP_ERR)) { fspval &= ~(ENVCTRL_FSP_TEMP_ERR); } else { fspval &= ~(ENVCTRL_FSP_TEMP_ERR | ENVCTRL_FSP_GEN_ERR); } } envctrl_set_fsp(unitp, &fspval); /* * Update temperature kstats. FSP kstats are updated in the * set and get routine. */ unitp->fan_kstats[ENVCTRL_FAN_TYPE_CPU].fanspeed = fan.val; /* CPU FANS */ (void) envctrl_xmit(unitp, (caddr_t *)(void *)&fan, TDA8444T); /* The afb Fan is always at max */ if (unitp->AFB_present == B_TRUE) { fan.val = AFB_MAX; /* AFB FANS */ unitp->fan_kstats[ENVCTRL_FAN_TYPE_AFB].fanspeed = fan.val; fan.fan_num = ENVCTRL_AFB_FANS; (void) envctrl_xmit(unitp, (caddr_t *)(void *)&fan, TDA8444T); } /* * Now set the Powersupply fans */ tmptemp = temperature = 0; for (i = 0; i <= MAXPS; i++) { if (unitp->ps_present[i]) { tmptemp = envctrl_get_ps_temp(unitp, psaddr[i]); unitp->ps_kstats[i].ps_tempr = tmptemp & 0xFFFF; if (tmptemp > temperature) { temperature = tmptemp; } if (temperature >= MAX_PS_TEMP) { if (!(envctrl_power_off_overide)) { unitp->shutdown = B_TRUE; } cmn_err(CE_WARN, "Power Supply %d OVERHEATING!!!\ Temp is %d C", i, temperature); } } } fan.fan_num = ENVCTRL_PS_FANS; if (temperature > PS_TEMP_WARN) { fspval = envctrl_get_fpm_status(unitp); fspval |= (ENVCTRL_FSP_TEMP_ERR | ENVCTRL_FSP_GEN_ERR); envctrl_set_fsp(unitp, &fspval); fan.val = MAX_FAN_SPEED; cmn_err(CE_WARN, "A Power Supply is close to OVERHEATING!!!"); } else { if (temperature - ambtemp > PS_AMB_RISE) { ambtemp = temperature - PS_AMB_RISE; } if (!envctrl_p0_enclosure) { fan.val = acme_ps_fanspd[ambtemp]; } else { fan.val = ps_fans[ambtemp]; } } /* * XXX add in error condition for ps overtemp */ unitp->fan_kstats[ENVCTRL_FAN_TYPE_PS].fanspeed = fan.val; (void) envctrl_xmit(unitp, (caddr_t *)(void *)&fan, TDA8444T); } /* called with mutex held */ static void envctrl_fan_fail_service(struct envctrlunit *unitp) { uint8_t recv_data, fpmstat; int fantype; int psfanflt, cpufanflt, afbfanflt; int retries = 0, max_retry_count; int status; psfanflt = cpufanflt = afbfanflt = 0; /* * The fan fail sensor is located at address 0x70 * on the envctrl bus. */ ASSERT(MUTEX_HELD(&unitp->umutex)); retry: status = eHc_read_pcf8574a((struct eHc_envcunit *)unitp, PCF8574A_BASE_ADDR | ENVCTRL_PCF8574_DEV4, &recv_data, 1); if (status != DDI_SUCCESS) cmn_err(CE_WARN, "fan_fail_service: status = %d, data = %x\n", status, recv_data); /* * If all fan ports are high (0xff) then we don't have any * fan faults. Reset the kstats */ if (recv_data == 0xff) { unitp->fan_kstats[ENVCTRL_FAN_TYPE_PS].fans_ok = B_TRUE; unitp->fan_kstats[ENVCTRL_FAN_TYPE_CPU].fans_ok = B_TRUE; unitp->fan_kstats[ENVCTRL_FAN_TYPE_AFB].fans_ok = B_TRUE; unitp->fan_kstats[ENVCTRL_FAN_TYPE_PS].fanflt_num = 0; unitp->fan_kstats[ENVCTRL_FAN_TYPE_CPU].fanflt_num = 0; unitp->fan_kstats[ENVCTRL_FAN_TYPE_AFB].fanflt_num = 0; unitp->num_fans_failed = 0; fpmstat = envctrl_get_fpm_status(unitp); if (!(envctrl_isother_fault_led(unitp, fpmstat, 0))) { fpmstat &= ~(ENVCTRL_FSP_GEN_ERR); } if (unitp->shutdown != B_TRUE) { envctrl_set_fsp(unitp, &fpmstat); } return; } fantype = ENVCTRL_FAN_TYPE_PS; if (!(recv_data & ENVCTRL_PCF8574_PORT0)) { psfanflt = PS_FAN_3; } if (!(recv_data & ENVCTRL_PCF8574_PORT1)) { psfanflt = PS_FAN_2; } if (!(recv_data & ENVCTRL_PCF8574_PORT2)) { psfanflt = PS_FAN_1; } if (psfanflt != 0) { unitp->fan_kstats[fantype].fans_ok = B_FALSE; unitp->fan_kstats[fantype].fanflt_num = psfanflt - 1; if (retries == MAX_FAN_FAIL_RETRY && status == DDI_SUCCESS && unitp->current_mode == ENVCTRL_NORMAL_MODE) { cmn_err(CE_WARN, "PS Fan Number %d Failed", psfanflt - 1); } } else { unitp->fan_kstats[fantype].fans_ok = B_TRUE; unitp->fan_kstats[fantype].fanflt_num = 0; } fantype = ENVCTRL_FAN_TYPE_CPU; if (!(recv_data & ENVCTRL_PCF8574_PORT3)) { cpufanflt = CPU_FAN_1; } if (!(recv_data & ENVCTRL_PCF8574_PORT4)) { cpufanflt = CPU_FAN_2; } if (!(recv_data & ENVCTRL_PCF8574_PORT5)) { cpufanflt = CPU_FAN_3; } if (cpufanflt != 0) { unitp->fan_kstats[fantype].fans_ok = B_FALSE; unitp->fan_kstats[fantype].fanflt_num = cpufanflt - 1; if (retries == MAX_FAN_FAIL_RETRY && status == DDI_SUCCESS && unitp->current_mode == ENVCTRL_NORMAL_MODE) { cmn_err(CE_WARN, "CPU Fan Number %d Failed", cpufanflt - 1); } } else { unitp->fan_kstats[fantype].fans_ok = B_TRUE; unitp->fan_kstats[fantype].fanflt_num = 0; } if (!(recv_data & ENVCTRL_PCF8574_PORT6) && (unitp->AFB_present == B_TRUE)) { /* * If the afb is present and the afb fan fails, * we need to power off or else it will melt! * If it isn't present just log the error. * We make the decision off of the afbfanflt * flag later on in an if statement. */ afbfanflt++; unitp->fan_kstats[ENVCTRL_FAN_TYPE_AFB].fans_ok = B_FALSE; unitp->fan_kstats[ENVCTRL_FAN_TYPE_AFB].fanflt_num = AFB_FAN_1; if (unitp->current_mode == ENVCTRL_NORMAL_MODE) { cmn_err(CE_WARN, "AFB Fan Failed"); } } /* * If we have no Fan Faults Clear the LED's * If we have fan faults set the Gen Fault LED. */ if (psfanflt == 0 && cpufanflt == 0 && afbfanflt == 0 && unitp->num_fans_failed != 0) { fpmstat = envctrl_get_fpm_status(unitp); if (!(envctrl_isother_fault_led(unitp, fpmstat, 0))) { fpmstat &= ~(ENVCTRL_FSP_GEN_ERR); } envctrl_set_fsp(unitp, &fpmstat); } else if (psfanflt != 0 || cpufanflt != 0 || afbfanflt != 0) { fpmstat = envctrl_get_fpm_status(unitp); fpmstat |= ENVCTRL_FSP_GEN_ERR; envctrl_set_fsp(unitp, &fpmstat); } if (unitp->AFB_present == B_FALSE) { afbfanflt = 0; } if ((cpufanflt > 0 || psfanflt > 0 || afbfanflt > 0 || (status != DDI_SUCCESS)) && !unitp->initting && unitp->current_mode == ENVCTRL_NORMAL_MODE) { if (status != DDI_SUCCESS) max_retry_count = envctrl_max_retries; else max_retry_count = MAX_FAN_FAIL_RETRY; if (retries <= max_retry_count) { retries++; drv_usecwait(1000); if (retries == max_retry_count) { cmn_err(CE_WARN, "Fan Fail is 0x%x, retries = %d\n", recv_data, retries); } envctrl_get_sys_temperatures(unitp, (uint8_t *)NULL); goto retry; } if (!(envctrl_power_off_overide)) { unitp->shutdown = B_TRUE; } cmn_err(CE_WARN, "Fan Failure(s), System Shutdown"); } unitp->num_fans_failed = (psfanflt + cpufanflt + afbfanflt); } /* * Check for power supply insertion and failure. * This is a bit tricky, because a power supply insertion will * trigger a load share interrupt 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, uint8_t psaddr) { uint8_t recv_data; int status, retrys = 0; ASSERT(MUTEX_HELD(&unitp->umutex)); if (unitp->current_mode == ENVCTRL_DIAG_MODE) { return; } retry: status = eHc_read_pcf8574a((struct eHc_envcunit *)unitp, PCF8574A_BASE_ADDR | psaddr & 0xF, &recv_data, 1); if (status != DDI_SUCCESS) { drv_usecwait(1000); if (retrys < envctrl_max_retries) { retrys++; goto retry; } else { mutex_exit(&unitp->umutex); envctrl_init_bus(unitp); mutex_enter(&unitp->umutex); if (envctrl_debug_flags) cmn_err(CE_WARN, "PS_intr_service: Read from 8574A " \ "failed\n"); } } /* * 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)); } /* called with mutex held */ static void envctrl_reset_dflop(struct envctrlunit *unitp) { struct envctrl_pcf8574_chip initval; ASSERT(MUTEX_HELD(&unitp->umutex)); /* * This initialization sequence allows a * to change state to stop the fans from * blastion upon poweron. If this isn't * done the writes to the 8444 will not complete * to the hardware because the dflop will * be closed */ initval.chip_num = ENVCTRL_PCF8574_DEV0; /* 0x01 port 1 */ initval.type = PCF8574A; initval.val = ENVCTRL_DFLOP_INIT0; (void) envctrl_xmit(unitp, (caddr_t *)(void *)&initval, PCF8574); initval.val = ENVCTRL_DFLOP_INIT1; (void) envctrl_xmit(unitp, (caddr_t *)(void *)&initval, PCF8574); } static void envctrl_add_encl_kstats(struct envctrlunit *unitp, int type, int instance, uint8_t val) { int i = 0; boolean_t inserted = B_FALSE; ASSERT(MUTEX_HELD(&unitp->umutex)); while (i < MAX_DEVS && inserted == B_FALSE) { if (unitp->encl_kstats[i].instance == I2C_NODEV) { unitp->encl_kstats[i].instance = instance; unitp->encl_kstats[i].type = type; unitp->encl_kstats[i].value = val; inserted = B_TRUE; } i++; } unitp->num_encl_present++; } /* called with mutex held */ static void envctrl_enable_devintrs(struct envctrlunit *unitp) { struct envctrl_pcf8574_chip initval; ASSERT(MUTEX_HELD(&unitp->umutex)); /* * This initialization sequence allows a * to change state to stop the fans from * blastion upon poweron. If this isn't * done the writes to the 8444 will not complete * to the hardware because the dflop will * be closed */ initval.chip_num = ENVCTRL_PCF8574_DEV0; /* 0x01 port 1 */ initval.type = PCF8574A; initval.val = ENVCTRL_DEVINTR_INTI0; (void) envctrl_xmit(unitp, (caddr_t *)(void *)&initval, PCF8574); /* * set lowerbits all high p0 = PS1, p1 = PS2 * p2 = PS3 p4 = envctrl intr_ctrl */ initval.val = ENVCTRL_DEVINTR_INTI1; (void) envctrl_xmit(unitp, (caddr_t *)(void *)&initval, PCF8574); } /* called with mutex held */ static void envctrl_stop_clock(struct envctrlunit *unitp) { int status; uint8_t buf[2]; /* * This routine talks to the PCF8583 which * is a clock calendar chip on the envctrl bus. * We use this chip as a watchdog timer for the * fan control. At reset this chip pulses the interrupt * line every 1 second. We need to be able to shut * this off. */ ASSERT(MUTEX_HELD(&unitp->umutex)); buf[0] = CLOCK_CSR_REG; buf[1] = CLOCK_DISABLE; status = eHc_write_pcf8583((struct eHc_envcunit *)unitp, PCF8583_BASE_ADDR | 0, buf, 2); if (status != DDI_SUCCESS) cmn_err(CE_WARN, "write to PCF8583 failed\n"); } static void envctrl_reset_watchdog(struct envctrlunit *unitp, uint8_t *wdval) { uint8_t w, r; uint8_t res = 0; int status; uint8_t buf[3]; ASSERT(MUTEX_HELD(&unitp->umutex)); /* the clock MUST be stopped before we re-set it */ envctrl_stop_clock(unitp); /* * Reset the minutes counter to 0. */ buf[0] = ALARM_CTR_REG_MINS; buf[1] = 0x0; status = eHc_write_pcf8583((struct eHc_envcunit *)unitp, PCF8583_BASE_ADDR | 0, buf, 2); if (status != DDI_SUCCESS) cmn_err(CE_WARN, "write to PCF8583 failed\n"); /* * set up the alarm timer for 3 minutes * start by setting reg 8 ALARM_CTRL_REG * If we are in diag mode, we set the timer in * seconds. Valid values are 40-99. The timer * counts up to 99. 40 would be 59 seconds */ buf[0] = CLOCK_ALARM_REG_A; if (unitp->current_mode == ENVCTRL_DIAG_MODE) { if (unitp->timeout_id != 0) { (void) untimeout(unitp->timeout_id); unitp->timeout_id = 0; unitp->timeout_id = (timeout(envctrl_tempr_poll, (caddr_t)unitp, overtemp_timeout_hz)); } buf[1] = CLOCK_ENABLE_TIMER_S; } else { buf[1] = CLOCK_ENABLE_TIMER; } /* STEP 10: End Transmission */ status = eHc_write_pcf8583((struct eHc_envcunit *)unitp, PCF8583_BASE_ADDR | 0, buf, 2); if (status != DDI_SUCCESS) cmn_err(CE_WARN, "Reset envctrl watchdog failed\n"); /* * Now set up the alarm timer register it * counts from 0-99 with an intr triggered * when it gets to overflow.. or 99. It will * also count from a pre-set value which is * where we are seting from. We want a 3 minute fail * safe so our value is 99-3 or 96. * we are programming register 7 in the 8583. */ buf[0] = ALARM_CTRL_REG; /* * Allow the diagnostic to set the egg timer val. * never allow it to be set greater than the default. */ if (unitp->current_mode == ENVCTRL_DIAG_MODE) { if (*wdval > MAX_CL_VAL) { buf[1] = EGG_TIMER_VAL; } else { w = *wdval/10; r = *wdval%10; res = res | r; res = (0x99 - (res | (w << 4))); buf[1] = res; } } else { buf[1] = EGG_TIMER_VAL; } status = eHc_write_pcf8583((struct eHc_envcunit *)unitp, PCF8583_BASE_ADDR | 0, buf, 2); if (status != DDI_SUCCESS) cmn_err(CE_WARN, "Reset envctrl watchdog failed\n"); /* * Now that we have set up.. it is time * to re-start the clock in the CSR. */ buf[0] = CLOCK_CSR_REG; buf[1] = CLOCK_ENABLE; status = eHc_write_pcf8583((struct eHc_envcunit *)unitp, PCF8583_BASE_ADDR | 0, buf, 2); if (status != DDI_SUCCESS) cmn_err(CE_WARN, "Reset envctrl watchdog failed\n"); } /* Called with unip mutex held */ static void envctrl_ps_probe(struct envctrlunit *unitp) { uint8_t recv_data, fpmstat; uint8_t psaddr[] = {PS1, PS2, PS3, PSTEMP0}; int i; int ps_error = 0, retrys = 0; int devaddr; int status; int twotimes = 0; ASSERT(MUTEX_HELD(&unitp->umutex)); unitp->num_ps_present = 0; for (i = 0; i <= MAXPS; i++) { unitp->ps_present[i] = B_FALSE; unitp->ps_kstats[i].ps_rating = 0; unitp->ps_kstats[i].ps_tempr = 0; switch (psaddr[i]) { case PS1: devaddr = ENVCTRL_PCF8574_DEV3; break; case PS2: devaddr = ENVCTRL_PCF8574_DEV2; break; case PS3: devaddr = ENVCTRL_PCF8574_DEV1; break; case PSTEMP0: devaddr = 0; break; } retrys = 0; retry: status = eHc_read_pcf8574a((struct eHc_envcunit *)unitp, PCF8574A_BASE_ADDR | devaddr, &recv_data, 1); if (status != DDI_SUCCESS) { drv_usecwait(1000); if (retrys < envctrl_max_retries) { retrys++; goto retry; } else { mutex_exit(&unitp->umutex); envctrl_init_bus(unitp); mutex_enter(&unitp->umutex); /* * If we just reset the bus we need to reread * the status. If a second attempt still fails * then report the read failure. */ if (twotimes == 0) { twotimes++; retrys = 0; goto retry; } else { cmn_err(CE_WARN, "PS_probe: Read from 8574A failed\n"); } } } /* * Port 0 = PS Present * Port 1 = PS Type * Port 2 = PS Type * Port 3 = PS TYpe * Port 4 = DC Status * Port 5 = Current Limit * Port 6 = Current Share * Port 7 = SPARE */ /* * Port 0 = PS Present * Port is pulled LOW "0" to indicate * present. */ if (!(recv_data & ENVCTRL_PCF8574_PORT0)) { unitp->ps_present[i] = B_TRUE; /* update unit kstat array */ unitp->ps_kstats[i].instance = i; unitp->ps_kstats[i].ps_tempr = ENVCTRL_INIT_TEMPR; ++unitp->num_ps_present; if (power_supply_previous_state[i] == 0) { cmn_err(CE_NOTE, "Power Supply %d inserted\n", i); } power_supply_previous_state[i] = 1; if (!(recv_data & ENVCTRL_PCF8574_PORT1)) { unitp->ps_kstats[i].ps_rating = ENVCTRL_PS_550; } if (!(recv_data & ENVCTRL_PCF8574_PORT2)) { unitp->ps_kstats[i].ps_rating = ENVCTRL_PS_650; } if (!(recv_data & ENVCTRL_PCF8574_PORT3)) { cmn_err(CE_WARN, "Power Supply %d NOT okay\n", i); unitp->ps_kstats[i].ps_ok = B_FALSE; ps_error++; } else { unitp->ps_kstats[i].ps_ok = B_TRUE; } if (!(recv_data & ENVCTRL_PCF8574_PORT4)) { cmn_err(CE_WARN, "Power Supply %d Overloaded\n", i); unitp->ps_kstats[i].limit_ok = B_FALSE; ps_error++; } else { unitp->ps_kstats[i].limit_ok = B_TRUE; } if (!(recv_data & ENVCTRL_PCF8574_PORT5)) { cmn_err(CE_WARN, "Power Supply %d load share err\n", i); unitp->ps_kstats[i].curr_share_ok = B_FALSE; ps_error++; } else { unitp->ps_kstats[i].curr_share_ok = B_TRUE; } if (!(recv_data & ENVCTRL_PCF8574_PORT6)) { cmn_err(CE_WARN, "PS %d Shouln't interrupt\n", i); ps_error++; } if (!(recv_data & ENVCTRL_PCF8574_PORT7)) { cmn_err(CE_WARN, "PS %d Shouln't interrupt\n", i); ps_error++; } } else { /* No power supply present */ if (power_supply_previous_state[i] == 1) { cmn_err(CE_NOTE, "Power Supply %d removed\n", i); } power_supply_previous_state[i] = 0; } } fpmstat = envctrl_get_fpm_status(unitp); if (ps_error) { fpmstat |= (ENVCTRL_FSP_PS_ERR | ENVCTRL_FSP_GEN_ERR); } else { if (envctrl_isother_fault_led(unitp, fpmstat, ENVCTRL_FSP_PS_ERR)) { fpmstat &= ~(ENVCTRL_FSP_PS_ERR); } else { fpmstat &= ~(ENVCTRL_FSP_PS_ERR | ENVCTRL_FSP_GEN_ERR); } } envctrl_set_fsp(unitp, &fpmstat); /* * We need to reset all of the fans etc when a supply is * interrupted and added, but we don't want to reset the * fans if we are in DIAG mode. This will mess up SUNVTS. */ if (unitp->current_mode == ENVCTRL_NORMAL_MODE) { envctrl_get_sys_temperatures(unitp, (uint8_t *)NULL); } } /* * 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); for (i = 0; i < MAX_DEVS; i++) { if ((unitp->encl_kstats[i].type == ENVCTRL_ENCL_FSP) && (unitp->encl_kstats[i].instance != I2C_NODEV)) { secure = unitp->encl_kstats[i].value; break; } } /* * take the logical not because we are in hardware mode only */ if ((secure & ENVCTRL_FSP_KEYMASK) == ENVCTRL_FSP_KEYLOCKED) { cmn_err(CE_CONT, "!envctrl: ignoring debug enter sequence\n"); } else { if (envctrl_debug_flags) { cmn_err(CE_CONT, "!envctrl: allowing debug enter\n"); } 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 uint8_t envctrl_get_fpm_status(struct envctrlunit *unitp) { uint8_t recv_data; int status, retrys = 0; ASSERT(MUTEX_HELD(&unitp->umutex)); retry: status = eHc_read_pcf8574a((struct eHc_envcunit *)unitp, PCF8574A_BASE_ADDR | ENVCTRL_PCF8574_DEV6, &recv_data, 1); /* * yet another place where a read can cause the * the SDA line of the i2c bus to get stuck low. * this funky sequence frees the SDA line. */ if (status != DDI_SUCCESS) { drv_usecwait(1000); if (retrys < envctrl_max_retries) { retrys++; goto retry; } else { mutex_exit(&unitp->umutex); envctrl_init_bus(unitp); mutex_enter(&unitp->umutex); if (envctrl_debug_flags) cmn_err(CE_WARN, "Read from PCF8574 (FPM) "\ "failed\n"); } } recv_data = ~recv_data; envctrl_mod_encl_kstats(unitp, ENVCTRL_ENCL_FSP, INSTANCE_0, recv_data); return (recv_data); } static void envctrl_set_fsp(struct envctrlunit *unitp, uint8_t *val) { struct envctrl_pcf8574_chip chip; ASSERT(MUTEX_HELD(&unitp->umutex)); chip.val = ENVCTRL_FSP_OFF; /* init all values to off */ chip.chip_num = ENVCTRL_PCF8574_DEV6; /* 0x01 port 1 */ chip.type = PCF8574A; /* * strip off bits that are R/O */ chip.val = (~(ENVCTRL_FSP_KEYMASK | ENVCTRL_FSP_POMASK) & (*val)); chip.val = ~chip.val; (void) envctrl_xmit(unitp, (caddr_t *)(void *)&chip, PCF8574); } static int envctrl_get_dskled(struct envctrlunit *unitp, struct envctrl_pcf8574_chip *chip) { uint_t oldtype; ASSERT(MUTEX_HELD(&unitp->umutex)); if (chip->chip_num > ENVCTRL_PCF8574_DEV2 || chip->type != ENVCTRL_ENCL_BACKPLANE4 && chip->type != ENVCTRL_ENCL_BACKPLANE8) { return (DDI_FAILURE); } oldtype = chip->type; chip->type = PCF8574; envctrl_recv(unitp, (caddr_t *)(void *)chip, PCF8574); chip->type = oldtype; chip->val = ~chip->val; return (DDI_SUCCESS); } static int envctrl_set_dskled(struct envctrlunit *unitp, struct envctrl_pcf8574_chip *chip) { struct envctrl_pcf8574_chip fspchip; struct envctrl_pcf8574_chip backchip; int i, instance; int diskfault = 0; uint8_t controller_addr[] = {ENVCTRL_PCF8574_DEV0, ENVCTRL_PCF8574_DEV1, ENVCTRL_PCF8574_DEV2}; /* * 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 > ENVCTRL_PCF8574_DEV2 || chip->val > ENVCTRL_DISK8LED_ALLOFF || chip->val < ENVCTRL_CHAR_ZERO) { return (DDI_FAILURE); } if (chip->type != ENVCTRL_ENCL_BACKPLANE4 && chip->type != ENVCTRL_ENCL_BACKPLANE8) { return (DDI_FAILURE); } /* * Check all of the other controllwes LED states to make sure * that there are no disk faults. If so then if the user is * clearing the disk faults on this contoller, turn off * the mass storage fault led. */ backchip.type = PCF8574; for (i = 0; i <= MAX_TAZ_CONTROLLERS; i++) { if (controller_present[i] == -1) continue; backchip.chip_num = controller_addr[i]; envctrl_recv(unitp, (caddr_t *)(void *)&backchip, PCF8574); if (chip->chip_num == controller_addr[i]) { if (chip->val != ENVCTRL_CHAR_ZERO) diskfault++; } else if ((~backchip.val & 0xFF) != ENVCTRL_CHAR_ZERO) { diskfault++; } } fspchip.type = PCF8574A; fspchip.chip_num = ENVCTRL_PCF8574_DEV6; /* 0x01 port 1 */ envctrl_recv(unitp, (caddr_t *)(void *)&fspchip, PCF8574); if (diskfault) { if (!(envctrl_isother_fault_led(unitp, fspchip.val & 0xFF, ENVCTRL_FSP_DISK_ERR))) { fspchip.val &= ~(ENVCTRL_FSP_DISK_ERR); } else { fspchip.val &= ~(ENVCTRL_FSP_DISK_ERR | ENVCTRL_FSP_GEN_ERR); } fspchip.val = (fspchip.val & ~(ENVCTRL_FSP_DISK_ERR | ENVCTRL_FSP_GEN_ERR)); } else { fspchip.val = (fspchip.val | (ENVCTRL_FSP_DISK_ERR | ENVCTRL_FSP_GEN_ERR)); } fspchip.type = PCF8574A; fspchip.chip_num = ENVCTRL_PCF8574_DEV6; /* 0x01 port 1 */ (void) envctrl_xmit(unitp, (caddr_t *)(void *)&fspchip, PCF8574); for (i = 0; i < (sizeof (backaddrs) / sizeof (uint8_t)); i++) { if (chip->chip_num == backaddrs[i]) { instance = i; } } switch (chip->type) { case ENVCTRL_ENCL_BACKPLANE4: envctrl_mod_encl_kstats(unitp, ENVCTRL_ENCL_BACKPLANE4, instance, chip->val); break; case ENVCTRL_ENCL_BACKPLANE8: envctrl_mod_encl_kstats(unitp, ENVCTRL_ENCL_BACKPLANE8, instance, chip->val); break; default: break; } chip->type = PCF8574; /* * 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. ;-) */ chip->val = ~(chip->val); (void) envctrl_xmit(unitp, (caddr_t *)(void *)chip, PCF8574); return (DDI_SUCCESS); } 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, "envctrl%d: encl raw kstat_create failed", 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)) == NULL) { cmn_err(CE_WARN, "envctrl%d: fans kstat_create failed", 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_PSNAME, "misc", KSTAT_TYPE_RAW, sizeof (unitp->ps_kstats), KSTAT_FLAG_PERSISTENT)) == NULL) { cmn_err(CE_WARN, "envctrl%d: ps name kstat_create failed", unitp->instance); return; } unitp->psksp->ks_update = envctrl_ps_kstat_update; unitp->psksp->ks_private = (void *)unitp; kstat_install(unitp->psksp); } 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) { return (EACCES); } else { unitp->psksp->ks_ndata = unitp->num_ps_present; bcopy(&unitp->ps_kstats, kstatp, sizeof (unitp->ps_kstats)); } mutex_exit(&unitp->umutex); return (DDI_SUCCESS); } 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) { return (EACCES); } else { unitp->fanksp->ks_ndata = unitp->num_fans_present; bcopy(unitp->fan_kstats, kstatp, sizeof (unitp->fan_kstats)); } mutex_exit(&unitp->umutex); return (DDI_SUCCESS); } int envctrl_encl_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) { return (EACCES); } else { unitp->enclksp->ks_ndata = unitp->num_encl_present; (void) envctrl_get_fpm_status(unitp); /* XXX Need to ad disk updates too ??? */ bcopy(unitp->encl_kstats, kstatp, sizeof (unitp->encl_kstats)); } mutex_exit(&unitp->umutex); return (DDI_SUCCESS); } /* * called with unitp lock held * type, fanspeed and fanflt will be set by the service routines */ static void envctrl_init_fan_kstats(struct envctrlunit *unitp) { int i; ASSERT(MUTEX_HELD(&unitp->umutex)); for (i = 0; i < unitp->num_fans_present; i++) { unitp->fan_kstats[i].instance = 0; unitp->fan_kstats[i].type = 0; unitp->fan_kstats[i].fans_ok = B_TRUE; unitp->fan_kstats[i].fanflt_num = B_FALSE; unitp->fan_kstats[i].fanspeed = B_FALSE; } unitp->fan_kstats[ENVCTRL_FAN_TYPE_PS].type = ENVCTRL_FAN_TYPE_PS; unitp->fan_kstats[ENVCTRL_FAN_TYPE_CPU].type = ENVCTRL_FAN_TYPE_CPU; if (unitp->AFB_present == B_TRUE) unitp->fan_kstats[ENVCTRL_FAN_TYPE_AFB].type = ENVCTRL_FAN_TYPE_AFB; } static void envctrl_init_encl_kstats(struct envctrlunit *unitp) { int i; uint8_t val; struct envctrl_pcf8574_chip chip; int *reg_prop; uint_t len = 0; ASSERT(MUTEX_HELD(&unitp->umutex)); for (i = 0; i < MAX_DEVS; i++) { unitp->encl_kstats[i].instance = I2C_NODEV; } /* * add in kstats now * We ALWAYS HAVE THE FOLLOWING * 1. FSP * 2. AMB TEMPR * 3. (1) CPU TEMPR * 4. (1) 4 slot disk backplane * OPTIONAL * 8 slot backplane * more cpu's */ chip.type = PCF8574A; chip.chip_num = ENVCTRL_PCF8574_DEV6; /* 0x01 port 1 */ envctrl_recv(unitp, (caddr_t *)(void *)&chip, PCF8574); envctrl_add_encl_kstats(unitp, ENVCTRL_ENCL_FSP, INSTANCE_0, chip.val & 0xFF); val = envctrl_get_lm75_temp(unitp) & 0xFF; envctrl_add_encl_kstats(unitp, ENVCTRL_ENCL_AMBTEMPR, INSTANCE_0, val); if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, unitp->dip, DDI_PROP_DONTPASS, ENVCTRL_DISK_LEDS_PR, ®_prop, &len) != DDI_PROP_SUCCESS) { cmn_err(CE_WARN, "prop lookup of %s failed\n", ENVCTRL_DISK_LEDS_PR); return; } ASSERT(len != 0); chip.type = PCF8574; for (i = 0; i < len; i++) { chip.chip_num = backaddrs[i]; if (reg_prop[i] == ENVCTRL_4SLOT_BACKPLANE) { envctrl_recv(unitp, (caddr_t *)(void *)&chip, PCF8574); envctrl_add_encl_kstats(unitp, ENVCTRL_ENCL_BACKPLANE4, i, ~chip.val); controller_present[i] = 1; } if (reg_prop[i] == ENVCTRL_8SLOT_BACKPLANE) { envctrl_recv(unitp, (caddr_t *)(void *)&chip, PCF8574); envctrl_add_encl_kstats(unitp, ENVCTRL_ENCL_BACKPLANE8, i, ~chip.val); controller_present[i] = 1; } } ddi_prop_free((void *)reg_prop); } static void envctrl_mod_encl_kstats(struct envctrlunit *unitp, int type, int instance, uint8_t val) { int i = 0; boolean_t inserted = B_FALSE; ASSERT(MUTEX_HELD(&unitp->umutex)); while (i < MAX_DEVS && inserted == B_FALSE) { if (unitp->encl_kstats[i].instance == instance && unitp->encl_kstats[i].type == type) { unitp->encl_kstats[i].value = val; inserted = B_TRUE; } i++; } } 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_TAZCPU_STRING); (void) sprintf(name1, "%s", ENVCTRL_TAZBLKBRDCPU_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, "envctrl no cpu upa-portid"); } 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->num_fans_failed > 0 && thisled != 0) { status = B_TRUE; } else if (fspval & ENVCTRL_FSP_DISK_ERR) { status = B_TRUE; } else if (fspval & ENVCTRL_FSP_PS_ERR) { status = B_TRUE; } else if (fspval & ENVCTRL_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); } /* * The following routines implement the i2c protocol. * They should be removed once the envctrl_targets.c file is included. */ /* * put host interface into master mode */ static int eHc_start_pcf8584(struct eHc_envcunit *ehcp, uint8_t byteaddress) { uint8_t poll_status; uint8_t discard; int i; /* wait if bus is busy */ i = 0; do { drv_usecwait(1000); poll_status = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1); i++; } while (((poll_status & EHC_S1_NBB) == 0) && i < EHC_MAX_WAIT); if (i == EHC_MAX_WAIT) { DCMNERR(CE_WARN, "eHc_start_pcf8584: I2C bus busy"); return (EHC_FAILURE); } if (poll_status & EHC_S1_BER) { DCMN2ERR(CE_WARN, "eHc_start_pcf8584: I2C bus error"); return (EHC_FAILURE); } if (poll_status & EHC_S1_LAB) { DCMN2ERR(CE_WARN, "eHc_start_pcf8584: Lost arbitration"); return (EHC_FAILURE); } /* load the slave address */ ddi_put8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0, byteaddress); /* generate the "start condition" and clock out the slave address */ ddi_put8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1, EHC_S1_PIN | EHC_S1_ES0 | EHC_S1_STA | EHC_S1_ACK); /* wait for completion of transmission */ i = 0; do { drv_usecwait(1000); poll_status = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1); i++; } while ((poll_status & EHC_S1_PIN) && i < EHC_MAX_WAIT); if (i == EHC_MAX_WAIT) { DCMNERR(CE_WARN, "eHc_start_pcf8584: I2C bus busy"); return (EHC_FAILURE); } if (poll_status & EHC_S1_BER) { DCMN2ERR(CE_WARN, "eHc_start_pcf8584: I2C bus error"); return (EHC_FAILURE); } if (poll_status & EHC_S1_LAB) { DCMN2ERR(CE_WARN, "eHc_start_pcf8584: Lost arbitration"); return (EHC_FAILURE); } if (poll_status & EHC_S1_LRB) { DCMNERR(CE_WARN, "eHc_start_pcf8584: No slave ACK"); return (EHC_NO_SLAVE_ACK); } /* * If this is a read we are setting up for (as indicated by * the least significant byte being set), read * and discard the first byte off the bus - this * is the slave address. */ i = 0; if (byteaddress & EHC_BYTE_READ) { discard = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0); #ifdef lint discard = discard; #endif /* wait for completion of transmission */ do { drv_usecwait(1000); poll_status = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1); i++; } while ((poll_status & EHC_S1_PIN) && i < EHC_MAX_WAIT); if (i == EHC_MAX_WAIT) { DCMNERR(CE_WARN, "eHc_start_pcf8584: I2C bus busy"); return (EHC_FAILURE); } if (poll_status & EHC_S1_BER) { DCMN2ERR(CE_WARN, "eHc_start_pcf8584: I2C bus error"); return (EHC_FAILURE); } if (poll_status & EHC_S1_LAB) { DCMN2ERR(CE_WARN, "eHc_start_pcf8584: Lost arbitration"); return (EHC_FAILURE); } } return (EHC_SUCCESS); } /* * put host interface into slave/receiver mode */ static void eHc_stop_pcf8584(struct eHc_envcunit *ehcp) { ddi_put8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1, EHC_S1_PIN | EHC_S1_ES0 | EHC_S1_STO | EHC_S1_ACK); } static int eHc_read_pcf8584(struct eHc_envcunit *ehcp, uint8_t *data) { uint8_t poll_status; int i = 0; /* Read the byte of interest */ *data = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0); /* wait for completion of transmission */ do { drv_usecwait(1000); poll_status = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1); i++; } while ((poll_status & EHC_S1_PIN) && i < EHC_MAX_WAIT); if (i == EHC_MAX_WAIT) { DCMNERR(CE_WARN, "eHc_read_pcf8584: I2C bus busy"); return (EHC_FAILURE); } if (poll_status & EHC_S1_BER) { DCMN2ERR(CE_WARN, "eHc_read_pcf8584: I2C bus error"); return (EHC_FAILURE); } if (poll_status & EHC_S1_LAB) { DCMN2ERR(CE_WARN, "eHc_read_pcf8584: Lost arbitration"); return (EHC_FAILURE); } return (EHC_SUCCESS); } /* * host interface is in transmitter state, thus mode is master/transmitter * NOTE to Bill: this check the LRB bit (only done in transmit mode). */ static int eHc_write_pcf8584(struct eHc_envcunit *ehcp, uint8_t data) { uint8_t poll_status; int i = 0; /* send the data, EHC_S1_PIN should go to "1" immediately */ ddi_put8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0, data); /* wait for completion of transmission */ do { drv_usecwait(1000); poll_status = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1); i++; } while ((poll_status & EHC_S1_PIN) && i < EHC_MAX_WAIT); if (i == EHC_MAX_WAIT) { DCMNERR(CE_WARN, "eHc_write_pcf8584: I2C bus busy"); return (EHC_FAILURE); } if (poll_status & EHC_S1_BER) { DCMN2ERR(CE_WARN, "eHc_write_pcf8584: I2C bus error"); return (EHC_FAILURE); } if (poll_status & EHC_S1_LAB) { DCMN2ERR(CE_WARN, "eHc_write_pcf8584: Lost arbitration"); return (EHC_FAILURE); } if (poll_status & EHC_S1_LRB) { DCMNERR(CE_WARN, "eHc_write_pcf8584: No slave ACK"); return (EHC_NO_SLAVE_ACK); } return (EHC_SUCCESS); } static int eHc_after_read_pcf8584(struct eHc_envcunit *ehcp, uint8_t *data) { uint8_t discard; uint8_t poll_status; int i = 0; /* set ACK in register S1 to 0 */ ddi_put8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1, EHC_S1_ES0); /* * Read the "byte-before-the-last-byte" - sets PIN bit to '1' */ *data = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0); /* wait for completion of transmission */ do { drv_usecwait(1000); poll_status = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1); i++; } while ((poll_status & EHC_S1_PIN) && i < EHC_MAX_WAIT); if (i == EHC_MAX_WAIT) { DCMNERR(CE_WARN, "eHc_after_read_pcf8584: I2C bus busy"); return (EHC_FAILURE); } if (poll_status & EHC_S1_BER) { DCMN2ERR(CE_WARN, "eHc_after_read_pcf8584: I2C bus error"); return (EHC_FAILURE); } if (poll_status & EHC_S1_LAB) { DCMN2ERR(CE_WARN, "eHc_after_read_pcf8584: Lost arbitration"); return (EHC_FAILURE); } /* * Generate the "stop" condition. */ eHc_stop_pcf8584(ehcp); /* * Read the "last" byte. */ discard = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0); #ifdef lint discard = discard; #endif return (EHC_SUCCESS); } /* * Write to the TDA8444 chip. * byteaddress = chip type base address | chip offset address. */ static int eHc_write_tda8444(struct eHc_envcunit *ehcp, int byteaddress, int instruction, int subaddress, uint8_t *buf, int size) { uint8_t control; int i, status; ASSERT((byteaddress & 0x1) == 0); ASSERT(subaddress < 8); ASSERT(instruction == 0xf || instruction == 0x0); ASSERT(MUTEX_HELD(&ehcp->umutex)); control = (instruction << 4) | subaddress; if ((status = eHc_start_pcf8584(ehcp, byteaddress)) != EHC_SUCCESS) { if (status == EHC_NO_SLAVE_ACK) { /* * Send the "stop" condition. */ eHc_stop_pcf8584(ehcp); } return (EHC_FAILURE); } if ((status = eHc_write_pcf8584(ehcp, control)) != EHC_SUCCESS) { if (status == EHC_NO_SLAVE_ACK) { /* * Send the "stop" condition. */ eHc_stop_pcf8584(ehcp); } return (EHC_FAILURE); } for (i = 0; i < size; i++) { if ((status = eHc_write_pcf8584(ehcp, (buf[i] & 0x3f))) != EHC_SUCCESS) { if (status == EHC_NO_SLAVE_ACK) eHc_stop_pcf8584(ehcp); return (EHC_FAILURE); } } eHc_stop_pcf8584(ehcp); return (EHC_SUCCESS); } /* * Read from PCF8574A chip. * byteaddress = chip type base address | chip offset address. */ static int eHc_read_pcf8574a(struct eHc_envcunit *ehcp, int byteaddress, uint8_t *buf, int size) { int i; int status; uint8_t discard; ASSERT((byteaddress & 0x1) == 0); ASSERT(MUTEX_HELD(&ehcp->umutex)); /* * Put the bus into the start condition */ if ((status = eHc_start_pcf8584(ehcp, EHC_BYTE_READ | byteaddress)) != EHC_SUCCESS) { if (status == EHC_NO_SLAVE_ACK) { /* * Send the "stop" condition. */ eHc_stop_pcf8584(ehcp); /* * Read the last byte - discard it. */ discard = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0); #ifdef lint discard = discard; #endif } return (EHC_FAILURE); } for (i = 0; i < size - 1; i++) { if ((status = eHc_read_pcf8584(ehcp, &buf[i])) != EHC_SUCCESS) { return (EHC_FAILURE); } } /* * Handle the part of the bus protocol which comes * after a read, including reading the last byte. */ if (eHc_after_read_pcf8584(ehcp, &buf[i]) != EHC_SUCCESS) { return (EHC_FAILURE); } return (EHC_SUCCESS); } /* * Write to the PCF8574A chip. * byteaddress = chip type base address | chip offset address. */ static int eHc_write_pcf8574a(struct eHc_envcunit *ehcp, int byteaddress, uint8_t *buf, int size) { int i; int status; ASSERT((byteaddress & 0x1) == 0); ASSERT(MUTEX_HELD(&ehcp->umutex)); /* * Put the bus into the start condition (write) */ if ((status = eHc_start_pcf8584(ehcp, byteaddress)) != EHC_SUCCESS) { if (status == EHC_NO_SLAVE_ACK) { /* * Send the "stop" condition. */ eHc_stop_pcf8584(ehcp); } return (EHC_FAILURE); } /* * Send the data - poll as needed. */ for (i = 0; i < size; i++) { if ((status = eHc_write_pcf8584(ehcp, buf[i])) != EHC_SUCCESS) { if (status == EHC_NO_SLAVE_ACK) eHc_stop_pcf8584(ehcp); return (EHC_FAILURE); } } /* * Transmission complete - generate stop condition and * put device back into slave receiver mode. */ eHc_stop_pcf8584(ehcp); return (EHC_SUCCESS); } /* * Read from the PCF8574 chip. * byteaddress = chip type base address | chip offset address. */ static int eHc_read_pcf8574(struct eHc_envcunit *ehcp, int byteaddress, uint8_t *buf, int size) { int i; int status; uint8_t discard; ASSERT((byteaddress & 0x1) == 0); ASSERT(MUTEX_HELD(&ehcp->umutex)); /* * Put the bus into the start condition */ if ((status = eHc_start_pcf8584(ehcp, EHC_BYTE_READ | byteaddress)) != EHC_SUCCESS) { if (status == EHC_NO_SLAVE_ACK) { /* * Send the "stop" condition. */ eHc_stop_pcf8584(ehcp); /* * Read the last byte - discard it. */ discard = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0); #ifdef lint discard = discard; #endif } return (EHC_FAILURE); } for (i = 0; i < size - 1; i++) { if ((status = eHc_read_pcf8584(ehcp, &buf[i])) != EHC_SUCCESS) { return (EHC_FAILURE); } } /* * Handle the part of the bus protocol which comes * after a read. */ if (eHc_after_read_pcf8584(ehcp, &buf[i]) != EHC_SUCCESS) { return (EHC_FAILURE); } return (EHC_SUCCESS); } /* * Write to the PCF8574 chip. * byteaddress = chip type base address | chip offset address. */ static int eHc_write_pcf8574(struct eHc_envcunit *ehcp, int byteaddress, uint8_t *buf, int size) { int i; int status; ASSERT((byteaddress & 0x1) == 0); ASSERT(MUTEX_HELD(&ehcp->umutex)); /* * Put the bus into the start condition (write) */ if ((status = eHc_start_pcf8584(ehcp, byteaddress)) != EHC_SUCCESS) { if (status == EHC_NO_SLAVE_ACK) { /* * Send the "stop" condition. */ eHc_stop_pcf8584(ehcp); } return (EHC_FAILURE); } /* * Send the data - poll as needed. */ for (i = 0; i < size; i++) { if ((status = eHc_write_pcf8584(ehcp, buf[i])) != EHC_SUCCESS) { if (status == EHC_NO_SLAVE_ACK) eHc_stop_pcf8584(ehcp); return (EHC_FAILURE); } } /* * Transmission complete - generate stop condition and * put device back into slave receiver mode. */ eHc_stop_pcf8584(ehcp); return (EHC_SUCCESS); } /* * Read from the LM75 * byteaddress = chip type base address | chip offset address. */ static int eHc_read_lm75(struct eHc_envcunit *ehcp, int byteaddress, uint8_t *buf, int size) { int i; int status; uint8_t discard; ASSERT((byteaddress & 0x1) == 0); ASSERT(MUTEX_HELD(&ehcp->umutex)); /* * Put the bus into the start condition */ if ((status = eHc_start_pcf8584(ehcp, EHC_BYTE_READ | byteaddress)) != EHC_SUCCESS) { if (status == EHC_NO_SLAVE_ACK) { /* * Send the stop condition. */ eHc_stop_pcf8584(ehcp); /* * Read the last byte - discard it. */ discard = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0); #ifdef lint discard = discard; #endif } return (EHC_FAILURE); } for (i = 0; i < size - 1; i++) { if ((status = eHc_read_pcf8584(ehcp, &buf[i])) != EHC_SUCCESS) { return (EHC_FAILURE); } } /* * Handle the part of the bus protocol which comes * after a read. */ if (eHc_after_read_pcf8584(ehcp, &buf[i]) != EHC_SUCCESS) { return (EHC_FAILURE); } return (EHC_SUCCESS); } /* * Write to the PCF8583 chip. * byteaddress = chip type base address | chip offset address. */ static int eHc_write_pcf8583(struct eHc_envcunit *ehcp, int byteaddress, uint8_t *buf, int size) { int i; int status; ASSERT((byteaddress & 0x1) == 0); ASSERT(MUTEX_HELD(&ehcp->umutex)); if ((status = eHc_start_pcf8584(ehcp, byteaddress)) != EHC_SUCCESS) { if (status == EHC_NO_SLAVE_ACK) { /* * Send the "stop" condition. */ eHc_stop_pcf8584(ehcp); } return (EHC_FAILURE); } /* * Send the data - poll as needed. */ for (i = 0; i < size; i++) { if ((status = eHc_write_pcf8584(ehcp, buf[i])) != EHC_SUCCESS) { if (status == EHC_NO_SLAVE_ACK) eHc_stop_pcf8584(ehcp); return (EHC_FAILURE); } } /* * Transmission complete - generate stop condition and * put device back into slave receiver mode. */ eHc_stop_pcf8584(ehcp); return (EHC_SUCCESS); } /* * Read from the PCF8581 chip. * byteaddress = chip type base address | chip offset address. */ static int eHc_read_pcf8591(struct eHc_envcunit *ehcp, int byteaddress, int channel, int autoinc, int amode, int aenable, uint8_t *buf, int size) { int i; int status; uint8_t control; uint8_t discard; ASSERT((byteaddress & 0x1) == 0); ASSERT(channel < 4); ASSERT(amode < 4); ASSERT(MUTEX_HELD(&ehcp->umutex)); /* * Write the control word to the PCF8591. * Follow the control word with a repeated START byte * rather than a STOP so that reads can follow without giving * up the bus. */ control = ((aenable << 6) | (amode << 4) | (autoinc << 2) | channel); if ((status = eHc_start_pcf8584(ehcp, byteaddress)) != EHC_SUCCESS) { if (status == EHC_NO_SLAVE_ACK) { eHc_stop_pcf8584(ehcp); } return (EHC_FAILURE); } if ((status = eHc_write_pcf8584(ehcp, control)) != EHC_SUCCESS) { if (status == EHC_NO_SLAVE_ACK) eHc_stop_pcf8584(ehcp); return (EHC_FAILURE); } /* * The following two operations, 0x45 to S1, and the byteaddress * to S0, will result in a repeated START being sent out on the bus. * Refer to Fig.8 of Philips Semiconductors PCF8584 product spec. */ ddi_put8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1, EHC_S1_ES0 | EHC_S1_STA | EHC_S1_ACK); ddi_put8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0, EHC_BYTE_READ | byteaddress); i = 0; do { drv_usecwait(1000); status = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s1); i++; } while ((status & EHC_S1_PIN) && i < EHC_MAX_WAIT); if (i == EHC_MAX_WAIT) { DCMNERR(CE_WARN, "eHc_read_pcf8591(): read of S1 failed"); return (EHC_FAILURE); } if (status & EHC_S1_LRB) { DCMNERR(CE_WARN, "eHc_read_pcf8591(): No slave ACK"); /* * Send the stop condition. */ eHc_stop_pcf8584(ehcp); /* * Read the last byte - discard it. */ discard = ddi_get8(ehcp->ctlr_handle, &ehcp->bus_ctl_regs->s0); #ifdef lint discard = discard; #endif return (EHC_FAILURE); } if (status & EHC_S1_BER) { DCMN2ERR(CE_WARN, "eHc_read_pcf8591(): Bus error"); return (EHC_FAILURE); } if (status & EHC_S1_LAB) { DCMN2ERR(CE_WARN, "eHc_read_pcf8591(): Lost Arbitration"); return (EHC_FAILURE); } /* * Discard first read as per PCF8584 master receiver protocol. * This is normally done in the eHc_start_pcf8584() routine. */ if ((status = eHc_read_pcf8584(ehcp, &discard)) != EHC_SUCCESS) { return (EHC_FAILURE); } /* Discard second read as per PCF8591 protocol */ if ((status = eHc_read_pcf8584(ehcp, &discard)) != EHC_SUCCESS) { return (EHC_FAILURE); } for (i = 0; i < size - 1; i++) { if ((status = eHc_read_pcf8584(ehcp, &buf[i])) != EHC_SUCCESS) { return (EHC_FAILURE); } } if (eHc_after_read_pcf8584(ehcp, &buf[i]) != EHC_SUCCESS) { return (EHC_FAILURE); } return (EHC_SUCCESS); }