/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 #include #include /* must be before netract_gen.h */ #include #include #include #ifdef DEBUG #define dbg_print(level, str) cmn_err(level, str); static int pcf8574_debug = 0x00000102; #else #define dbg_print(level, str) {; } #endif #define CV_LOCK(retval) \ { \ mutex_enter(&unitp->umutex); \ while (unitp->pcf8574_flags == PCF8574_BUSY) { \ if (cv_wait_sig(&unitp->pcf8574_cv, \ &unitp->umutex) <= 0) { \ mutex_exit(&unitp->umutex); \ return (retval); \ } \ } \ unitp->pcf8574_flags = PCF8574_BUSY; \ mutex_exit(&unitp->umutex); \ } #define CV_UNLOCK \ { \ mutex_enter(&unitp->umutex); \ unitp->pcf8574_flags = 0; \ cv_signal(&unitp->pcf8574_cv); \ mutex_exit(&unitp->umutex); \ } static int nct_p10fan_patch = 0; /* Fan patch for P1.0 */ static void *pcf8574_soft_statep; /* * cb ops (only need open,close,read,write,ioctl) */ static int pcf8574_open(dev_t *, int, int, cred_t *); static int pcf8574_close(dev_t, int, int, cred_t *); static int pcf8574_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); static int pcf8574_read(dev_t dev, struct uio *uiop, cred_t *cred_p); static int pcf8574_chpoll(dev_t, short, int, short *, struct pollhead **); static uint_t pcf8574_intr(caddr_t arg); static int pcf8574_io(dev_t, struct uio *, int); static struct cb_ops pcf8574_cbops = { pcf8574_open, /* open */ pcf8574_close, /* close */ nodev, /* strategy */ nodev, /* print */ nodev, /* dump */ pcf8574_read, /* read */ nodev, /* write */ pcf8574_ioctl, /* ioctl */ nodev, /* devmap */ nodev, /* mmap */ nodev, /* segmap */ pcf8574_chpoll, /* poll */ ddi_prop_op, /* cb_prop_op */ NULL, /* streamtab */ D_NEW | D_MP | D_HOTPLUG, /* Driver compatibility flag */ CB_REV, /* rev */ nodev, /* int (*cb_aread)() */ nodev /* int (*cb_awrite)() */ }; /* * dev ops */ static int pcf8574_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); static int pcf8574_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); /* kstat routines */ static int pcf8574_add_kstat(struct pcf8574_unit *, scsb_fru_status_t); static void pcf8574_delete_kstat(struct pcf8574_unit *); static int pcf8574_kstat_update(kstat_t *, int); static int pcf8574_read_chip(struct pcf8574_unit *unitp, uint16_t size); static int pcf8574_write_chip(struct pcf8574_unit *unitp, uint16_t size, uint8_t bitpattern); static int pcf8574_read_props(struct pcf8574_unit *unitp); static int pcf8574_init_chip(struct pcf8574_unit *unitp, int); /* * SCSB callback function */ static void pcf8574_callback(void *, scsb_fru_event_t, scsb_fru_status_t); extern int scsb_intr_register(uint_t (*intr_handler)(caddr_t), caddr_t, fru_id_t); extern int scsb_intr_unregister(fru_id_t); extern int nct_i2c_transfer(i2c_client_hdl_t i2c_hdl, i2c_transfer_t *i2c_tran); static struct dev_ops pcf8574_ops = { DEVO_REV, 0, ddi_getinfo_1to1, nulldev, nulldev, pcf8574_attach, pcf8574_detach, nodev, &pcf8574_cbops, NULL }; extern struct mod_ops mod_driverops; static struct modldrv pcf8574_modldrv = { &mod_driverops, /* type of module - driver */ "Netract pcf8574 (gpio) %I% ", &pcf8574_ops, }; static struct modlinkage pcf8574_modlinkage = { MODREV_1, &pcf8574_modldrv, 0 }; /* char _depends_on[] = "misc/i2c_svc drv/scsb"; */ int _init(void) { register int error; error = mod_install(&pcf8574_modlinkage); if (!error) { (void) ddi_soft_state_init(&pcf8574_soft_statep, sizeof (struct pcf8574_unit), PCF8574_MAX_DEVS); } return (error); } int _fini(void) { register int error; error = mod_remove(&pcf8574_modlinkage); if (!error) ddi_soft_state_fini(&pcf8574_soft_statep); return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&pcf8574_modlinkage, modinfop)); } /*ARGSUSED*/ static int pcf8574_open(dev_t *devp, int flags, int otyp, cred_t *credp) { struct pcf8574_unit *unitp; register int instance; int err = DDI_SUCCESS; instance = getminor(*devp); if (instance < 0) { return (ENXIO); } unitp = (struct pcf8574_unit *) ddi_get_soft_state(pcf8574_soft_statep, instance); if (unitp == NULL) { return (ENXIO); } if (otyp != OTYP_CHR) { return (EINVAL); } mutex_enter(&unitp->umutex); if (flags & FEXCL) { if (unitp->pcf8574_oflag != 0) { err = EBUSY; } else { unitp->pcf8574_oflag = FEXCL; } } else { if (unitp->pcf8574_oflag == FEXCL) { err = EBUSY; } else { unitp->pcf8574_oflag = FOPEN; } } mutex_exit(&unitp->umutex); return (err); } /*ARGSUSED*/ static int pcf8574_close(dev_t dev, int flags, int otyp, cred_t *credp) { struct pcf8574_unit *unitp; register int instance; #ifdef lint flags = flags; otyp = otyp; #endif instance = getminor(dev); if (instance < 0) { return (ENXIO); } unitp = (struct pcf8574_unit *) ddi_get_soft_state(pcf8574_soft_statep, instance); if (unitp == NULL) { return (ENXIO); } mutex_enter(&unitp->umutex); unitp->pcf8574_oflag = 0; mutex_exit(&unitp->umutex); return (DDI_SUCCESS); } /*ARGSUSED*/ static int pcf8574_read(dev_t dev, struct uio *uiop, cred_t *cred_p) { return (pcf8574_io(dev, uiop, B_READ)); } static int pcf8574_io(dev_t dev, struct uio *uiop, int rw) { struct pcf8574_unit *unitp; register int instance; uint16_t bytes_to_rw; int err = DDI_SUCCESS; err = 0; instance = getminor(dev); if (instance < 0) { return (ENXIO); } unitp = (struct pcf8574_unit *) ddi_get_soft_state(pcf8574_soft_statep, instance); if (unitp == NULL) { return (ENXIO); } if ((bytes_to_rw = uiop->uio_resid) > PCF8574_TRAN_SIZE) { return (EINVAL); } CV_LOCK(EINTR) if (rw == B_WRITE) { err = uiomove(unitp->i2c_tran->i2c_wbuf, bytes_to_rw, UIO_WRITE, uiop); if (!err) { err = pcf8574_write_chip(unitp, bytes_to_rw, unitp->writemask); } } else { err = pcf8574_read_chip(unitp, bytes_to_rw); if (!err) { err = uiomove(unitp->i2c_tran->i2c_rbuf, bytes_to_rw, UIO_READ, uiop); } } CV_UNLOCK if (err) err = EIO; return (err); } static int pcf8574_do_resume(dev_info_t *dip) { int instance = ddi_get_instance(dip); struct pcf8574_unit *unitp = ddi_get_soft_state(pcf8574_soft_statep, instance); if (unitp == NULL) { return (ENXIO); } CV_UNLOCK return (DDI_SUCCESS); } static int pcf8574_do_detach(dev_info_t *dip) { struct pcf8574_unit *unitp; int instance; uint_t attach_flag; instance = ddi_get_instance(dip); unitp = ddi_get_soft_state(pcf8574_soft_statep, instance); attach_flag = unitp->attach_flag; if (attach_flag & PCF8574_INTR_ADDED) { scsb_intr_unregister((fru_id_t)unitp->props.slave_address); } if (attach_flag & PCF8574_KSTAT_INIT) { pcf8574_delete_kstat(unitp); } if (attach_flag & PCF8574_LOCK_INIT) { mutex_destroy(&unitp->umutex); cv_destroy(&unitp->pcf8574_cv); } scsb_fru_unregister((void *)unitp, (fru_id_t)unitp->props.slave_address); if (attach_flag & PCF8574_ALLOC_TRANSFER) { /* * restore the lengths to allocated lengths * before freeing. */ unitp->i2c_tran->i2c_wlen = MAX_WLEN; unitp->i2c_tran->i2c_rlen = MAX_RLEN; i2c_transfer_free(unitp->pcf8574_hdl, unitp->i2c_tran); } if (attach_flag & PCF8574_REGISTER_CLIENT) { i2c_client_unregister(unitp->pcf8574_hdl); } if (attach_flag & PCF8574_MINORS_CREATED) { ddi_remove_minor_node(dip, NULL); } if (attach_flag & PCF8574_PROPS_READ) { if (unitp->pcf8574_type == PCF8574_ADR_CPUVOLTAGE && unitp->props.num_chans_used != 0) { ddi_prop_free(unitp->props.channels_in_use); } else { ddi_prop_remove(DDI_DEV_T_NONE, dip, "interrupt-priorities"); } } if (attach_flag & PCF8574_SOFT_STATE_ALLOC) { ddi_soft_state_free(pcf8574_soft_statep, instance); } return (DDI_SUCCESS); } /* * NOTE**** * The OBP will create device tree node for all I2C devices which * may be present in a system. This means, even if the device is * not physically present, the device tree node exists. We also * will succeed the attach routine, since currently there is no * hotplug support in the I2C bus, and the FRUs need to be hot * swappable. Only during an I2C transaction we figure out whether * the particular I2C device is actually present in the system * by looking at the system controller board register. The fantray * and power-supply devices may be swapped any time after system * reboot, and the way we can make sure that the device is attached * to the driver, is by always keeping the driver loaded, and report * an error during the actual transaction. */ static int pcf8574_do_attach(dev_info_t *dip) { register struct pcf8574_unit *unitp; int instance; char name[MAXNAMELEN]; int i; pcf8574_channel_t *chp; scsb_fru_status_t dev_presence; instance = ddi_get_instance(dip); #ifdef DEBUG if (pcf8574_debug & 0x04) cmn_err(CE_NOTE, "pcf8574_attach: instance=%d\n", instance); #endif /* DEBUG */ if (ddi_soft_state_zalloc(pcf8574_soft_statep, instance) != DDI_SUCCESS) { return (DDI_FAILURE); } unitp = ddi_get_soft_state(pcf8574_soft_statep, instance); if (unitp == NULL) { ddi_soft_state_free(pcf8574_soft_statep, instance); return (DDI_FAILURE); } unitp->dip = dip; unitp->attach_flag = PCF8574_SOFT_STATE_ALLOC; if (pcf8574_read_props(unitp) != DDI_PROP_SUCCESS) { ddi_soft_state_free(pcf8574_soft_statep, instance); return (DDI_FAILURE); } unitp->attach_flag |= PCF8574_PROPS_READ; /* * Set the current operating mode to NORMAL_MODE. */ unitp->current_mode = ENVCTRL_NORMAL_MODE; snprintf(unitp->pcf8574_name, PCF8574_NAMELEN, "%s%d", ddi_driver_name(dip), instance); if (unitp->pcf8574_type == PCF8574_TYPE_PWRSUPP) { (void) sprintf(name, "pwrsuppply"); if (ddi_create_minor_node(dip, name, S_IFCHR, instance, PCF8574_NODE_TYPE, NULL) == DDI_FAILURE) { ddi_remove_minor_node(dip, NULL); pcf8574_do_detach(dip); return (DDI_FAILURE); } } else if (unitp->pcf8574_type == PCF8574_TYPE_FANTRAY) { (void) sprintf(name, "fantray"); if (ddi_create_minor_node(dip, name, S_IFCHR, instance, PCF8574_NODE_TYPE, NULL) == DDI_FAILURE) { ddi_remove_minor_node(dip, NULL); pcf8574_do_detach(dip); return (DDI_FAILURE); } } else if (unitp->pcf8574_type == PCF8574_TYPE_CPUVOLTAGE) { (void) sprintf(name, "cpuvoltage"); if (ddi_create_minor_node(dip, name, S_IFCHR, instance, PCF8574_NODE_TYPE, NULL) == DDI_FAILURE) { ddi_remove_minor_node(dip, NULL); pcf8574_do_detach(dip); return (DDI_FAILURE); } } else { return (DDI_FAILURE); } unitp->attach_flag |= PCF8574_MINORS_CREATED; /* * Now we need read/write masks since all the 8574 bits can be either * read/written, but some ports are intended to be RD/WR only, or RW * If no channels-in-use propoerty, set default values. */ if (unitp->pcf8574_type == PCF8574_TYPE_FANTRAY) { unitp->readmask = PCF8574_FAN_READMASK; unitp->writemask = PCF8574_FAN_WRITEMASK; } if (unitp->pcf8574_type == PCF8574_TYPE_PWRSUPP) { unitp->readmask = PCF8574_PS_READMASK; unitp->writemask = PCF8574_PS_WRITEMASK; } for (i = unitp->props.num_chans_used, chp = unitp->props.channels_in_use; i; --i, ++chp) { unitp->readmask |= (uint8_t)( (chp->io_dir == I2C_PROP_IODIR_IN || chp->io_dir == I2C_PROP_IODIR_INOUT) << chp->port); unitp->writemask |= (uint8_t)( (chp->io_dir == I2C_PROP_IODIR_OUT || chp->io_dir == I2C_PROP_IODIR_INOUT) << chp->port); } #ifdef DEBUG cmn_err(CE_NOTE, "pcf8574_do_attach: readmask = 0x%x \ writemask = 0x%x\n", unitp->readmask, unitp->writemask); #endif /* DEBUG */ if (i2c_client_register(dip, &unitp->pcf8574_hdl) != I2C_SUCCESS) { pcf8574_do_detach(dip); return (DDI_FAILURE); } unitp->attach_flag |= PCF8574_REGISTER_CLIENT; /* * Allocate the I2C_transfer structure. The same structure * is used throughout the driver. */ if (i2c_transfer_alloc(unitp->pcf8574_hdl, &unitp->i2c_tran, MAX_WLEN, MAX_RLEN, KM_SLEEP) != I2C_SUCCESS) { pcf8574_do_detach(dip); return (DDI_FAILURE); } unitp->attach_flag |= PCF8574_ALLOC_TRANSFER; /* * To begin with we set the mode to I2C_RD. */ unitp->i2c_tran->i2c_flags = I2C_RD; unitp->i2c_tran->i2c_version = I2C_XFER_REV; /* * Set the busy flag and open flag to 0. */ unitp->pcf8574_flags = 0; unitp->pcf8574_oflag = 0; mutex_init(&unitp->umutex, NULL, MUTEX_DRIVER, NULL); cv_init(&unitp->pcf8574_cv, NULL, CV_DRIVER, NULL); unitp->attach_flag |= PCF8574_LOCK_INIT; /* * Register out callback function with the SCSB driver, and save * the returned value to check that the device instance exists. */ dev_presence = scsb_fru_register(pcf8574_callback, (void *)unitp, (fru_id_t)unitp->props.slave_address); if (dev_presence == FRU_NOT_AVAILABLE) { scsb_fru_unregister((void *)unitp, (fru_id_t)unitp->props.slave_address); } /* * Add the kstats. First we need to get the property values * depending on the device type. For example, for the fan * tray there will be a different set of properties, and there * will be another for the powersupplies, and another one for * the CPU voltage monitor. Initialize the kstat structures with * these values. */ if (pcf8574_add_kstat(unitp, dev_presence) != DDI_SUCCESS) { pcf8574_do_detach(dip); return (DDI_FAILURE); } unitp->attach_flag |= PCF8574_KSTAT_INIT; /* * Due to observed behavior on Solaris 8, the handler must be * registered before any interrupts are enabled, * in spite of what the ddi_get_iblock_cookie() manual says. * As per the HW/SW spec, by default interrupts are disabled. */ if (dev_presence == FRU_PRESENT) { /* program the chip */ pcf8574_init_chip(unitp, 0); /* Disable intr first */ } if (unitp->pcf8574_canintr == PCF8574_INTR_ON) { #ifdef DEBUG if (pcf8574_debug & 0x0004) cmn_err(CE_NOTE, "registering pcf9574 interrupt " "handler"); #endif /* DEBUG */ if (scsb_intr_register(pcf8574_intr, (void *)unitp, (fru_id_t)unitp->props.slave_address) == DDI_SUCCESS) { unitp->pcf8574_canintr |= PCF8574_INTR_ENABLED; unitp->attach_flag |= PCF8574_INTR_ADDED; } else { pcf8574_do_detach(dip); return (DDI_FAILURE); } } ddi_report_dev(dip); return (DDI_SUCCESS); } static int pcf8574_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { switch (cmd) { case DDI_ATTACH: return (pcf8574_do_attach(dip)); case DDI_RESUME: return (pcf8574_do_resume(dip)); default: return (DDI_FAILURE); } } static int pcf8574_do_suspend(dev_info_t *dip) { int instance = ddi_get_instance(dip); struct pcf8574_unit *unitp = ddi_get_soft_state(pcf8574_soft_statep, instance); if (unitp == NULL) { return (ENXIO); } /* * Set the busy flag so that future transactions block * until resume. */ CV_LOCK(ENXIO) return (DDI_SUCCESS); } static int pcf8574_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { switch (cmd) { case DDI_DETACH: return (pcf8574_do_detach(dip)); case DDI_SUSPEND: return (pcf8574_do_suspend(dip)); default: return (DDI_FAILURE); } } static int pcf8574_chpoll(dev_t dev, short events, int anyyet, short *reventsp, struct pollhead **phpp) { struct pcf8574_unit *unitp; int instance; instance = getminor(dev); if ((unitp = (struct pcf8574_unit *)ddi_get_soft_state( pcf8574_soft_statep, instance)) == NULL) { return (ENXIO); } *reventsp = 0; mutex_enter(&unitp->umutex); if (unitp->poll_event) { *reventsp = unitp->poll_event; unitp->poll_event = 0; } else if ((events & POLLIN) && !anyyet) *phpp = &unitp->poll; mutex_exit(&unitp->umutex); return (0); } /* * In normal scenarios, this function should never get called. * But, we will still come back and call this function if scsb * interrupt sources does not indicate an scsb interrupt. We may * come to this situation when SunVTS env4test is independently * changing the device registers. */ uint_t pcf8574_intr(caddr_t arg) { int ic; uint8_t value; struct pcf8574_unit *unitp = (struct pcf8574_unit *)(void *)arg; scsb_fru_status_t dev_presence; i2c_transfer_t *tp = unitp->i2c_tran; ic = DDI_INTR_CLAIMED; #ifdef DEBUG cmn_err(CE_NOTE, " In the interrupt service routine, %x", unitp->props.slave_address); #endif /* * Initiate an I2C transaction to find out * whether this is the device which interrupted. */ mutex_enter(&unitp->umutex); while (unitp->pcf8574_flags == PCF8574_BUSY) { if (cv_wait_sig(&unitp->pcf8574_cv, &unitp->umutex) <= 0) { mutex_exit(&unitp->umutex); return (DDI_INTR_UNCLAIMED); } } unitp->pcf8574_flags = PCF8574_BUSY; mutex_exit(&unitp->umutex); switch (unitp->pcf8574_type) { case PCF8574_TYPE_CPUVOLTAGE: { dev_presence = FRU_PRESENT; break; } case PCF8574_TYPE_PWRSUPP: { envctrl_pwrsupp_t *envp = (envctrl_pwrsupp_t *)unitp->envctrl_kstat; dev_presence = envp->ps_present; break; } case PCF8574_TYPE_FANTRAY: { envctrl_fantray_t *envp = (envctrl_fantray_t *)unitp->envctrl_kstat; dev_presence = envp->fan_present; break; } } if (dev_presence != FRU_PRESENT) { ic = DDI_INTR_UNCLAIMED; goto intr_exit; } if (pcf8574_read_chip(unitp, 1) != I2C_SUCCESS) { ic = DDI_INTR_UNCLAIMED; goto intr_exit; } value = unitp->i2c_tran->i2c_rbuf[0]; /* * If interrupt is already masked, return */ if (value & PCF8574_INTRMASK_BIT) { ic = DDI_INTR_UNCLAIMED; goto intr_exit; } /* * In case a fault bit is set, claim the interrupt. */ switch (unitp->pcf8574_type) { case PCF8574_TYPE_PWRSUPP: { envctrl_pwrsupp_t *envp = (envctrl_pwrsupp_t *)unitp->envctrl_kstat; if (PCF8574_PS_FAULT(value) || PCF8574_PS_TEMPOK(value) || PCF8574_PS_ONOFF(value) || PCF8574_PS_FANOK(value)) { envp->ps_ok = PCF8574_PS_FAULT(value); envp->temp_ok = PCF8574_PS_TEMPOK(value); envp->psfan_ok = PCF8574_PS_FANOK(value); envp->on_state = PCF8574_PS_ONOFF(value); envp->ps_ver = PCF8574_PS_TYPE(value); tp->i2c_wbuf[0] = PCF8574_PS_DEFAULT | PCF8574_PS_MASKINTR; tp->i2c_wlen = 1; tp->i2c_rlen = 0; tp->i2c_flags = I2C_WR; unitp->i2c_status = nct_i2c_transfer(unitp->pcf8574_hdl, tp); unitp->poll_event = POLLIN; pollwakeup(&unitp->poll, POLLIN); } else { ic = DDI_INTR_UNCLAIMED; } } break; case PCF8574_TYPE_FANTRAY: { envctrl_fantray_t *envp = (envctrl_fantray_t *)unitp->envctrl_kstat; if (!PCF8574_FAN_FAULT(value)) { envp->fan_ver = PCF8574_FAN_TYPE(value); envp->fan_ok = PCF8574_FAN_FAULT(value); envp->fanspeed = PCF8574_FAN_FANSPD(value); tp->i2c_wbuf[0] = PCF8574_FAN_DEFAULT | PCF8574_FAN_MASKINTR; tp->i2c_wlen = 1; tp->i2c_rlen = 0; tp->i2c_flags = I2C_WR; unitp->i2c_status = nct_i2c_transfer(unitp->pcf8574_hdl, tp); unitp->poll_event = POLLIN; pollwakeup(&unitp->poll, POLLIN); } else { ic = DDI_INTR_UNCLAIMED; } } break; default: ic = DDI_INTR_UNCLAIMED; } /* switch */ intr_exit: mutex_enter(&unitp->umutex); unitp->pcf8574_flags = 0; cv_signal(&unitp->pcf8574_cv); mutex_exit(&unitp->umutex); return (ic); } static int call_copyin(caddr_t arg, struct pcf8574_unit *unitp, int mode) { uchar_t *wbuf; uchar_t *rbuf; i2c_transfer_t i2ct; i2c_transfer_t *i2ctp = unitp->i2c_tran; if (ddi_copyin((void *)arg, (caddr_t)&i2ct, sizeof (i2c_transfer_t), mode) != DDI_SUCCESS) { return (I2C_FAILURE); } /* * Save the read and write buffer pointers in the transfer * structure, otherwise these will get overwritten when we * do a bcopy. Restore once done. */ wbuf = i2ctp->i2c_wbuf; rbuf = i2ctp->i2c_rbuf; bcopy(&i2ct, i2ctp, sizeof (i2c_transfer_t)); i2ctp->i2c_wbuf = wbuf; i2ctp->i2c_rbuf = rbuf; /* * copyin the read and write buffers to the saved buffers. */ if (i2ct.i2c_wlen != 0) { if (ddi_copyin(i2ct.i2c_wbuf, (caddr_t)i2ctp->i2c_wbuf, i2ct.i2c_wlen, mode) != DDI_SUCCESS) { return (I2C_FAILURE); } } return (I2C_SUCCESS); } static int call_copyout(caddr_t arg, struct pcf8574_unit *unitp, int mode) { i2c_transfer_t i2ct; i2c_transfer_t *i2ctp = unitp->i2c_tran; /* * We will copyout the last three fields only, skipping * the remaining ones, before copying the rbuf to the * user buffer. */ int uskip = sizeof (i2c_transfer_t) - 3*sizeof (int16_t), kskip = sizeof (i2c_transfer_t) - 3*sizeof (int16_t); /* * First copyin the user structure to the temporary i2ct, * so that we have the wbuf and rbuf addresses in it. */ uskip = sizeof (i2c_transfer_t) - 3 * (sizeof (uint16_t)); /* * copyout the last three out fields now. */ if (ddi_copyout((void *)((intptr_t)i2ctp+kskip), (void *) ((intptr_t)arg + uskip), 3*sizeof (uint16_t), mode) != DDI_SUCCESS) { return (I2C_FAILURE); } /* * In case we have something to write, get the address of the read * buffer. */ if (i2ctp->i2c_rlen > i2ctp->i2c_r_resid) { if (ddi_copyin((void *)arg, &i2ct, sizeof (i2c_transfer_t), mode) != DDI_SUCCESS) { return (I2C_FAILURE); } /* * copyout the read buffer to the saved user buffer in i2ct. */ if (ddi_copyout(i2ctp->i2c_rbuf, i2ct.i2c_rbuf, i2ctp->i2c_rlen - i2ctp->i2c_r_resid, mode) != DDI_SUCCESS) { return (I2C_FAILURE); } } return (I2C_SUCCESS); } /*ARGSUSED*/ static int pcf8574_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { struct pcf8574_unit *unitp; register int instance; int err = 0; uint8_t value, inval, outval; scsb_fru_status_t dev_presence; instance = getminor(dev); if (instance < 0) { return (ENXIO); } unitp = (struct pcf8574_unit *) ddi_get_soft_state(pcf8574_soft_statep, instance); if (unitp == NULL) { return (ENXIO); } dev_presence = scsb_fru_status((uchar_t)unitp->props.slave_address); CV_LOCK(EINTR) switch (cmd) { case ENVC_IOC_INTRMASK: if (dev_presence == FRU_NOT_PRESENT) { break; } if (ddi_copyin((caddr_t)arg, (caddr_t)&inval, sizeof (uint8_t), mode) != DDI_SUCCESS) { err = EFAULT; break; } if (inval != 0 && inval != 1) { err = EINVAL; } else { unitp->i2c_tran->i2c_wbuf[0] = PCF8574_INT_MASK(inval); if (pcf8574_write_chip(unitp, 1, PCF8574_INTRMASK_BIT) != I2C_SUCCESS) { err = EFAULT; } } break; case ENVC_IOC_SETFAN: if (unitp->pcf8574_type != PCF8574_TYPE_FANTRAY) { err = EINVAL; break; } if (dev_presence == FRU_NOT_PRESENT) { err = EINVAL; break; } if (ddi_copyin((caddr_t)arg, (caddr_t)&inval, sizeof (uint8_t), mode) != DDI_SUCCESS) { err = EFAULT; break; } if (inval != PCF8574_FAN_SPEED_LOW && inval != PCF8574_FAN_SPEED_HIGH) { err = EINVAL; break; } unitp->i2c_tran->i2c_wbuf[0] = PCF8574_FAN_SPEED(inval); if (pcf8574_write_chip(unitp, 1, PCF8574_FANSPEED_BIT) != I2C_SUCCESS) { err = EFAULT; } break; case ENVC_IOC_SETSTATUS: /* * Allow this ioctl only in DIAG mode. */ if (unitp->current_mode != ENVCTRL_DIAG_MODE) { err = EINVAL; } else { if (dev_presence == FRU_NOT_PRESENT) { err = EINVAL; break; } if (ddi_copyin((caddr_t)arg, (caddr_t)&inval, sizeof (uint8_t), mode) != DDI_SUCCESS) { err = EFAULT; } else { unitp->i2c_tran->i2c_wbuf[0] = inval & 0xff; if (pcf8574_write_chip(unitp, 1, 0xff) != I2C_SUCCESS) { err = EFAULT; } } } break; case ENVC_IOC_GETFAN: case ENVC_IOC_GETSTATUS: case ENVC_IOC_GETTYPE: case ENVC_IOC_GETFAULT: case ENVC_IOC_PSTEMPOK: case ENVC_IOC_PSFANOK: case ENVC_IOC_PSONOFF: { if (dev_presence == FRU_NOT_PRESENT) { err = EINVAL; break; } if (pcf8574_read_chip(unitp, 1) != I2C_SUCCESS) { err = EFAULT; break; } value = unitp->i2c_tran->i2c_rbuf[0]; if (cmd == ENVC_IOC_GETFAN) { if (unitp->pcf8574_type != PCF8574_TYPE_FANTRAY) { err = EINVAL; break; } else { outval = PCF8574_FAN_FANSPD(value); } } else if (cmd == ENVC_IOC_GETSTATUS) { outval = value; } else if (cmd == ENVC_IOC_GETTYPE) { if (unitp->pcf8574_type == PCF8574_TYPE_PWRSUPP) outval = PCF8574_PS_TYPE(value); if (unitp->pcf8574_type == PCF8574_TYPE_FANTRAY) outval = PCF8574_FAN_TYPE(value); } else if (cmd == ENVC_IOC_GETFAULT) { if (unitp->pcf8574_type == PCF8574_TYPE_PWRSUPP) outval = PCF8574_PS_FAULT(value); if (unitp->pcf8574_type == PCF8574_TYPE_FANTRAY) outval = PCF8574_PS_FAULT(value); } else if (cmd == ENVC_IOC_PSTEMPOK) { outval = PCF8574_PS_TEMPOK(value); } else if (cmd == ENVC_IOC_PSFANOK) { outval = PCF8574_PS_FANOK(value); } else if (cmd == ENVC_IOC_PSONOFF) { outval = PCF8574_PS_ONOFF(value); } else { outval = 0; } if (ddi_copyout((caddr_t)&outval, (caddr_t)arg, sizeof (uint8_t), mode) != DDI_SUCCESS) { err = EFAULT; } } break; case ENVC_IOC_GETMODE: { uint8_t curr_mode = unitp->current_mode; if (ddi_copyout((caddr_t)&curr_mode, (caddr_t)arg, sizeof (uint8_t), mode) != DDI_SUCCESS) { err = EFAULT; } break; } case ENVC_IOC_SETMODE: { uint8_t curr_mode; if (ddi_copyin((caddr_t)arg, (caddr_t)&curr_mode, sizeof (uint8_t), mode) != DDI_SUCCESS) { err = EFAULT; break; } if (curr_mode == ENVCTRL_DIAG_MODE || curr_mode == ENVCTRL_NORMAL_MODE) { unitp->current_mode = curr_mode; /* Don't do anything */ } break; } case I2CDEV_TRAN: if (call_copyin((caddr_t)arg, unitp, mode) != DDI_SUCCESS) { err = EFAULT; break; } unitp->i2c_status = err = nct_i2c_transfer(unitp->pcf8574_hdl, unitp->i2c_tran); if (err != I2C_SUCCESS) { err = EIO; } else { if (call_copyout((caddr_t)arg, unitp, mode) != DDI_SUCCESS) { err = EFAULT; break; } } break; default: err = EINVAL; } CV_UNLOCK return (err); } static int pcf8574_add_kstat(struct pcf8574_unit *unitp, scsb_fru_status_t dev_presence) { char ksname[50]; int id; uint8_t i2c_address = unitp->props.slave_address; /* * We create the kstat depending on the device function, * allocate the kstat placeholder and initialize the * values. */ unitp->envctrl_kstat = NULL; switch (unitp->pcf8574_type) { case PCF8574_TYPE_CPUVOLTAGE: { if ((unitp->kstatp = kstat_create(I2C_PCF8574_NAME, unitp->instance, I2C_KSTAT_CPUVOLTAGE, "misc", KSTAT_TYPE_RAW, sizeof (envctrl_cpuvoltage_t), KSTAT_FLAG_PERSISTENT)) != NULL) { if ((unitp->envctrl_kstat = kmem_zalloc( sizeof (envctrl_cpuvoltage_t), KM_NOSLEEP)) == NULL) { kstat_delete(unitp->kstatp); return (DDI_FAILURE); } } else { return (DDI_FAILURE); } break; } case PCF8574_TYPE_PWRSUPP: { envctrl_pwrsupp_t *envp; if (i2c_address == PCF8574_ADR_PWRSUPPLY1) { id = 1; } else if (i2c_address == PCF8574_ADR_PWRSUPPLY2) { id = 2; } else { id = i2c_address - PCF8574_ADR_PWRSUPPLY1; } sprintf(ksname, "%s%d", I2C_KSTAT_PWRSUPPLY, id); if ((unitp->kstatp = kstat_create(I2C_PCF8574_NAME, unitp->instance, ksname, "misc", KSTAT_TYPE_RAW, sizeof (envctrl_pwrsupp_t), KSTAT_FLAG_PERSISTENT)) != NULL) { if ((unitp->envctrl_kstat = kmem_zalloc( sizeof (envctrl_pwrsupp_t), KM_NOSLEEP)) == NULL) { kstat_delete(unitp->kstatp); return (DDI_FAILURE); } /* * Initialize the kstat fields. Need to initialize * the present field from SCSB info (dev_presence) */ envp = (envctrl_pwrsupp_t *)unitp->envctrl_kstat; envp->ps_present = dev_presence; envp->ps_ok = 0; envp->temp_ok = 0; envp->psfan_ok = 0; envp->on_state = 0; envp->ps_ver = 0; } else { return (DDI_FAILURE); } break; } case PCF8574_TYPE_FANTRAY: { envctrl_fantray_t *envp; if (i2c_address == PCF8574_ADR_FANTRAY1) { id = 1; } else if (i2c_address == PCF8574_ADR_FANTRAY2) { id = 2; } else { id = i2c_address - PCF8574_ADR_FANTRAY1; } sprintf(ksname, "%s%d", I2C_KSTAT_FANTRAY, id); if ((unitp->kstatp = kstat_create(I2C_PCF8574_NAME, unitp->instance, ksname, "misc", KSTAT_TYPE_RAW, sizeof (envctrl_fantray_t), KSTAT_FLAG_PERSISTENT | KSTAT_FLAG_WRITABLE)) != NULL) { if ((unitp->envctrl_kstat = kmem_zalloc( sizeof (envctrl_fantray_t), KM_NOSLEEP)) == NULL) { kstat_delete(unitp->kstatp); return (DDI_FAILURE); } /* * Initialize the kstat fields. Need to initialize * the present field from SCSB info (dev_presence) */ envp = (envctrl_fantray_t *)unitp->envctrl_kstat; envp->fan_present = dev_presence; envp->fan_ok = 0; envp->fanspeed = PCF8574_FAN_SPEED60; envp->fan_ver = 0; } else { return (DDI_FAILURE); } break; } default: return (DDI_FAILURE); } unitp->kstatp->ks_private = (void *)unitp; unitp->kstatp->ks_update = pcf8574_kstat_update; kstat_install(unitp->kstatp); return (DDI_SUCCESS); } /* * This function reads a single byte from the pcf8574 chip, for use by the * kstat routines. The protocol for read will depend on the function. */ static int pcf8574_read_chip(struct pcf8574_unit *unitp, uint16_t size) { int retval, i; i2c_transfer_t *tp = unitp->i2c_tran; tp->i2c_flags = I2C_RD; tp->i2c_rlen = size; tp->i2c_wlen = 0; /* * Read the bytes from the pcf8574, mask off the * non-read bits and return the value. Block with * the driverwide lock. */ unitp->i2c_status = retval = nct_i2c_transfer(unitp->pcf8574_hdl, unitp->i2c_tran); if (retval != I2C_SUCCESS) { return (retval); } for (i = 0; i < size; i++) { tp->i2c_rbuf[i] &= unitp->readmask; } return (I2C_SUCCESS); } /* * This function writes a single byte to the pcf8574 chip, for use by the * ioctl routines. The protocol for write will depend on the function. * The bitpattern tells which bits are being modified, by setting these * bits in bitpattern to 1, e.g for fanspeed, bitpattern = 0x08, fanspeed * and intr 0x0c, only intr 0x04. */ static int pcf8574_write_chip(struct pcf8574_unit *unitp, uint16_t size, uint8_t bitpattern) { i2c_transfer_t *tp = unitp->i2c_tran; int i; /* * pcf8574_write * * First read the byte, modify only the writable * ports, then write back the modified data. */ tp->i2c_wlen = 0; tp->i2c_rlen = size; tp->i2c_flags = I2C_RD; unitp->i2c_status = nct_i2c_transfer(unitp->pcf8574_hdl, tp); if (unitp->i2c_status != I2C_SUCCESS) { return (I2C_FAILURE); } /* * Our concern is when we have to write only a few bits. * We need to make sure we write the same value to those * bit positions which does not appear in bitpattern. */ /* * 1) Ignore all bits than the one we are writing * 2) Now 0 the bits we intend to modify in the value * read from the chip, preserving all others. * 3) Now turn all non-writable ( read only/reserved ) * bits to 1. The value now should contain: * 1 in all non-writable bits. * 0 in the bis(s) we intend to modify. * no change in the writable bits we don't modify. * 4) Now OR it with the bits we got before, i.e. after * ignoring all bits other than one we are writing. */ for (i = 0; i < size; i++) { tp->i2c_rbuf[i] &= ~(bitpattern); tp->i2c_rbuf[i] |= ~(unitp->writemask); tp->i2c_wbuf[i] = tp->i2c_rbuf[i] | (tp->i2c_wbuf[i] & bitpattern); } tp->i2c_rlen = 0; tp->i2c_wlen = size; tp->i2c_flags = I2C_WR; unitp->i2c_status = nct_i2c_transfer(unitp->pcf8574_hdl, tp); return (unitp->i2c_status); } static int pcf8574_kstat_update(kstat_t *ksp, int rw) { struct pcf8574_unit *unitp; char *kstatp; uint8_t value; int err = DDI_SUCCESS; scsb_fru_status_t dev_presence; unitp = (struct pcf8574_unit *)ksp->ks_private; if (unitp->envctrl_kstat == NULL) { /* May be detaching */ return (err); } CV_LOCK(EINTR) /* * Need to call scsb to find whether device is present. * For I2C devices, the I2C address is used as a FRU ID. */ if (unitp->pcf8574_type == PCF8574_TYPE_CPUVOLTAGE) { dev_presence = FRU_PRESENT; } else { dev_presence = scsb_fru_status((uchar_t)unitp->props.slave_address); } kstatp = (char *)ksp->ks_data; /* * We could have write on the power supply and the fantray * pcf8574 chips. For masking the interrupt on both, or * controlling the fan speed on the fantray. But write * will not be allowed through the kstat interface. For * the present field, call SCSB. */ if (rw == KSTAT_WRITE) { if (unitp->pcf8574_type != PCF8574_TYPE_FANTRAY) { err = EACCES; goto kstat_exit; } value = ((envctrl_fantray_t *)kstatp)->fanspeed; if (value != PCF8574_FAN_SPEED_LOW && value != PCF8574_FAN_SPEED_HIGH) { err = EINVAL; goto kstat_exit; } unitp->i2c_tran->i2c_wbuf[0] = PCF8574_FAN_SPEED(value); if (dev_presence == FRU_PRESENT && pcf8574_write_chip(unitp, 1, PCF8574_FANSPEED_BIT) != I2C_SUCCESS) { err = EFAULT; goto kstat_exit; } } else { /* * First make sure that the FRU exists by checking the SCSB * dev_presence info. If not present, set the change field, * clear the kstat fields and make sure the kstat *_present * field is set to dev_presence from the SCSB driver. */ if (dev_presence == FRU_PRESENT && pcf8574_read_chip(unitp, 1) != I2C_SUCCESS) { /* * Looks like a real IO error. */ err = EIO; CV_UNLOCK return (err); } if (dev_presence == FRU_PRESENT) value = unitp->i2c_tran->i2c_rbuf[0]; else value = 0; switch (unitp->pcf8574_type) { case PCF8574_TYPE_CPUVOLTAGE: { envctrl_cpuvoltage_t *envp = (envctrl_cpuvoltage_t *)unitp->envctrl_kstat; envp->value = value; bcopy((caddr_t)envp, kstatp, sizeof (envctrl_cpuvoltage_t)); break; } case PCF8574_TYPE_PWRSUPP: { envctrl_pwrsupp_t *envp = (envctrl_pwrsupp_t *)unitp->envctrl_kstat; envp->ps_present = dev_presence; envp->ps_ok = PCF8574_PS_FAULT(value); envp->temp_ok = PCF8574_PS_TEMPOK(value); envp->psfan_ok = PCF8574_PS_FANOK(value); envp->on_state = PCF8574_PS_ONOFF(value); envp->ps_ver = PCF8574_PS_TYPE(value); bcopy((caddr_t)envp, kstatp, sizeof (envctrl_pwrsupp_t)); break; } case PCF8574_TYPE_FANTRAY: { envctrl_fantray_t *envp = (envctrl_fantray_t *)unitp->envctrl_kstat; envp->fan_present = dev_presence; envp->fan_ver = PCF8574_FAN_TYPE(value); envp->fan_ok = PCF8574_FAN_FAULT(value); envp->fanspeed = PCF8574_FAN_FANSPD(value); bcopy((caddr_t)unitp->envctrl_kstat, kstatp, sizeof (envctrl_fantray_t)); break; } default: break; } } kstat_exit: CV_UNLOCK return (err); } static void pcf8574_delete_kstat(struct pcf8574_unit *unitp) { /* * Depending on the function, deallocate the correct * kernel allocated memory. */ if (unitp->kstatp != NULL) { kstat_delete(unitp->kstatp); } switch (unitp->pcf8574_type) { case PCF8574_TYPE_CPUVOLTAGE: { if (unitp->envctrl_kstat != NULL) { kmem_free(unitp->envctrl_kstat, sizeof (envctrl_cpuvoltage_t)); } break; } case PCF8574_TYPE_PWRSUPP: { if (unitp->envctrl_kstat != NULL) { kmem_free(unitp->envctrl_kstat, sizeof (envctrl_pwrsupp_t)); } break; } case PCF8574_TYPE_FANTRAY: { if (unitp->envctrl_kstat != NULL) { kmem_free(unitp->envctrl_kstat, sizeof (envctrl_fantray_t)); } break; } default: break; } unitp->envctrl_kstat = NULL; } static int pcf8574_read_props(struct pcf8574_unit *unitp) { dev_info_t *dip = unitp->dip; int retval = 0, prop_len; uint32_t *prop_value = NULL; uint8_t i2c_address; char *function; /* * read the pcf8574_function property. If this property is not * found, return ERROR. Else, make sure it's either powersupply * or fantray. */ if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "pcf8574_function", &function) != DDI_SUCCESS) { dbg_print(CE_WARN, "Couldn't find pcf8574_function property"); return (DDI_FAILURE); } if (strcmp(function, "fantray") == 0) { unitp->pcf8574_type = PCF8574_TYPE_FANTRAY; /* * Will fail the fantray attach if patch - 1. */ if (nct_p10fan_patch) { #ifdef DEBUG cmn_err(CE_WARN, "nct_p10fan_patch set: will not load " "fantary:address %x,%x", unitp->props.i2c_bus, unitp->props.slave_address); #endif ddi_prop_free(function); return (DDI_FAILURE); } } else if (strcmp(function, "powersupply") == 0) { unitp->pcf8574_type = PCF8574_TYPE_PWRSUPP; } else { dbg_print(CE_WARN, "Neither powersupply nor fantray"); ddi_prop_free(function); return (DDI_FAILURE); } ddi_prop_free(function); retval = ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP, "reg", (caddr_t)&prop_value, &prop_len); if (retval == DDI_PROP_SUCCESS) { unitp->props.i2c_bus = (uint16_t)prop_value[0]; unitp->props.slave_address = i2c_address = (uint8_t)prop_value[1]; kmem_free(prop_value, prop_len); if (i2c_address>>4 == 7) unitp->sensor_type = PCF8574A; else if (i2c_address>>4 == 4) unitp->sensor_type = PCF8574; else { unitp->sensor_type = PCF8574A; dbg_print(CE_WARN, "Not a pcf8574/a device"); } } else { unitp->props.i2c_bus = (uint16_t)-1; unitp->props.slave_address = (uint16_t)-1; } /* * Get the Property information that the driver will be using * see typedef struct pcf8574_properties_t; */ unitp->pcf8574_canintr = 0; retval = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "interrupts", -1); if (retval >= 0) { int prop_len, intr_pri = 4; unitp->pcf8574_canintr |= PCF8574_INTR_ON; if (ddi_getproplen(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "interrupt-priorities", &prop_len) == DDI_PROP_NOT_FOUND) { retval = ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP, "interrupt-priorities", (caddr_t)&intr_pri, sizeof (int)); #ifdef DEBUG if (retval != DDI_PROP_SUCCESS) { cmn_err(CE_WARN, "Failed to create interrupt- \ priorities property, retval %d", retval); } #endif /* DEBUG */ } } /* * No channels-in-use property for the fan and powersupplies. */ unitp->props.num_chans_used = 0; if (i2c_address == PCF8574_ADR_CPUVOLTAGE) { if (ddi_getproplen(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "channels-in-use", &prop_len) == DDI_PROP_SUCCESS) { retval = ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "channels-in-use", (uchar_t **)&unitp->props.channels_in_use, &unitp->props.num_chans_used); if (retval != DDI_PROP_SUCCESS) { unitp->props.num_chans_used = 0; } else { unitp->props.num_chans_used /= sizeof (pcf8574_channel_t); } } } return (DDI_PROP_SUCCESS); } /* * callback function to register with the SCSB driver in order to be * informed about changes in device instance presence. */ /*ARGSUSED*/ void pcf8574_callback(void *softstate, scsb_fru_event_t cb_event, scsb_fru_status_t dev_presence) { struct pcf8574_unit *unitp = (struct pcf8574_unit *)softstate; #ifdef DEBUG if (pcf8574_debug & 0x00800001) cmn_err(CE_NOTE, "pcf8574_callback(unitp,%d,%d)", (int)cb_event, (int)dev_presence); #endif /* DEBUG */ switch (unitp->pcf8574_type) { case PCF8574_TYPE_CPUVOLTAGE: { /* * This Unit is not Field Replacable and will not * generate any events at the SCB. */ break; } case PCF8574_TYPE_PWRSUPP: { envctrl_pwrsupp_t *envp; envp = (envctrl_pwrsupp_t *)unitp->envctrl_kstat; if (dev_presence == FRU_NOT_PRESENT) { envp->ps_ok = 0; envp->temp_ok = 0; envp->psfan_ok = 0; envp->on_state = 0; envp->ps_ver = 0; } else if (dev_presence == FRU_PRESENT && envp->ps_present == FRU_NOT_PRESENT) { pcf8574_init_chip(unitp, 0); } envp->ps_present = dev_presence; unitp->poll_event = POLLIN; pollwakeup(&unitp->poll, POLLIN); break; } case PCF8574_TYPE_FANTRAY: { envctrl_fantray_t *envp; envp = (envctrl_fantray_t *)unitp->envctrl_kstat; if (dev_presence == FRU_NOT_PRESENT) { envp->fan_ok = 0; envp->fanspeed = PCF8574_FAN_SPEED60; envp->fan_ver = 0; } else if (dev_presence == FRU_PRESENT && envp->fan_present == FRU_NOT_PRESENT) { pcf8574_init_chip(unitp, 0); } envp->fan_present = dev_presence; unitp->poll_event = POLLIN; pollwakeup(&unitp->poll, POLLIN); break; } } } /* * Initializes the chip after attach or after being inserted. * intron = 0 => disable interrupt. * intron = 1 => read register, enable interrupt if no fault. */ static int pcf8574_init_chip(struct pcf8574_unit *unitp, int intron) { int ret = I2C_SUCCESS; i2c_transfer_t *tp = unitp->i2c_tran; uint8_t value = 0; boolean_t device_faulty = B_FALSE; /* true is faulty */ if (unitp->pcf8574_type != PCF8574_TYPE_PWRSUPP && unitp->pcf8574_type != PCF8574_TYPE_FANTRAY) { return (ret); } switch (unitp->pcf8574_type) { case PCF8574_TYPE_PWRSUPP: tp->i2c_wbuf[0] = PCF8574_PS_DEFAULT; break; case PCF8574_TYPE_FANTRAY: tp->i2c_wbuf[0] = PCF8574_FAN_DEFAULT; break; default: break; } /* * First, read the device. If the device is faulty, it does * not make sense to enable the interrupt, so in this case * keep interrupt maskked inspite of what "intron" says. */ tp->i2c_wlen = 0; tp->i2c_rlen = 1; tp->i2c_flags = I2C_RD; unitp->i2c_status = ret = nct_i2c_transfer(unitp->pcf8574_hdl, tp); if (ret != I2C_SUCCESS) { return (ret); } value = tp->i2c_rbuf[0]; switch (unitp->pcf8574_type) { case PCF8574_TYPE_PWRSUPP: { envctrl_pwrsupp_t *envp = (envctrl_pwrsupp_t *)unitp->envctrl_kstat; envp->ps_ok = PCF8574_PS_FAULT(value); envp->temp_ok = PCF8574_PS_TEMPOK(value); envp->psfan_ok = PCF8574_PS_FANOK(value); envp->on_state = PCF8574_PS_ONOFF(value); envp->ps_ver = PCF8574_PS_TYPE(value); if (envp->ps_ok || envp->temp_ok || envp->psfan_ok || envp->on_state) device_faulty = B_TRUE; break; } case PCF8574_TYPE_FANTRAY: { envctrl_fantray_t *envp = (envctrl_fantray_t *)unitp->envctrl_kstat; envp->fan_ver = PCF8574_FAN_TYPE(value); envp->fan_ok = PCF8574_FAN_FAULT(value); envp->fanspeed = PCF8574_FAN_FANSPD(value); if (!envp->fan_ok) device_faulty = B_TRUE; /* remember, 0 is faulty */ break; } default: break; } /* * Mask interrupt, if intron = 0. */ if (!intron || device_faulty == B_TRUE) { tp->i2c_wbuf[0] |= PCF8574_INTRMASK_BIT; } tp->i2c_wlen = 1; tp->i2c_rlen = 0; tp->i2c_flags = I2C_WR; unitp->i2c_status = nct_i2c_transfer(unitp->pcf8574_hdl, tp); return (unitp->i2c_status); }