11708Sstevel /*
21708Sstevel * CDDL HEADER START
31708Sstevel *
41708Sstevel * The contents of this file are subject to the terms of the
57656SSherry.Moore@Sun.COM * Common Development and Distribution License (the "License").
67656SSherry.Moore@Sun.COM * You may not use this file except in compliance with the License.
71708Sstevel *
81708Sstevel * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
91708Sstevel * or http://www.opensolaris.org/os/licensing.
101708Sstevel * See the License for the specific language governing permissions
111708Sstevel * and limitations under the License.
121708Sstevel *
131708Sstevel * When distributing Covered Code, include this CDDL HEADER in each
141708Sstevel * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
151708Sstevel * If applicable, add the following below this CDDL HEADER, with the
161708Sstevel * fields enclosed by brackets "[]" replaced with your own identifying
171708Sstevel * information: Portions Copyright [yyyy] [name of copyright owner]
181708Sstevel *
191708Sstevel * CDDL HEADER END
201708Sstevel */
21*11311SSurya.Prakki@Sun.COM
221708Sstevel /*
23*11311SSurya.Prakki@Sun.COM * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
241708Sstevel * Use is subject to license terms.
251708Sstevel */
261708Sstevel
271708Sstevel #include <sys/param.h>
281708Sstevel #include <sys/types.h>
291708Sstevel #include <sys/signal.h>
301708Sstevel #include <sys/errno.h>
311708Sstevel #include <sys/file.h>
321708Sstevel #include <sys/termio.h>
331708Sstevel #include <sys/termios.h>
341708Sstevel #include <sys/cmn_err.h>
351708Sstevel #include <sys/stream.h>
361708Sstevel #include <sys/strsun.h>
371708Sstevel #include <sys/stropts.h>
381708Sstevel #include <sys/strtty.h>
391708Sstevel #include <sys/debug.h>
401708Sstevel #include <sys/eucioctl.h>
411708Sstevel #include <sys/cred.h>
421708Sstevel #include <sys/uio.h>
431708Sstevel #include <sys/stat.h>
441708Sstevel #include <sys/kmem.h>
451708Sstevel
461708Sstevel #include <sys/ddi.h>
471708Sstevel #include <sys/sunddi.h>
481708Sstevel #include <sys/obpdefs.h>
491708Sstevel #include <sys/conf.h> /* req. by dev_ops flags MTSAFE etc. */
501708Sstevel #include <sys/modctl.h> /* for modldrv */
511708Sstevel #include <sys/stat.h> /* ddi_create_minor_node S_IFCHR */
521708Sstevel #include <sys/open.h> /* for open params. */
531708Sstevel #include <sys/uio.h> /* for read/write */
541708Sstevel
551708Sstevel #include <sys/i2c/misc/i2c_svc.h>
561708Sstevel #include <sys/mct_topology.h>
571708Sstevel #include <sys/envctrl_gen.h> /* must be before netract_gen.h */
581708Sstevel #include <sys/netract_gen.h>
591708Sstevel #include <sys/pcf8574_nct.h>
601708Sstevel #include <sys/scsb_cbi.h>
611708Sstevel
621708Sstevel #ifdef DEBUG
631708Sstevel #define dbg_print(level, str) cmn_err(level, str);
641708Sstevel static int pcf8574_debug = 0x00000102;
651708Sstevel #else
661708Sstevel #define dbg_print(level, str) {; }
671708Sstevel #endif
681708Sstevel
691708Sstevel #define CV_LOCK(retval) \
701708Sstevel { \
711708Sstevel mutex_enter(&unitp->umutex); \
721708Sstevel while (unitp->pcf8574_flags == PCF8574_BUSY) { \
731708Sstevel if (cv_wait_sig(&unitp->pcf8574_cv, \
741708Sstevel &unitp->umutex) <= 0) { \
751708Sstevel mutex_exit(&unitp->umutex); \
761708Sstevel return (retval); \
771708Sstevel } \
781708Sstevel } \
791708Sstevel unitp->pcf8574_flags = PCF8574_BUSY; \
801708Sstevel mutex_exit(&unitp->umutex); \
811708Sstevel }
821708Sstevel
831708Sstevel #define CV_UNLOCK \
841708Sstevel { \
851708Sstevel mutex_enter(&unitp->umutex); \
861708Sstevel unitp->pcf8574_flags = 0; \
871708Sstevel cv_signal(&unitp->pcf8574_cv); \
881708Sstevel mutex_exit(&unitp->umutex); \
891708Sstevel }
901708Sstevel
911708Sstevel static int nct_p10fan_patch = 0; /* Fan patch for P1.0 */
921708Sstevel static void *pcf8574_soft_statep;
931708Sstevel
941708Sstevel /*
951708Sstevel * cb ops (only need open,close,read,write,ioctl)
961708Sstevel */
971708Sstevel static int pcf8574_open(dev_t *, int, int, cred_t *);
981708Sstevel static int pcf8574_close(dev_t, int, int, cred_t *);
991708Sstevel static int pcf8574_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
1001708Sstevel static int pcf8574_read(dev_t dev, struct uio *uiop, cred_t *cred_p);
1011708Sstevel static int pcf8574_chpoll(dev_t, short, int, short *, struct pollhead **);
1021708Sstevel static uint_t pcf8574_intr(caddr_t arg);
1031708Sstevel static int pcf8574_io(dev_t, struct uio *, int);
1041708Sstevel
1051708Sstevel static struct cb_ops pcf8574_cbops = {
1061708Sstevel pcf8574_open, /* open */
1071708Sstevel pcf8574_close, /* close */
1081708Sstevel nodev, /* strategy */
1091708Sstevel nodev, /* print */
1101708Sstevel nodev, /* dump */
1111708Sstevel pcf8574_read, /* read */
1121708Sstevel nodev, /* write */
1131708Sstevel pcf8574_ioctl, /* ioctl */
1141708Sstevel nodev, /* devmap */
1151708Sstevel nodev, /* mmap */
1161708Sstevel nodev, /* segmap */
1171708Sstevel pcf8574_chpoll, /* poll */
1181708Sstevel ddi_prop_op, /* cb_prop_op */
1191708Sstevel NULL, /* streamtab */
1201708Sstevel D_NEW | D_MP | D_HOTPLUG, /* Driver compatibility flag */
1211708Sstevel CB_REV, /* rev */
1221708Sstevel nodev, /* int (*cb_aread)() */
1231708Sstevel nodev /* int (*cb_awrite)() */
1241708Sstevel };
1251708Sstevel
1261708Sstevel /*
1271708Sstevel * dev ops
1281708Sstevel */
1291708Sstevel static int pcf8574_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
1301708Sstevel static int pcf8574_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
1311708Sstevel
1321708Sstevel /* kstat routines */
1331708Sstevel static int pcf8574_add_kstat(struct pcf8574_unit *, scsb_fru_status_t);
1341708Sstevel static void pcf8574_delete_kstat(struct pcf8574_unit *);
1351708Sstevel static int pcf8574_kstat_update(kstat_t *, int);
1361708Sstevel static int pcf8574_read_chip(struct pcf8574_unit *unitp,
1371708Sstevel uint16_t size);
1381708Sstevel static int pcf8574_write_chip(struct pcf8574_unit *unitp,
1391708Sstevel uint16_t size, uint8_t bitpattern);
1401708Sstevel static int pcf8574_read_props(struct pcf8574_unit *unitp);
1411708Sstevel static int pcf8574_init_chip(struct pcf8574_unit *unitp, int);
1421708Sstevel /*
1431708Sstevel * SCSB callback function
1441708Sstevel */
1451708Sstevel static void pcf8574_callback(void *, scsb_fru_event_t, scsb_fru_status_t);
1461708Sstevel extern int scsb_intr_register(uint_t (*intr_handler)(caddr_t), caddr_t,
1471708Sstevel fru_id_t);
1481708Sstevel extern int scsb_intr_unregister(fru_id_t);
1491708Sstevel
1501708Sstevel extern int nct_i2c_transfer(i2c_client_hdl_t i2c_hdl, i2c_transfer_t *i2c_tran);
1511708Sstevel
1521708Sstevel static struct dev_ops pcf8574_ops = {
1531708Sstevel DEVO_REV,
1541708Sstevel 0,
1551708Sstevel ddi_getinfo_1to1,
1561708Sstevel nulldev,
1571708Sstevel nulldev,
1581708Sstevel pcf8574_attach,
1591708Sstevel pcf8574_detach,
1601708Sstevel nodev,
1611708Sstevel &pcf8574_cbops,
1627656SSherry.Moore@Sun.COM NULL, /* bus_ops */
1637656SSherry.Moore@Sun.COM NULL, /* power */
1647656SSherry.Moore@Sun.COM ddi_quiesce_not_supported, /* devo_quiesce */
1651708Sstevel };
1661708Sstevel
1671708Sstevel extern struct mod_ops mod_driverops;
1681708Sstevel
1691708Sstevel static struct modldrv pcf8574_modldrv = {
1701708Sstevel &mod_driverops, /* type of module - driver */
1717656SSherry.Moore@Sun.COM "Netract pcf8574 (gpio)",
1721708Sstevel &pcf8574_ops,
1731708Sstevel };
1741708Sstevel
1751708Sstevel static struct modlinkage pcf8574_modlinkage = {
1761708Sstevel MODREV_1,
1771708Sstevel &pcf8574_modldrv,
1781708Sstevel 0
1791708Sstevel };
1801708Sstevel
1811708Sstevel /* char _depends_on[] = "misc/i2c_svc drv/scsb"; */
1821708Sstevel
1831708Sstevel int
_init(void)1841708Sstevel _init(void)
1851708Sstevel {
1861708Sstevel register int error;
1871708Sstevel
1881708Sstevel error = mod_install(&pcf8574_modlinkage);
1891708Sstevel if (!error) {
1901708Sstevel (void) ddi_soft_state_init(&pcf8574_soft_statep,
1917656SSherry.Moore@Sun.COM sizeof (struct pcf8574_unit), PCF8574_MAX_DEVS);
1921708Sstevel }
1931708Sstevel
1941708Sstevel return (error);
1951708Sstevel }
1961708Sstevel
1971708Sstevel int
_fini(void)1981708Sstevel _fini(void)
1991708Sstevel {
2001708Sstevel register int error;
2011708Sstevel
2021708Sstevel error = mod_remove(&pcf8574_modlinkage);
2031708Sstevel if (!error)
2041708Sstevel ddi_soft_state_fini(&pcf8574_soft_statep);
2051708Sstevel
2061708Sstevel return (error);
2071708Sstevel }
2081708Sstevel
2091708Sstevel int
_info(struct modinfo * modinfop)2101708Sstevel _info(struct modinfo *modinfop)
2111708Sstevel {
2121708Sstevel return (mod_info(&pcf8574_modlinkage, modinfop));
2131708Sstevel }
2141708Sstevel
2151708Sstevel /*ARGSUSED*/
2161708Sstevel static int
pcf8574_open(dev_t * devp,int flags,int otyp,cred_t * credp)2171708Sstevel pcf8574_open(dev_t *devp, int flags, int otyp, cred_t *credp)
2181708Sstevel {
2191708Sstevel struct pcf8574_unit *unitp;
2201708Sstevel register int instance;
2211708Sstevel int err = DDI_SUCCESS;
2221708Sstevel
2231708Sstevel instance = getminor(*devp);
2241708Sstevel if (instance < 0) {
2251708Sstevel return (ENXIO);
2261708Sstevel }
2271708Sstevel
2281708Sstevel unitp = (struct pcf8574_unit *)
2297656SSherry.Moore@Sun.COM ddi_get_soft_state(pcf8574_soft_statep, instance);
2301708Sstevel
2311708Sstevel if (unitp == NULL) {
2321708Sstevel return (ENXIO);
2331708Sstevel }
2341708Sstevel
2351708Sstevel if (otyp != OTYP_CHR) {
2361708Sstevel return (EINVAL);
2371708Sstevel }
2381708Sstevel
2391708Sstevel mutex_enter(&unitp->umutex);
2401708Sstevel
2411708Sstevel if (flags & FEXCL) {
2421708Sstevel if (unitp->pcf8574_oflag != 0) {
2431708Sstevel err = EBUSY;
2441708Sstevel } else {
2451708Sstevel unitp->pcf8574_oflag = FEXCL;
2461708Sstevel }
2471708Sstevel } else {
2481708Sstevel if (unitp->pcf8574_oflag == FEXCL) {
2491708Sstevel err = EBUSY;
2501708Sstevel } else {
2511708Sstevel unitp->pcf8574_oflag = FOPEN;
2521708Sstevel }
2531708Sstevel }
2541708Sstevel
2551708Sstevel mutex_exit(&unitp->umutex);
2561708Sstevel
2571708Sstevel return (err);
2581708Sstevel }
2591708Sstevel
2601708Sstevel /*ARGSUSED*/
2611708Sstevel static int
pcf8574_close(dev_t dev,int flags,int otyp,cred_t * credp)2621708Sstevel pcf8574_close(dev_t dev, int flags, int otyp, cred_t *credp)
2631708Sstevel {
2641708Sstevel struct pcf8574_unit *unitp;
2651708Sstevel register int instance;
2661708Sstevel
2671708Sstevel #ifdef lint
2681708Sstevel flags = flags;
2691708Sstevel otyp = otyp;
2701708Sstevel #endif
2711708Sstevel
2721708Sstevel instance = getminor(dev);
2731708Sstevel
2741708Sstevel if (instance < 0) {
2751708Sstevel return (ENXIO);
2761708Sstevel }
2771708Sstevel
2781708Sstevel unitp = (struct pcf8574_unit *)
2797656SSherry.Moore@Sun.COM ddi_get_soft_state(pcf8574_soft_statep, instance);
2801708Sstevel
2811708Sstevel if (unitp == NULL) {
2821708Sstevel return (ENXIO);
2831708Sstevel }
2841708Sstevel
2851708Sstevel mutex_enter(&unitp->umutex);
2861708Sstevel
2871708Sstevel unitp->pcf8574_oflag = 0;
2881708Sstevel
2891708Sstevel mutex_exit(&unitp->umutex);
2901708Sstevel
2911708Sstevel return (DDI_SUCCESS);
2921708Sstevel }
2931708Sstevel
2941708Sstevel
2951708Sstevel /*ARGSUSED*/
2961708Sstevel static int
pcf8574_read(dev_t dev,struct uio * uiop,cred_t * cred_p)2971708Sstevel pcf8574_read(dev_t dev, struct uio *uiop, cred_t *cred_p)
2981708Sstevel {
2991708Sstevel return (pcf8574_io(dev, uiop, B_READ));
3001708Sstevel }
3011708Sstevel
3021708Sstevel static int
pcf8574_io(dev_t dev,struct uio * uiop,int rw)3031708Sstevel pcf8574_io(dev_t dev, struct uio *uiop, int rw)
3041708Sstevel {
3051708Sstevel struct pcf8574_unit *unitp;
3061708Sstevel register int instance;
3071708Sstevel uint16_t bytes_to_rw;
3081708Sstevel int err = DDI_SUCCESS;
3091708Sstevel
3101708Sstevel err = 0;
3111708Sstevel instance = getminor(dev);
3121708Sstevel
3131708Sstevel if (instance < 0) {
3141708Sstevel return (ENXIO);
3151708Sstevel }
3161708Sstevel
3171708Sstevel unitp = (struct pcf8574_unit *)
3187656SSherry.Moore@Sun.COM ddi_get_soft_state(pcf8574_soft_statep, instance);
3191708Sstevel if (unitp == NULL) {
3201708Sstevel return (ENXIO);
3211708Sstevel }
3221708Sstevel if ((bytes_to_rw = uiop->uio_resid) > PCF8574_TRAN_SIZE) {
3231708Sstevel return (EINVAL);
3241708Sstevel }
3251708Sstevel
3261708Sstevel CV_LOCK(EINTR)
3271708Sstevel
3281708Sstevel if (rw == B_WRITE) {
3291708Sstevel err = uiomove(unitp->i2c_tran->i2c_wbuf,
3307656SSherry.Moore@Sun.COM bytes_to_rw, UIO_WRITE, uiop);
3311708Sstevel
3321708Sstevel if (!err) {
3331708Sstevel err = pcf8574_write_chip(unitp, bytes_to_rw,
3347656SSherry.Moore@Sun.COM unitp->writemask);
3351708Sstevel }
3361708Sstevel
3371708Sstevel } else {
3381708Sstevel err = pcf8574_read_chip(unitp, bytes_to_rw);
3391708Sstevel if (!err) {
3401708Sstevel err = uiomove(unitp->i2c_tran->i2c_rbuf,
3417656SSherry.Moore@Sun.COM bytes_to_rw, UIO_READ, uiop);
3421708Sstevel }
3431708Sstevel }
3441708Sstevel
3451708Sstevel CV_UNLOCK
3461708Sstevel if (err)
3471708Sstevel err = EIO;
3481708Sstevel
3491708Sstevel return (err);
3501708Sstevel }
3511708Sstevel
3521708Sstevel static int
pcf8574_do_resume(dev_info_t * dip)3531708Sstevel pcf8574_do_resume(dev_info_t *dip)
3541708Sstevel {
3551708Sstevel int instance = ddi_get_instance(dip);
3561708Sstevel struct pcf8574_unit *unitp =
3577656SSherry.Moore@Sun.COM ddi_get_soft_state(pcf8574_soft_statep, instance);
3581708Sstevel
3591708Sstevel if (unitp == NULL) {
3601708Sstevel return (ENXIO);
3611708Sstevel }
3621708Sstevel
3631708Sstevel CV_UNLOCK
3641708Sstevel
3651708Sstevel return (DDI_SUCCESS);
3661708Sstevel }
3671708Sstevel
3681708Sstevel static int
pcf8574_do_detach(dev_info_t * dip)3691708Sstevel pcf8574_do_detach(dev_info_t *dip)
3701708Sstevel {
3711708Sstevel struct pcf8574_unit *unitp;
3721708Sstevel int instance;
3731708Sstevel uint_t attach_flag;
3741708Sstevel
3751708Sstevel instance = ddi_get_instance(dip);
3761708Sstevel unitp = ddi_get_soft_state(pcf8574_soft_statep, instance);
3771708Sstevel
3781708Sstevel attach_flag = unitp->attach_flag;
3791708Sstevel
3801708Sstevel if (attach_flag & PCF8574_INTR_ADDED) {
381*11311SSurya.Prakki@Sun.COM (void) scsb_intr_unregister(
382*11311SSurya.Prakki@Sun.COM (fru_id_t)unitp->props.slave_address);
3831708Sstevel }
3841708Sstevel
3851708Sstevel if (attach_flag & PCF8574_KSTAT_INIT) {
3861708Sstevel pcf8574_delete_kstat(unitp);
3871708Sstevel }
3881708Sstevel
3891708Sstevel if (attach_flag & PCF8574_LOCK_INIT) {
3901708Sstevel mutex_destroy(&unitp->umutex);
3911708Sstevel cv_destroy(&unitp->pcf8574_cv);
3921708Sstevel }
3931708Sstevel
3941708Sstevel scsb_fru_unregister((void *)unitp,
3957656SSherry.Moore@Sun.COM (fru_id_t)unitp->props.slave_address);
3961708Sstevel
3971708Sstevel if (attach_flag & PCF8574_ALLOC_TRANSFER) {
3981708Sstevel /*
3991708Sstevel * restore the lengths to allocated lengths
4001708Sstevel * before freeing.
4011708Sstevel */
4021708Sstevel unitp->i2c_tran->i2c_wlen = MAX_WLEN;
4031708Sstevel unitp->i2c_tran->i2c_rlen = MAX_RLEN;
4041708Sstevel i2c_transfer_free(unitp->pcf8574_hdl, unitp->i2c_tran);
4051708Sstevel }
4061708Sstevel
4071708Sstevel if (attach_flag & PCF8574_REGISTER_CLIENT) {
4081708Sstevel i2c_client_unregister(unitp->pcf8574_hdl);
4091708Sstevel }
4101708Sstevel
4111708Sstevel if (attach_flag & PCF8574_MINORS_CREATED) {
4121708Sstevel ddi_remove_minor_node(dip, NULL);
4131708Sstevel }
4141708Sstevel
4151708Sstevel if (attach_flag & PCF8574_PROPS_READ) {
4161708Sstevel if (unitp->pcf8574_type == PCF8574_ADR_CPUVOLTAGE &&
4177656SSherry.Moore@Sun.COM unitp->props.num_chans_used != 0) {
4181708Sstevel ddi_prop_free(unitp->props.channels_in_use);
4191708Sstevel } else {
420*11311SSurya.Prakki@Sun.COM (void) ddi_prop_remove(DDI_DEV_T_NONE, dip,
4217656SSherry.Moore@Sun.COM "interrupt-priorities");
4221708Sstevel }
4231708Sstevel }
4241708Sstevel
4251708Sstevel if (attach_flag & PCF8574_SOFT_STATE_ALLOC) {
4261708Sstevel ddi_soft_state_free(pcf8574_soft_statep, instance);
4271708Sstevel }
4281708Sstevel
4291708Sstevel return (DDI_SUCCESS);
4301708Sstevel }
4311708Sstevel
4321708Sstevel /*
4331708Sstevel * NOTE****
4341708Sstevel * The OBP will create device tree node for all I2C devices which
4351708Sstevel * may be present in a system. This means, even if the device is
4361708Sstevel * not physically present, the device tree node exists. We also
4371708Sstevel * will succeed the attach routine, since currently there is no
4381708Sstevel * hotplug support in the I2C bus, and the FRUs need to be hot
4391708Sstevel * swappable. Only during an I2C transaction we figure out whether
4401708Sstevel * the particular I2C device is actually present in the system
4411708Sstevel * by looking at the system controller board register. The fantray
4421708Sstevel * and power-supply devices may be swapped any time after system
4431708Sstevel * reboot, and the way we can make sure that the device is attached
4441708Sstevel * to the driver, is by always keeping the driver loaded, and report
4451708Sstevel * an error during the actual transaction.
4461708Sstevel */
4471708Sstevel static int
pcf8574_do_attach(dev_info_t * dip)4481708Sstevel pcf8574_do_attach(dev_info_t *dip)
4491708Sstevel {
4501708Sstevel register struct pcf8574_unit *unitp;
4511708Sstevel int instance;
4521708Sstevel char name[MAXNAMELEN];
4531708Sstevel int i;
4541708Sstevel pcf8574_channel_t *chp;
4551708Sstevel scsb_fru_status_t dev_presence;
4561708Sstevel
4571708Sstevel instance = ddi_get_instance(dip);
4581708Sstevel #ifdef DEBUG
4591708Sstevel if (pcf8574_debug & 0x04)
4601708Sstevel cmn_err(CE_NOTE, "pcf8574_attach: instance=%d\n",
4617656SSherry.Moore@Sun.COM instance);
4621708Sstevel #endif /* DEBUG */
4631708Sstevel
4641708Sstevel if (ddi_soft_state_zalloc(pcf8574_soft_statep, instance) !=
4657656SSherry.Moore@Sun.COM DDI_SUCCESS) {
4661708Sstevel return (DDI_FAILURE);
4671708Sstevel }
4681708Sstevel unitp = ddi_get_soft_state(pcf8574_soft_statep, instance);
4691708Sstevel
4701708Sstevel if (unitp == NULL) {
4711708Sstevel ddi_soft_state_free(pcf8574_soft_statep, instance);
4721708Sstevel return (DDI_FAILURE);
4731708Sstevel }
4741708Sstevel
4751708Sstevel unitp->dip = dip;
4761708Sstevel
4771708Sstevel unitp->attach_flag = PCF8574_SOFT_STATE_ALLOC;
4781708Sstevel
4791708Sstevel if (pcf8574_read_props(unitp) != DDI_PROP_SUCCESS) {
4801708Sstevel ddi_soft_state_free(pcf8574_soft_statep, instance);
4811708Sstevel return (DDI_FAILURE);
4821708Sstevel }
4831708Sstevel
4841708Sstevel unitp->attach_flag |= PCF8574_PROPS_READ;
4851708Sstevel
4861708Sstevel /*
4871708Sstevel * Set the current operating mode to NORMAL_MODE.
4881708Sstevel */
4891708Sstevel unitp->current_mode = ENVCTRL_NORMAL_MODE;
4901708Sstevel
491*11311SSurya.Prakki@Sun.COM (void) snprintf(unitp->pcf8574_name, PCF8574_NAMELEN,
4927656SSherry.Moore@Sun.COM "%s%d", ddi_driver_name(dip), instance);
4931708Sstevel
4941708Sstevel if (unitp->pcf8574_type == PCF8574_TYPE_PWRSUPP) {
4951708Sstevel (void) sprintf(name, "pwrsuppply");
4961708Sstevel if (ddi_create_minor_node(dip, name, S_IFCHR, instance,
4977656SSherry.Moore@Sun.COM PCF8574_NODE_TYPE, NULL) == DDI_FAILURE) {
4981708Sstevel ddi_remove_minor_node(dip, NULL);
499*11311SSurya.Prakki@Sun.COM (void) pcf8574_do_detach(dip);
5001708Sstevel
5011708Sstevel return (DDI_FAILURE);
5021708Sstevel }
5031708Sstevel }
5041708Sstevel else
5051708Sstevel if (unitp->pcf8574_type == PCF8574_TYPE_FANTRAY) {
5061708Sstevel (void) sprintf(name, "fantray");
5071708Sstevel if (ddi_create_minor_node(dip, name, S_IFCHR, instance,
5087656SSherry.Moore@Sun.COM PCF8574_NODE_TYPE, NULL) == DDI_FAILURE) {
5091708Sstevel ddi_remove_minor_node(dip, NULL);
510*11311SSurya.Prakki@Sun.COM (void) pcf8574_do_detach(dip);
5111708Sstevel
5121708Sstevel return (DDI_FAILURE);
5131708Sstevel }
5141708Sstevel }
5151708Sstevel else
5161708Sstevel if (unitp->pcf8574_type == PCF8574_TYPE_CPUVOLTAGE) {
5171708Sstevel (void) sprintf(name, "cpuvoltage");
5181708Sstevel if (ddi_create_minor_node(dip, name, S_IFCHR, instance,
5197656SSherry.Moore@Sun.COM PCF8574_NODE_TYPE, NULL) == DDI_FAILURE) {
5201708Sstevel ddi_remove_minor_node(dip, NULL);
521*11311SSurya.Prakki@Sun.COM (void) pcf8574_do_detach(dip);
5221708Sstevel
5231708Sstevel return (DDI_FAILURE);
5241708Sstevel }
5251708Sstevel } else {
5261708Sstevel return (DDI_FAILURE);
5271708Sstevel }
5281708Sstevel
5291708Sstevel unitp->attach_flag |= PCF8574_MINORS_CREATED;
5301708Sstevel
5311708Sstevel /*
5321708Sstevel * Now we need read/write masks since all the 8574 bits can be either
5331708Sstevel * read/written, but some ports are intended to be RD/WR only, or RW
5341708Sstevel * If no channels-in-use propoerty, set default values.
5351708Sstevel */
5361708Sstevel if (unitp->pcf8574_type == PCF8574_TYPE_FANTRAY) {
5371708Sstevel unitp->readmask = PCF8574_FAN_READMASK;
5381708Sstevel unitp->writemask = PCF8574_FAN_WRITEMASK;
5391708Sstevel }
5401708Sstevel if (unitp->pcf8574_type == PCF8574_TYPE_PWRSUPP) {
5411708Sstevel unitp->readmask = PCF8574_PS_READMASK;
5421708Sstevel unitp->writemask = PCF8574_PS_WRITEMASK;
5431708Sstevel }
5441708Sstevel
5451708Sstevel for (i = unitp->props.num_chans_used,
5467656SSherry.Moore@Sun.COM chp = unitp->props.channels_in_use; i; --i, ++chp) {
5471708Sstevel unitp->readmask |= (uint8_t)(
5487656SSherry.Moore@Sun.COM (chp->io_dir == I2C_PROP_IODIR_IN ||
5497656SSherry.Moore@Sun.COM chp->io_dir == I2C_PROP_IODIR_INOUT) << chp->port);
5501708Sstevel unitp->writemask |= (uint8_t)(
5517656SSherry.Moore@Sun.COM (chp->io_dir == I2C_PROP_IODIR_OUT ||
5527656SSherry.Moore@Sun.COM chp->io_dir == I2C_PROP_IODIR_INOUT) << chp->port);
5531708Sstevel }
5541708Sstevel
5551708Sstevel #ifdef DEBUG
5561708Sstevel cmn_err(CE_NOTE, "pcf8574_do_attach: readmask = 0x%x \
5571708Sstevel writemask = 0x%x\n", unitp->readmask, unitp->writemask);
5581708Sstevel #endif /* DEBUG */
5591708Sstevel
5601708Sstevel if (i2c_client_register(dip, &unitp->pcf8574_hdl)
5617656SSherry.Moore@Sun.COM != I2C_SUCCESS) {
562*11311SSurya.Prakki@Sun.COM (void) pcf8574_do_detach(dip);
5631708Sstevel return (DDI_FAILURE);
5641708Sstevel }
5651708Sstevel unitp->attach_flag |= PCF8574_REGISTER_CLIENT;
5661708Sstevel
5671708Sstevel /*
5681708Sstevel * Allocate the I2C_transfer structure. The same structure
5691708Sstevel * is used throughout the driver.
5701708Sstevel */
5711708Sstevel if (i2c_transfer_alloc(unitp->pcf8574_hdl, &unitp->i2c_tran,
572*11311SSurya.Prakki@Sun.COM MAX_WLEN, MAX_RLEN, KM_SLEEP) != I2C_SUCCESS) {
573*11311SSurya.Prakki@Sun.COM (void) pcf8574_do_detach(dip);
5741708Sstevel return (DDI_FAILURE);
5751708Sstevel }
5761708Sstevel unitp->attach_flag |= PCF8574_ALLOC_TRANSFER;
5771708Sstevel
5781708Sstevel /*
5791708Sstevel * To begin with we set the mode to I2C_RD.
5801708Sstevel */
5811708Sstevel unitp->i2c_tran->i2c_flags = I2C_RD;
5821708Sstevel unitp->i2c_tran->i2c_version = I2C_XFER_REV;
5831708Sstevel
5841708Sstevel /*
5851708Sstevel * Set the busy flag and open flag to 0.
5861708Sstevel */
5871708Sstevel unitp->pcf8574_flags = 0;
5881708Sstevel unitp->pcf8574_oflag = 0;
5891708Sstevel
5901708Sstevel mutex_init(&unitp->umutex, NULL, MUTEX_DRIVER, NULL);
5911708Sstevel cv_init(&unitp->pcf8574_cv, NULL, CV_DRIVER, NULL);
5921708Sstevel
5931708Sstevel unitp->attach_flag |= PCF8574_LOCK_INIT;
5941708Sstevel
5951708Sstevel /*
5961708Sstevel * Register out callback function with the SCSB driver, and save
5971708Sstevel * the returned value to check that the device instance exists.
5981708Sstevel */
5991708Sstevel dev_presence = scsb_fru_register(pcf8574_callback, (void *)unitp,
6007656SSherry.Moore@Sun.COM (fru_id_t)unitp->props.slave_address);
6011708Sstevel if (dev_presence == FRU_NOT_AVAILABLE) {
6021708Sstevel scsb_fru_unregister((void *)unitp,
6037656SSherry.Moore@Sun.COM (fru_id_t)unitp->props.slave_address);
6041708Sstevel }
6051708Sstevel
6061708Sstevel /*
6071708Sstevel * Add the kstats. First we need to get the property values
6081708Sstevel * depending on the device type. For example, for the fan
6091708Sstevel * tray there will be a different set of properties, and there
6101708Sstevel * will be another for the powersupplies, and another one for
6111708Sstevel * the CPU voltage monitor. Initialize the kstat structures with
6121708Sstevel * these values.
6131708Sstevel */
6141708Sstevel
6151708Sstevel if (pcf8574_add_kstat(unitp, dev_presence) != DDI_SUCCESS) {
616*11311SSurya.Prakki@Sun.COM (void) pcf8574_do_detach(dip);
6171708Sstevel return (DDI_FAILURE);
6181708Sstevel }
6191708Sstevel
6201708Sstevel unitp->attach_flag |= PCF8574_KSTAT_INIT;
6211708Sstevel
6221708Sstevel /*
6231708Sstevel * Due to observed behavior on Solaris 8, the handler must be
6241708Sstevel * registered before any interrupts are enabled,
6251708Sstevel * in spite of what the ddi_get_iblock_cookie() manual says.
6261708Sstevel * As per the HW/SW spec, by default interrupts are disabled.
6271708Sstevel */
6281708Sstevel
6291708Sstevel if (dev_presence == FRU_PRESENT) { /* program the chip */
630*11311SSurya.Prakki@Sun.COM (void) pcf8574_init_chip(unitp, 0); /* Disable intr first */
6311708Sstevel }
6321708Sstevel
6331708Sstevel if (unitp->pcf8574_canintr == PCF8574_INTR_ON) {
6341708Sstevel #ifdef DEBUG
6351708Sstevel if (pcf8574_debug & 0x0004)
6361708Sstevel cmn_err(CE_NOTE, "registering pcf9574 interrupt "
6377656SSherry.Moore@Sun.COM "handler");
6381708Sstevel #endif /* DEBUG */
6391708Sstevel if (scsb_intr_register(pcf8574_intr, (void *)unitp,
6407656SSherry.Moore@Sun.COM (fru_id_t)unitp->props.slave_address) == DDI_SUCCESS) {
6411708Sstevel unitp->pcf8574_canintr |= PCF8574_INTR_ENABLED;
6421708Sstevel unitp->attach_flag |= PCF8574_INTR_ADDED;
6431708Sstevel } else {
644*11311SSurya.Prakki@Sun.COM (void) pcf8574_do_detach(dip);
6451708Sstevel return (DDI_FAILURE);
6461708Sstevel }
6471708Sstevel }
6481708Sstevel
6491708Sstevel ddi_report_dev(dip);
6501708Sstevel
6511708Sstevel return (DDI_SUCCESS);
6521708Sstevel }
6531708Sstevel
6541708Sstevel static int
pcf8574_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)6551708Sstevel pcf8574_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
6561708Sstevel {
6571708Sstevel switch (cmd) {
6581708Sstevel case DDI_ATTACH:
6591708Sstevel return (pcf8574_do_attach(dip));
6601708Sstevel case DDI_RESUME:
6611708Sstevel return (pcf8574_do_resume(dip));
6621708Sstevel default:
6631708Sstevel return (DDI_FAILURE);
6641708Sstevel }
6651708Sstevel }
6661708Sstevel
6671708Sstevel static int
pcf8574_do_suspend(dev_info_t * dip)6681708Sstevel pcf8574_do_suspend(dev_info_t *dip)
6691708Sstevel {
6701708Sstevel int instance = ddi_get_instance(dip);
6711708Sstevel struct pcf8574_unit *unitp =
6727656SSherry.Moore@Sun.COM ddi_get_soft_state(pcf8574_soft_statep, instance);
6731708Sstevel
6741708Sstevel if (unitp == NULL) {
6751708Sstevel return (ENXIO);
6761708Sstevel }
6771708Sstevel
6781708Sstevel /*
6791708Sstevel * Set the busy flag so that future transactions block
6801708Sstevel * until resume.
6811708Sstevel */
6821708Sstevel CV_LOCK(ENXIO)
6831708Sstevel
6841708Sstevel return (DDI_SUCCESS);
6851708Sstevel }
6861708Sstevel
6871708Sstevel static int
pcf8574_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)6881708Sstevel pcf8574_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
6891708Sstevel {
6901708Sstevel switch (cmd) {
6911708Sstevel case DDI_DETACH:
6921708Sstevel return (pcf8574_do_detach(dip));
6931708Sstevel case DDI_SUSPEND:
6941708Sstevel return (pcf8574_do_suspend(dip));
6951708Sstevel default:
6961708Sstevel return (DDI_FAILURE);
6971708Sstevel }
6981708Sstevel }
6991708Sstevel
7001708Sstevel static int
pcf8574_chpoll(dev_t dev,short events,int anyyet,short * reventsp,struct pollhead ** phpp)7011708Sstevel pcf8574_chpoll(dev_t dev, short events, int anyyet, short *reventsp,
7021708Sstevel struct pollhead **phpp)
7031708Sstevel {
7041708Sstevel struct pcf8574_unit *unitp;
7051708Sstevel int instance;
7061708Sstevel
7071708Sstevel instance = getminor(dev);
7081708Sstevel if ((unitp = (struct pcf8574_unit *)ddi_get_soft_state(
7097656SSherry.Moore@Sun.COM pcf8574_soft_statep, instance)) == NULL) {
7101708Sstevel return (ENXIO);
7111708Sstevel }
7121708Sstevel *reventsp = 0;
7131708Sstevel mutex_enter(&unitp->umutex);
7141708Sstevel if (unitp->poll_event) {
7151708Sstevel *reventsp = unitp->poll_event;
7161708Sstevel unitp->poll_event = 0;
7171708Sstevel } else if ((events & POLLIN) && !anyyet)
7181708Sstevel *phpp = &unitp->poll;
7191708Sstevel mutex_exit(&unitp->umutex);
7201708Sstevel return (0);
7211708Sstevel }
7221708Sstevel
7231708Sstevel /*
7241708Sstevel * In normal scenarios, this function should never get called.
7251708Sstevel * But, we will still come back and call this function if scsb
7261708Sstevel * interrupt sources does not indicate an scsb interrupt. We may
7271708Sstevel * come to this situation when SunVTS env4test is independently
7281708Sstevel * changing the device registers.
7291708Sstevel */
7301708Sstevel uint_t
pcf8574_intr(caddr_t arg)7311708Sstevel pcf8574_intr(caddr_t arg)
7321708Sstevel {
7331708Sstevel int ic;
7341708Sstevel uint8_t value;
7351708Sstevel struct pcf8574_unit *unitp = (struct pcf8574_unit *)(void *)arg;
7361708Sstevel scsb_fru_status_t dev_presence;
7371708Sstevel i2c_transfer_t *tp = unitp->i2c_tran;
7381708Sstevel
7391708Sstevel ic = DDI_INTR_CLAIMED;
7401708Sstevel #ifdef DEBUG
7411708Sstevel cmn_err(CE_NOTE, " In the interrupt service routine, %x",
7427656SSherry.Moore@Sun.COM unitp->props.slave_address);
7431708Sstevel #endif
7441708Sstevel
7451708Sstevel /*
7461708Sstevel * Initiate an I2C transaction to find out
7471708Sstevel * whether this is the device which interrupted.
7481708Sstevel */
7491708Sstevel mutex_enter(&unitp->umutex);
7501708Sstevel while (unitp->pcf8574_flags == PCF8574_BUSY) {
7511708Sstevel if (cv_wait_sig(&unitp->pcf8574_cv, &unitp->umutex) <= 0) {
7521708Sstevel mutex_exit(&unitp->umutex);
7531708Sstevel return (DDI_INTR_UNCLAIMED);
7541708Sstevel }
7551708Sstevel }
7561708Sstevel
7571708Sstevel unitp->pcf8574_flags = PCF8574_BUSY;
7581708Sstevel mutex_exit(&unitp->umutex);
7591708Sstevel
7601708Sstevel switch (unitp->pcf8574_type) {
7611708Sstevel case PCF8574_TYPE_CPUVOLTAGE: {
7621708Sstevel dev_presence = FRU_PRESENT;
7631708Sstevel break;
7641708Sstevel }
7651708Sstevel case PCF8574_TYPE_PWRSUPP: {
7661708Sstevel envctrl_pwrsupp_t *envp =
7677656SSherry.Moore@Sun.COM (envctrl_pwrsupp_t *)unitp->envctrl_kstat;
7681708Sstevel dev_presence = envp->ps_present;
7691708Sstevel break;
7701708Sstevel }
7711708Sstevel case PCF8574_TYPE_FANTRAY: {
7721708Sstevel envctrl_fantray_t *envp =
7737656SSherry.Moore@Sun.COM (envctrl_fantray_t *)unitp->envctrl_kstat;
7741708Sstevel dev_presence = envp->fan_present;
7751708Sstevel break;
7761708Sstevel }
7771708Sstevel }
7781708Sstevel if (dev_presence != FRU_PRESENT) {
7791708Sstevel ic = DDI_INTR_UNCLAIMED;
7801708Sstevel goto intr_exit;
7811708Sstevel }
7821708Sstevel if (pcf8574_read_chip(unitp, 1) != I2C_SUCCESS) {
7831708Sstevel ic = DDI_INTR_UNCLAIMED;
7841708Sstevel goto intr_exit;
7851708Sstevel }
7861708Sstevel value = unitp->i2c_tran->i2c_rbuf[0];
7871708Sstevel /*
7881708Sstevel * If interrupt is already masked, return
7891708Sstevel */
7901708Sstevel if (value & PCF8574_INTRMASK_BIT) {
7911708Sstevel ic = DDI_INTR_UNCLAIMED;
7921708Sstevel goto intr_exit;
7931708Sstevel }
7941708Sstevel
7951708Sstevel /*
7961708Sstevel * In case a fault bit is set, claim the interrupt.
7971708Sstevel */
7981708Sstevel switch (unitp->pcf8574_type) {
7991708Sstevel case PCF8574_TYPE_PWRSUPP:
8001708Sstevel {
8011708Sstevel envctrl_pwrsupp_t *envp =
8027656SSherry.Moore@Sun.COM (envctrl_pwrsupp_t *)unitp->envctrl_kstat;
8031708Sstevel
8041708Sstevel if (PCF8574_PS_FAULT(value) ||
8057656SSherry.Moore@Sun.COM PCF8574_PS_TEMPOK(value) ||
8067656SSherry.Moore@Sun.COM PCF8574_PS_ONOFF(value) ||
8077656SSherry.Moore@Sun.COM PCF8574_PS_FANOK(value)) {
8081708Sstevel
8091708Sstevel envp->ps_ok = PCF8574_PS_FAULT(value);
8101708Sstevel envp->temp_ok = PCF8574_PS_TEMPOK(value);
8111708Sstevel envp->psfan_ok = PCF8574_PS_FANOK(value);
8121708Sstevel envp->on_state = PCF8574_PS_ONOFF(value);
8131708Sstevel envp->ps_ver = PCF8574_PS_TYPE(value);
8141708Sstevel
8151708Sstevel tp->i2c_wbuf[0] =
8167656SSherry.Moore@Sun.COM PCF8574_PS_DEFAULT | PCF8574_PS_MASKINTR;
8171708Sstevel tp->i2c_wlen = 1;
8181708Sstevel tp->i2c_rlen = 0;
8191708Sstevel tp->i2c_flags = I2C_WR;
8201708Sstevel
8211708Sstevel unitp->i2c_status =
8227656SSherry.Moore@Sun.COM nct_i2c_transfer(unitp->pcf8574_hdl, tp);
8231708Sstevel
8241708Sstevel unitp->poll_event = POLLIN;
8251708Sstevel pollwakeup(&unitp->poll, POLLIN);
8261708Sstevel } else {
8271708Sstevel ic = DDI_INTR_UNCLAIMED;
8281708Sstevel }
8291708Sstevel }
8301708Sstevel break;
8311708Sstevel
8321708Sstevel case PCF8574_TYPE_FANTRAY:
8331708Sstevel {
8341708Sstevel envctrl_fantray_t *envp =
8357656SSherry.Moore@Sun.COM (envctrl_fantray_t *)unitp->envctrl_kstat;
8361708Sstevel
8371708Sstevel if (!PCF8574_FAN_FAULT(value)) {
8381708Sstevel
8391708Sstevel envp->fan_ver = PCF8574_FAN_TYPE(value);
8401708Sstevel envp->fan_ok = PCF8574_FAN_FAULT(value);
8411708Sstevel envp->fanspeed = PCF8574_FAN_FANSPD(value);
8421708Sstevel
8431708Sstevel tp->i2c_wbuf[0] =
8447656SSherry.Moore@Sun.COM PCF8574_FAN_DEFAULT | PCF8574_FAN_MASKINTR;
8451708Sstevel tp->i2c_wlen = 1;
8461708Sstevel tp->i2c_rlen = 0;
8471708Sstevel tp->i2c_flags = I2C_WR;
8481708Sstevel
8491708Sstevel unitp->i2c_status =
8507656SSherry.Moore@Sun.COM nct_i2c_transfer(unitp->pcf8574_hdl, tp);
8511708Sstevel
8521708Sstevel unitp->poll_event = POLLIN;
8531708Sstevel pollwakeup(&unitp->poll, POLLIN);
8541708Sstevel
8551708Sstevel } else {
8561708Sstevel ic = DDI_INTR_UNCLAIMED;
8571708Sstevel }
8581708Sstevel }
8591708Sstevel break;
8601708Sstevel
8611708Sstevel default:
8621708Sstevel ic = DDI_INTR_UNCLAIMED;
8631708Sstevel } /* switch */
8641708Sstevel
8651708Sstevel intr_exit:
8661708Sstevel mutex_enter(&unitp->umutex);
8671708Sstevel unitp->pcf8574_flags = 0;
8681708Sstevel cv_signal(&unitp->pcf8574_cv);
8691708Sstevel mutex_exit(&unitp->umutex);
8701708Sstevel
8711708Sstevel return (ic);
8721708Sstevel }
8731708Sstevel
8741708Sstevel static int
call_copyin(caddr_t arg,struct pcf8574_unit * unitp,int mode)8751708Sstevel call_copyin(caddr_t arg, struct pcf8574_unit *unitp, int mode)
8761708Sstevel {
8771708Sstevel uchar_t *wbuf;
8781708Sstevel uchar_t *rbuf;
8791708Sstevel i2c_transfer_t i2ct;
8801708Sstevel i2c_transfer_t *i2ctp = unitp->i2c_tran;
8811708Sstevel
8821708Sstevel
8831708Sstevel if (ddi_copyin((void *)arg, (caddr_t)&i2ct,
8847656SSherry.Moore@Sun.COM sizeof (i2c_transfer_t), mode) != DDI_SUCCESS) {
8851708Sstevel return (I2C_FAILURE);
8861708Sstevel }
8871708Sstevel
8881708Sstevel /*
8891708Sstevel * Save the read and write buffer pointers in the transfer
8901708Sstevel * structure, otherwise these will get overwritten when we
8911708Sstevel * do a bcopy. Restore once done.
8921708Sstevel */
8931708Sstevel
8941708Sstevel wbuf = i2ctp->i2c_wbuf;
8951708Sstevel rbuf = i2ctp->i2c_rbuf;
8961708Sstevel
8971708Sstevel bcopy(&i2ct, i2ctp, sizeof (i2c_transfer_t));
8981708Sstevel
8991708Sstevel i2ctp->i2c_wbuf = wbuf;
9001708Sstevel i2ctp->i2c_rbuf = rbuf;
9011708Sstevel
9021708Sstevel /*
9031708Sstevel * copyin the read and write buffers to the saved buffers.
9041708Sstevel */
9051708Sstevel
9061708Sstevel if (i2ct.i2c_wlen != 0) {
9071708Sstevel if (ddi_copyin(i2ct.i2c_wbuf, (caddr_t)i2ctp->i2c_wbuf,
9087656SSherry.Moore@Sun.COM i2ct.i2c_wlen, mode) != DDI_SUCCESS) {
9091708Sstevel return (I2C_FAILURE);
9101708Sstevel }
9111708Sstevel }
9121708Sstevel
9131708Sstevel return (I2C_SUCCESS);
9141708Sstevel }
9151708Sstevel
9161708Sstevel static int
call_copyout(caddr_t arg,struct pcf8574_unit * unitp,int mode)9171708Sstevel call_copyout(caddr_t arg, struct pcf8574_unit *unitp, int mode)
9181708Sstevel {
9191708Sstevel i2c_transfer_t i2ct;
9201708Sstevel i2c_transfer_t *i2ctp = unitp->i2c_tran;
9211708Sstevel
9221708Sstevel /*
9231708Sstevel * We will copyout the last three fields only, skipping
9241708Sstevel * the remaining ones, before copying the rbuf to the
9251708Sstevel * user buffer.
9261708Sstevel */
9271708Sstevel
9281708Sstevel int uskip = sizeof (i2c_transfer_t) - 3*sizeof (int16_t),
9297656SSherry.Moore@Sun.COM kskip = sizeof (i2c_transfer_t) - 3*sizeof (int16_t);
9301708Sstevel
9311708Sstevel /*
9321708Sstevel * First copyin the user structure to the temporary i2ct,
9331708Sstevel * so that we have the wbuf and rbuf addresses in it.
9341708Sstevel */
9351708Sstevel
9361708Sstevel uskip = sizeof (i2c_transfer_t) - 3 * (sizeof (uint16_t));
9371708Sstevel
9381708Sstevel /*
9391708Sstevel * copyout the last three out fields now.
9401708Sstevel */
9411708Sstevel
9421708Sstevel if (ddi_copyout((void *)((intptr_t)i2ctp+kskip), (void *)
9437656SSherry.Moore@Sun.COM ((intptr_t)arg + uskip), 3*sizeof (uint16_t), mode)
9447656SSherry.Moore@Sun.COM != DDI_SUCCESS) {
9451708Sstevel return (I2C_FAILURE);
9461708Sstevel }
9471708Sstevel
9481708Sstevel /*
9491708Sstevel * In case we have something to write, get the address of the read
9501708Sstevel * buffer.
9511708Sstevel */
9521708Sstevel
9531708Sstevel if (i2ctp->i2c_rlen > i2ctp->i2c_r_resid) {
9541708Sstevel
9551708Sstevel if (ddi_copyin((void *)arg, &i2ct,
9567656SSherry.Moore@Sun.COM sizeof (i2c_transfer_t), mode) != DDI_SUCCESS) {
9571708Sstevel return (I2C_FAILURE);
9581708Sstevel }
9591708Sstevel
9601708Sstevel /*
9611708Sstevel * copyout the read buffer to the saved user buffer in i2ct.
9621708Sstevel */
9631708Sstevel
9641708Sstevel if (ddi_copyout(i2ctp->i2c_rbuf, i2ct.i2c_rbuf,
9657656SSherry.Moore@Sun.COM i2ctp->i2c_rlen - i2ctp->i2c_r_resid, mode)
9667656SSherry.Moore@Sun.COM != DDI_SUCCESS) {
9671708Sstevel return (I2C_FAILURE);
9681708Sstevel }
9691708Sstevel }
9701708Sstevel
9711708Sstevel return (I2C_SUCCESS);
9721708Sstevel }
9731708Sstevel
9741708Sstevel /*ARGSUSED*/
9751708Sstevel static int
pcf8574_ioctl(dev_t dev,int cmd,intptr_t arg,int mode,cred_t * credp,int * rvalp)9761708Sstevel pcf8574_ioctl(dev_t dev, int cmd, intptr_t arg,
9771708Sstevel int mode, cred_t *credp, int *rvalp)
9781708Sstevel {
9791708Sstevel struct pcf8574_unit *unitp;
9801708Sstevel register int instance;
9811708Sstevel int err = 0;
9821708Sstevel uint8_t value, inval, outval;
9831708Sstevel scsb_fru_status_t dev_presence;
9841708Sstevel
9851708Sstevel instance = getminor(dev);
9861708Sstevel
9871708Sstevel if (instance < 0) {
9881708Sstevel return (ENXIO);
9891708Sstevel }
9901708Sstevel unitp = (struct pcf8574_unit *)
9917656SSherry.Moore@Sun.COM ddi_get_soft_state(pcf8574_soft_statep, instance);
9921708Sstevel
9931708Sstevel if (unitp == NULL) {
9941708Sstevel return (ENXIO);
9951708Sstevel }
9961708Sstevel
9971708Sstevel dev_presence =
9987656SSherry.Moore@Sun.COM scsb_fru_status((uchar_t)unitp->props.slave_address);
9991708Sstevel
10001708Sstevel CV_LOCK(EINTR)
10011708Sstevel
10021708Sstevel switch (cmd) {
10031708Sstevel case ENVC_IOC_INTRMASK:
10041708Sstevel if (dev_presence == FRU_NOT_PRESENT) {
10051708Sstevel break;
10061708Sstevel }
10071708Sstevel
10081708Sstevel if (ddi_copyin((caddr_t)arg, (caddr_t)&inval,
10097656SSherry.Moore@Sun.COM sizeof (uint8_t), mode) != DDI_SUCCESS) {
10101708Sstevel err = EFAULT;
10111708Sstevel break;
10121708Sstevel }
10131708Sstevel
10141708Sstevel if (inval != 0 && inval != 1) {
10151708Sstevel err = EINVAL;
10161708Sstevel } else {
10171708Sstevel unitp->i2c_tran->i2c_wbuf[0] =
10187656SSherry.Moore@Sun.COM PCF8574_INT_MASK(inval);
10191708Sstevel if (pcf8574_write_chip(unitp, 1, PCF8574_INTRMASK_BIT)
10207656SSherry.Moore@Sun.COM != I2C_SUCCESS) {
10211708Sstevel err = EFAULT;
10221708Sstevel }
10231708Sstevel }
10241708Sstevel break;
10251708Sstevel
10261708Sstevel case ENVC_IOC_SETFAN:
10271708Sstevel if (unitp->pcf8574_type != PCF8574_TYPE_FANTRAY) {
10281708Sstevel err = EINVAL;
10291708Sstevel break;
10301708Sstevel }
10311708Sstevel if (dev_presence == FRU_NOT_PRESENT) {
10321708Sstevel err = EINVAL;
10331708Sstevel break;
10341708Sstevel }
10351708Sstevel if (ddi_copyin((caddr_t)arg, (caddr_t)&inval, sizeof (uint8_t),
10367656SSherry.Moore@Sun.COM mode) != DDI_SUCCESS) {
10371708Sstevel err = EFAULT;
10381708Sstevel break;
10391708Sstevel }
10401708Sstevel if (inval != PCF8574_FAN_SPEED_LOW &&
10417656SSherry.Moore@Sun.COM inval != PCF8574_FAN_SPEED_HIGH) {
10421708Sstevel err = EINVAL;
10431708Sstevel break;
10441708Sstevel }
10451708Sstevel
10461708Sstevel unitp->i2c_tran->i2c_wbuf[0] = PCF8574_FAN_SPEED(inval);
10471708Sstevel
10481708Sstevel if (pcf8574_write_chip(unitp, 1, PCF8574_FANSPEED_BIT)
10497656SSherry.Moore@Sun.COM != I2C_SUCCESS) {
10501708Sstevel err = EFAULT;
10511708Sstevel }
10521708Sstevel break;
10531708Sstevel
10541708Sstevel case ENVC_IOC_SETSTATUS:
10551708Sstevel /*
10561708Sstevel * Allow this ioctl only in DIAG mode.
10571708Sstevel */
10581708Sstevel if (unitp->current_mode != ENVCTRL_DIAG_MODE) {
10591708Sstevel err = EINVAL;
10601708Sstevel } else {
10611708Sstevel if (dev_presence == FRU_NOT_PRESENT) {
10621708Sstevel err = EINVAL;
10631708Sstevel break;
10641708Sstevel }
10651708Sstevel if (ddi_copyin((caddr_t)arg, (caddr_t)&inval,
10667656SSherry.Moore@Sun.COM sizeof (uint8_t), mode) != DDI_SUCCESS) {
10671708Sstevel err = EFAULT;
10681708Sstevel } else {
10691708Sstevel unitp->i2c_tran->i2c_wbuf[0] = inval & 0xff;
10701708Sstevel if (pcf8574_write_chip(unitp, 1, 0xff)
10717656SSherry.Moore@Sun.COM != I2C_SUCCESS) {
10721708Sstevel err = EFAULT;
10731708Sstevel }
10741708Sstevel }
10751708Sstevel }
10761708Sstevel break;
10771708Sstevel
10781708Sstevel case ENVC_IOC_GETFAN:
10791708Sstevel case ENVC_IOC_GETSTATUS:
10801708Sstevel case ENVC_IOC_GETTYPE:
10811708Sstevel case ENVC_IOC_GETFAULT:
10821708Sstevel case ENVC_IOC_PSTEMPOK:
10831708Sstevel case ENVC_IOC_PSFANOK:
10841708Sstevel case ENVC_IOC_PSONOFF: {
10851708Sstevel if (dev_presence == FRU_NOT_PRESENT) {
10861708Sstevel err = EINVAL;
10871708Sstevel break;
10881708Sstevel }
10891708Sstevel if (pcf8574_read_chip(unitp, 1)
10907656SSherry.Moore@Sun.COM != I2C_SUCCESS) {
10911708Sstevel err = EFAULT;
10921708Sstevel break;
10931708Sstevel }
10941708Sstevel value = unitp->i2c_tran->i2c_rbuf[0];
10951708Sstevel if (cmd == ENVC_IOC_GETFAN) {
10961708Sstevel if (unitp->pcf8574_type != PCF8574_TYPE_FANTRAY) {
10971708Sstevel err = EINVAL;
10981708Sstevel break;
10991708Sstevel } else {
11001708Sstevel outval = PCF8574_FAN_FANSPD(value);
11011708Sstevel }
11021708Sstevel }
11031708Sstevel else
11041708Sstevel if (cmd == ENVC_IOC_GETSTATUS) {
11051708Sstevel outval = value;
11061708Sstevel }
11071708Sstevel else
11081708Sstevel if (cmd == ENVC_IOC_GETTYPE) {
11091708Sstevel if (unitp->pcf8574_type == PCF8574_TYPE_PWRSUPP)
11101708Sstevel outval = PCF8574_PS_TYPE(value);
11111708Sstevel if (unitp->pcf8574_type == PCF8574_TYPE_FANTRAY)
11121708Sstevel outval = PCF8574_FAN_TYPE(value);
11131708Sstevel }
11141708Sstevel else
11151708Sstevel if (cmd == ENVC_IOC_GETFAULT) {
11161708Sstevel if (unitp->pcf8574_type == PCF8574_TYPE_PWRSUPP)
11171708Sstevel outval = PCF8574_PS_FAULT(value);
11181708Sstevel if (unitp->pcf8574_type == PCF8574_TYPE_FANTRAY)
11191708Sstevel outval = PCF8574_PS_FAULT(value);
11201708Sstevel }
11211708Sstevel else
11221708Sstevel if (cmd == ENVC_IOC_PSTEMPOK) {
11231708Sstevel outval = PCF8574_PS_TEMPOK(value);
11241708Sstevel }
11251708Sstevel else
11261708Sstevel if (cmd == ENVC_IOC_PSFANOK) {
11271708Sstevel outval = PCF8574_PS_FANOK(value);
11281708Sstevel }
11291708Sstevel else
11301708Sstevel if (cmd == ENVC_IOC_PSONOFF) {
11311708Sstevel outval = PCF8574_PS_ONOFF(value);
11321708Sstevel } else {
11331708Sstevel outval = 0;
11341708Sstevel }
11351708Sstevel
11361708Sstevel if (ddi_copyout((caddr_t)&outval, (caddr_t)arg,
11377656SSherry.Moore@Sun.COM sizeof (uint8_t), mode) != DDI_SUCCESS) {
11381708Sstevel err = EFAULT;
11391708Sstevel }
11401708Sstevel }
11411708Sstevel break;
11421708Sstevel
11431708Sstevel case ENVC_IOC_GETMODE: {
11441708Sstevel uint8_t curr_mode = unitp->current_mode;
11451708Sstevel
11461708Sstevel if (ddi_copyout((caddr_t)&curr_mode, (caddr_t)arg,
11477656SSherry.Moore@Sun.COM sizeof (uint8_t), mode) != DDI_SUCCESS) {
11481708Sstevel err = EFAULT;
11491708Sstevel }
11501708Sstevel break;
11511708Sstevel }
11521708Sstevel
11531708Sstevel case ENVC_IOC_SETMODE: {
11541708Sstevel uint8_t curr_mode;
11551708Sstevel if (ddi_copyin((caddr_t)arg, (caddr_t)&curr_mode,
11567656SSherry.Moore@Sun.COM sizeof (uint8_t), mode) != DDI_SUCCESS) {
11571708Sstevel err = EFAULT;
11581708Sstevel break;
11591708Sstevel }
11601708Sstevel if (curr_mode == ENVCTRL_DIAG_MODE ||
11617656SSherry.Moore@Sun.COM curr_mode == ENVCTRL_NORMAL_MODE) {
11621708Sstevel unitp->current_mode = curr_mode; /* Don't do anything */
11631708Sstevel }
11641708Sstevel break;
11651708Sstevel }
11661708Sstevel
11671708Sstevel
11681708Sstevel case I2CDEV_TRAN:
11691708Sstevel if (call_copyin((caddr_t)arg, unitp, mode) != DDI_SUCCESS) {
11701708Sstevel err = EFAULT;
11711708Sstevel break;
11721708Sstevel }
11731708Sstevel unitp->i2c_status = err =
11747656SSherry.Moore@Sun.COM nct_i2c_transfer(unitp->pcf8574_hdl, unitp->i2c_tran);
11751708Sstevel
11761708Sstevel if (err != I2C_SUCCESS) {
11771708Sstevel err = EIO;
11781708Sstevel } else {
11791708Sstevel if (call_copyout((caddr_t)arg, unitp, mode)
11807656SSherry.Moore@Sun.COM != DDI_SUCCESS) {
11811708Sstevel err = EFAULT;
11821708Sstevel break;
11831708Sstevel }
11841708Sstevel }
11851708Sstevel break;
11861708Sstevel
11871708Sstevel default:
11881708Sstevel err = EINVAL;
11891708Sstevel }
11901708Sstevel
11911708Sstevel CV_UNLOCK
11921708Sstevel
11931708Sstevel return (err);
11941708Sstevel }
11951708Sstevel
11961708Sstevel static int
pcf8574_add_kstat(struct pcf8574_unit * unitp,scsb_fru_status_t dev_presence)11971708Sstevel pcf8574_add_kstat(struct pcf8574_unit *unitp, scsb_fru_status_t dev_presence)
11981708Sstevel {
11991708Sstevel char ksname[50];
12001708Sstevel int id;
12011708Sstevel uint8_t i2c_address = unitp->props.slave_address;
12021708Sstevel
12031708Sstevel /*
12041708Sstevel * We create the kstat depending on the device function,
12051708Sstevel * allocate the kstat placeholder and initialize the
12061708Sstevel * values.
12071708Sstevel */
12081708Sstevel unitp->envctrl_kstat = NULL;
12091708Sstevel switch (unitp->pcf8574_type) {
12101708Sstevel case PCF8574_TYPE_CPUVOLTAGE:
12111708Sstevel {
12121708Sstevel if ((unitp->kstatp = kstat_create(I2C_PCF8574_NAME,
12137656SSherry.Moore@Sun.COM unitp->instance, I2C_KSTAT_CPUVOLTAGE, "misc",
12147656SSherry.Moore@Sun.COM KSTAT_TYPE_RAW, sizeof (envctrl_cpuvoltage_t),
12157656SSherry.Moore@Sun.COM KSTAT_FLAG_PERSISTENT)) != NULL) {
12161708Sstevel
12171708Sstevel if ((unitp->envctrl_kstat = kmem_zalloc(
12187656SSherry.Moore@Sun.COM sizeof (envctrl_cpuvoltage_t), KM_NOSLEEP)) ==
12197656SSherry.Moore@Sun.COM NULL) {
12201708Sstevel kstat_delete(unitp->kstatp);
12211708Sstevel return (DDI_FAILURE);
12221708Sstevel }
12231708Sstevel } else {
12241708Sstevel return (DDI_FAILURE);
12251708Sstevel }
12261708Sstevel
12271708Sstevel break;
12281708Sstevel }
12291708Sstevel case PCF8574_TYPE_PWRSUPP:
12301708Sstevel {
12311708Sstevel envctrl_pwrsupp_t *envp;
12321708Sstevel if (i2c_address == PCF8574_ADR_PWRSUPPLY1) {
12331708Sstevel id = 1;
12341708Sstevel } else if (i2c_address == PCF8574_ADR_PWRSUPPLY2) {
12351708Sstevel id = 2;
12361708Sstevel } else {
12371708Sstevel id = i2c_address - PCF8574_ADR_PWRSUPPLY1;
12381708Sstevel }
1239*11311SSurya.Prakki@Sun.COM (void) sprintf(ksname, "%s%d", I2C_KSTAT_PWRSUPPLY, id);
12401708Sstevel if ((unitp->kstatp = kstat_create(I2C_PCF8574_NAME,
12417656SSherry.Moore@Sun.COM unitp->instance, ksname, "misc",
12427656SSherry.Moore@Sun.COM KSTAT_TYPE_RAW, sizeof (envctrl_pwrsupp_t),
12437656SSherry.Moore@Sun.COM KSTAT_FLAG_PERSISTENT)) != NULL) {
12441708Sstevel
12451708Sstevel if ((unitp->envctrl_kstat = kmem_zalloc(
12467656SSherry.Moore@Sun.COM sizeof (envctrl_pwrsupp_t), KM_NOSLEEP)) ==
12477656SSherry.Moore@Sun.COM NULL) {
12481708Sstevel kstat_delete(unitp->kstatp);
12491708Sstevel return (DDI_FAILURE);
12501708Sstevel }
12511708Sstevel /*
12521708Sstevel * Initialize the kstat fields. Need to initialize
12531708Sstevel * the present field from SCSB info (dev_presence)
12541708Sstevel */
12551708Sstevel envp = (envctrl_pwrsupp_t *)unitp->envctrl_kstat;
12561708Sstevel
12571708Sstevel envp->ps_present = dev_presence;
12581708Sstevel envp->ps_ok = 0;
12591708Sstevel envp->temp_ok = 0;
12601708Sstevel envp->psfan_ok = 0;
12611708Sstevel envp->on_state = 0;
12621708Sstevel envp->ps_ver = 0;
12631708Sstevel } else {
12641708Sstevel return (DDI_FAILURE);
12651708Sstevel }
12661708Sstevel
12671708Sstevel break;
12681708Sstevel }
12691708Sstevel case PCF8574_TYPE_FANTRAY:
12701708Sstevel {
12711708Sstevel envctrl_fantray_t *envp;
12721708Sstevel if (i2c_address == PCF8574_ADR_FANTRAY1) {
12731708Sstevel id = 1;
12741708Sstevel } else if (i2c_address == PCF8574_ADR_FANTRAY2) {
12751708Sstevel id = 2;
12761708Sstevel } else {
12771708Sstevel id = i2c_address - PCF8574_ADR_FANTRAY1;
12781708Sstevel }
1279*11311SSurya.Prakki@Sun.COM (void) sprintf(ksname, "%s%d", I2C_KSTAT_FANTRAY, id);
12801708Sstevel if ((unitp->kstatp = kstat_create(I2C_PCF8574_NAME,
12817656SSherry.Moore@Sun.COM unitp->instance, ksname, "misc",
12827656SSherry.Moore@Sun.COM KSTAT_TYPE_RAW, sizeof (envctrl_fantray_t),
12837656SSherry.Moore@Sun.COM KSTAT_FLAG_PERSISTENT | KSTAT_FLAG_WRITABLE)) != NULL) {
12841708Sstevel
12851708Sstevel if ((unitp->envctrl_kstat = kmem_zalloc(
12867656SSherry.Moore@Sun.COM sizeof (envctrl_fantray_t), KM_NOSLEEP)) ==
12877656SSherry.Moore@Sun.COM NULL) {
12881708Sstevel kstat_delete(unitp->kstatp);
12891708Sstevel return (DDI_FAILURE);
12901708Sstevel }
12911708Sstevel
12921708Sstevel /*
12931708Sstevel * Initialize the kstat fields. Need to initialize
12941708Sstevel * the present field from SCSB info (dev_presence)
12951708Sstevel */
12961708Sstevel envp = (envctrl_fantray_t *)unitp->envctrl_kstat;
12971708Sstevel
12981708Sstevel envp->fan_present = dev_presence;
12991708Sstevel envp->fan_ok = 0;
13001708Sstevel envp->fanspeed = PCF8574_FAN_SPEED60;
13011708Sstevel envp->fan_ver = 0;
13021708Sstevel } else {
13031708Sstevel return (DDI_FAILURE);
13041708Sstevel }
13051708Sstevel
13061708Sstevel break;
13071708Sstevel }
13081708Sstevel default:
13091708Sstevel return (DDI_FAILURE);
13101708Sstevel }
13111708Sstevel
13121708Sstevel unitp->kstatp->ks_private = (void *)unitp;
13131708Sstevel unitp->kstatp->ks_update = pcf8574_kstat_update;
13141708Sstevel
13151708Sstevel kstat_install(unitp->kstatp);
13161708Sstevel
13171708Sstevel return (DDI_SUCCESS);
13181708Sstevel }
13191708Sstevel
13201708Sstevel /*
13211708Sstevel * This function reads a single byte from the pcf8574 chip, for use by the
13221708Sstevel * kstat routines. The protocol for read will depend on the function.
13231708Sstevel */
13241708Sstevel
13251708Sstevel static int
pcf8574_read_chip(struct pcf8574_unit * unitp,uint16_t size)13261708Sstevel pcf8574_read_chip(struct pcf8574_unit *unitp, uint16_t size)
13271708Sstevel {
13281708Sstevel int retval, i;
13291708Sstevel i2c_transfer_t *tp = unitp->i2c_tran;
13301708Sstevel
13311708Sstevel
13321708Sstevel tp->i2c_flags = I2C_RD;
13331708Sstevel tp->i2c_rlen = size;
13341708Sstevel tp->i2c_wlen = 0;
13351708Sstevel
13361708Sstevel /*
13371708Sstevel * Read the bytes from the pcf8574, mask off the
13381708Sstevel * non-read bits and return the value. Block with
13391708Sstevel * the driverwide lock.
13401708Sstevel */
13411708Sstevel unitp->i2c_status = retval =
13427656SSherry.Moore@Sun.COM nct_i2c_transfer(unitp->pcf8574_hdl, unitp->i2c_tran);
13431708Sstevel
13441708Sstevel if (retval != I2C_SUCCESS) {
13451708Sstevel return (retval);
13461708Sstevel }
13471708Sstevel
13481708Sstevel for (i = 0; i < size; i++) {
13491708Sstevel tp->i2c_rbuf[i] &= unitp->readmask;
13501708Sstevel }
13511708Sstevel
13521708Sstevel return (I2C_SUCCESS);
13531708Sstevel }
13541708Sstevel
13551708Sstevel /*
13561708Sstevel * This function writes a single byte to the pcf8574 chip, for use by the
13571708Sstevel * ioctl routines. The protocol for write will depend on the function.
13581708Sstevel * The bitpattern tells which bits are being modified, by setting these
13591708Sstevel * bits in bitpattern to 1, e.g for fanspeed, bitpattern = 0x08, fanspeed
13601708Sstevel * and intr 0x0c, only intr 0x04.
13611708Sstevel */
13621708Sstevel
13631708Sstevel static int
pcf8574_write_chip(struct pcf8574_unit * unitp,uint16_t size,uint8_t bitpattern)13641708Sstevel pcf8574_write_chip(struct pcf8574_unit *unitp,
13651708Sstevel uint16_t size, uint8_t bitpattern)
13661708Sstevel {
13671708Sstevel i2c_transfer_t *tp = unitp->i2c_tran;
13681708Sstevel int i;
13691708Sstevel
13701708Sstevel /*
13711708Sstevel * pcf8574_write
13721708Sstevel *
13731708Sstevel * First read the byte, modify only the writable
13741708Sstevel * ports, then write back the modified data.
13751708Sstevel */
13761708Sstevel tp->i2c_wlen = 0;
13771708Sstevel tp->i2c_rlen = size;
13781708Sstevel tp->i2c_flags = I2C_RD;
13791708Sstevel
13801708Sstevel unitp->i2c_status = nct_i2c_transfer(unitp->pcf8574_hdl, tp);
13811708Sstevel
13821708Sstevel if (unitp->i2c_status != I2C_SUCCESS) {
13831708Sstevel return (I2C_FAILURE);
13841708Sstevel }
13851708Sstevel
13861708Sstevel /*
13871708Sstevel * Our concern is when we have to write only a few bits.
13881708Sstevel * We need to make sure we write the same value to those
13891708Sstevel * bit positions which does not appear in bitpattern.
13901708Sstevel */
13911708Sstevel
13921708Sstevel /*
13931708Sstevel * 1) Ignore all bits than the one we are writing
13941708Sstevel * 2) Now 0 the bits we intend to modify in the value
13951708Sstevel * read from the chip, preserving all others.
13961708Sstevel * 3) Now turn all non-writable ( read only/reserved )
13971708Sstevel * bits to 1. The value now should contain:
13981708Sstevel * 1 in all non-writable bits.
13991708Sstevel * 0 in the bis(s) we intend to modify.
14001708Sstevel * no change in the writable bits we don't modify.
14011708Sstevel * 4) Now OR it with the bits we got before, i.e. after
14021708Sstevel * ignoring all bits other than one we are writing.
14031708Sstevel */
14041708Sstevel
14051708Sstevel for (i = 0; i < size; i++) {
14061708Sstevel tp->i2c_rbuf[i] &= ~(bitpattern);
14071708Sstevel
14081708Sstevel tp->i2c_rbuf[i] |= ~(unitp->writemask);
14091708Sstevel
14101708Sstevel tp->i2c_wbuf[i] = tp->i2c_rbuf[i] |
14117656SSherry.Moore@Sun.COM (tp->i2c_wbuf[i] & bitpattern);
14121708Sstevel }
14131708Sstevel
14141708Sstevel tp->i2c_rlen = 0;
14151708Sstevel tp->i2c_wlen = size;
14161708Sstevel tp->i2c_flags = I2C_WR;
14171708Sstevel
14181708Sstevel unitp->i2c_status = nct_i2c_transfer(unitp->pcf8574_hdl, tp);
14191708Sstevel
14201708Sstevel return (unitp->i2c_status);
14211708Sstevel }
14221708Sstevel
14231708Sstevel static int
pcf8574_kstat_update(kstat_t * ksp,int rw)14241708Sstevel pcf8574_kstat_update(kstat_t *ksp, int rw)
14251708Sstevel {
14261708Sstevel struct pcf8574_unit *unitp;
14271708Sstevel char *kstatp;
14281708Sstevel uint8_t value;
14291708Sstevel int err = DDI_SUCCESS;
14301708Sstevel scsb_fru_status_t dev_presence;
14311708Sstevel
14321708Sstevel unitp = (struct pcf8574_unit *)ksp->ks_private;
14331708Sstevel if (unitp->envctrl_kstat == NULL) { /* May be detaching */
14341708Sstevel return (err);
14351708Sstevel }
14361708Sstevel
14371708Sstevel CV_LOCK(EINTR)
14381708Sstevel
14391708Sstevel /*
14401708Sstevel * Need to call scsb to find whether device is present.
14411708Sstevel * For I2C devices, the I2C address is used as a FRU ID.
14421708Sstevel */
14431708Sstevel if (unitp->pcf8574_type == PCF8574_TYPE_CPUVOLTAGE) {
14441708Sstevel dev_presence = FRU_PRESENT;
14451708Sstevel } else {
14461708Sstevel dev_presence =
14477656SSherry.Moore@Sun.COM scsb_fru_status((uchar_t)unitp->props.slave_address);
14481708Sstevel }
14491708Sstevel
14501708Sstevel kstatp = (char *)ksp->ks_data;
14511708Sstevel
14521708Sstevel /*
14531708Sstevel * We could have write on the power supply and the fantray
14541708Sstevel * pcf8574 chips. For masking the interrupt on both, or
14551708Sstevel * controlling the fan speed on the fantray. But write
14561708Sstevel * will not be allowed through the kstat interface. For
14571708Sstevel * the present field, call SCSB.
14581708Sstevel */
14591708Sstevel
14601708Sstevel if (rw == KSTAT_WRITE) {
14611708Sstevel if (unitp->pcf8574_type != PCF8574_TYPE_FANTRAY) {
14621708Sstevel err = EACCES;
14631708Sstevel goto kstat_exit;
14641708Sstevel }
14651708Sstevel value = ((envctrl_fantray_t *)kstatp)->fanspeed;
14661708Sstevel if (value != PCF8574_FAN_SPEED_LOW &&
14677656SSherry.Moore@Sun.COM value != PCF8574_FAN_SPEED_HIGH) {
14681708Sstevel err = EINVAL;
14691708Sstevel goto kstat_exit;
14701708Sstevel }
14711708Sstevel
14721708Sstevel unitp->i2c_tran->i2c_wbuf[0] = PCF8574_FAN_SPEED(value);
14731708Sstevel
14741708Sstevel if (dev_presence == FRU_PRESENT &&
14757656SSherry.Moore@Sun.COM pcf8574_write_chip(unitp, 1, PCF8574_FANSPEED_BIT)
14767656SSherry.Moore@Sun.COM != I2C_SUCCESS) {
14771708Sstevel err = EFAULT;
14781708Sstevel goto kstat_exit;
14791708Sstevel }
14801708Sstevel
14811708Sstevel } else {
14821708Sstevel /*
14831708Sstevel * First make sure that the FRU exists by checking the SCSB
14841708Sstevel * dev_presence info. If not present, set the change field,
14851708Sstevel * clear the kstat fields and make sure the kstat *_present
14861708Sstevel * field is set to dev_presence from the SCSB driver.
14871708Sstevel */
14881708Sstevel if (dev_presence == FRU_PRESENT &&
14897656SSherry.Moore@Sun.COM pcf8574_read_chip(unitp, 1) != I2C_SUCCESS) {
14901708Sstevel /*
14911708Sstevel * Looks like a real IO error.
14921708Sstevel */
14931708Sstevel err = EIO;
14941708Sstevel CV_UNLOCK
14951708Sstevel
14961708Sstevel return (err);
14971708Sstevel }
14981708Sstevel if (dev_presence == FRU_PRESENT)
14991708Sstevel value = unitp->i2c_tran->i2c_rbuf[0];
15001708Sstevel else
15011708Sstevel value = 0;
15021708Sstevel
15031708Sstevel switch (unitp->pcf8574_type) {
15041708Sstevel case PCF8574_TYPE_CPUVOLTAGE: {
15051708Sstevel envctrl_cpuvoltage_t *envp =
15067656SSherry.Moore@Sun.COM (envctrl_cpuvoltage_t *)unitp->envctrl_kstat;
15071708Sstevel envp->value = value;
15081708Sstevel bcopy((caddr_t)envp, kstatp,
15097656SSherry.Moore@Sun.COM sizeof (envctrl_cpuvoltage_t));
15101708Sstevel
15111708Sstevel break;
15121708Sstevel }
15131708Sstevel case PCF8574_TYPE_PWRSUPP: {
15141708Sstevel envctrl_pwrsupp_t *envp =
15157656SSherry.Moore@Sun.COM (envctrl_pwrsupp_t *)unitp->envctrl_kstat;
15161708Sstevel
15171708Sstevel envp->ps_present = dev_presence;
15181708Sstevel envp->ps_ok = PCF8574_PS_FAULT(value);
15191708Sstevel envp->temp_ok = PCF8574_PS_TEMPOK(value);
15201708Sstevel envp->psfan_ok = PCF8574_PS_FANOK(value);
15211708Sstevel envp->on_state = PCF8574_PS_ONOFF(value);
15221708Sstevel envp->ps_ver = PCF8574_PS_TYPE(value);
15231708Sstevel
15241708Sstevel bcopy((caddr_t)envp, kstatp,
15257656SSherry.Moore@Sun.COM sizeof (envctrl_pwrsupp_t));
15261708Sstevel
15271708Sstevel break;
15281708Sstevel }
15291708Sstevel case PCF8574_TYPE_FANTRAY: {
15301708Sstevel envctrl_fantray_t *envp =
15317656SSherry.Moore@Sun.COM (envctrl_fantray_t *)unitp->envctrl_kstat;
15321708Sstevel
15331708Sstevel envp->fan_present = dev_presence;
15341708Sstevel envp->fan_ver = PCF8574_FAN_TYPE(value);
15351708Sstevel envp->fan_ok = PCF8574_FAN_FAULT(value);
15361708Sstevel envp->fanspeed = PCF8574_FAN_FANSPD(value);
15371708Sstevel
15381708Sstevel bcopy((caddr_t)unitp->envctrl_kstat, kstatp,
15397656SSherry.Moore@Sun.COM sizeof (envctrl_fantray_t));
15401708Sstevel
15411708Sstevel break;
15421708Sstevel }
15431708Sstevel
15441708Sstevel default:
15451708Sstevel break;
15461708Sstevel }
15471708Sstevel }
15481708Sstevel
15491708Sstevel kstat_exit:
15501708Sstevel
15511708Sstevel CV_UNLOCK
15521708Sstevel
15531708Sstevel return (err);
15541708Sstevel }
15551708Sstevel
15561708Sstevel static void
pcf8574_delete_kstat(struct pcf8574_unit * unitp)15571708Sstevel pcf8574_delete_kstat(struct pcf8574_unit *unitp)
15581708Sstevel {
15591708Sstevel /*
15601708Sstevel * Depending on the function, deallocate the correct
15611708Sstevel * kernel allocated memory.
15621708Sstevel */
15631708Sstevel if (unitp->kstatp != NULL) {
15641708Sstevel kstat_delete(unitp->kstatp);
15651708Sstevel }
15661708Sstevel
15671708Sstevel switch (unitp->pcf8574_type) {
15681708Sstevel case PCF8574_TYPE_CPUVOLTAGE: {
15691708Sstevel if (unitp->envctrl_kstat != NULL) {
15701708Sstevel kmem_free(unitp->envctrl_kstat,
15717656SSherry.Moore@Sun.COM sizeof (envctrl_cpuvoltage_t));
15721708Sstevel }
15731708Sstevel break;
15741708Sstevel }
15751708Sstevel case PCF8574_TYPE_PWRSUPP: {
15761708Sstevel if (unitp->envctrl_kstat != NULL) {
15771708Sstevel kmem_free(unitp->envctrl_kstat,
15787656SSherry.Moore@Sun.COM sizeof (envctrl_pwrsupp_t));
15791708Sstevel }
15801708Sstevel
15811708Sstevel break;
15821708Sstevel }
15831708Sstevel case PCF8574_TYPE_FANTRAY: {
15841708Sstevel if (unitp->envctrl_kstat != NULL) {
15851708Sstevel kmem_free(unitp->envctrl_kstat,
15867656SSherry.Moore@Sun.COM sizeof (envctrl_fantray_t));
15871708Sstevel }
15881708Sstevel break;
15891708Sstevel }
15901708Sstevel default:
15911708Sstevel break;
15921708Sstevel }
15931708Sstevel
15941708Sstevel unitp->envctrl_kstat = NULL;
15951708Sstevel }
15961708Sstevel
15971708Sstevel static int
pcf8574_read_props(struct pcf8574_unit * unitp)15981708Sstevel pcf8574_read_props(struct pcf8574_unit *unitp)
15991708Sstevel {
16001708Sstevel dev_info_t *dip = unitp->dip;
16011708Sstevel int retval = 0, prop_len;
16021708Sstevel uint32_t *prop_value = NULL;
16031708Sstevel uint8_t i2c_address;
16041708Sstevel char *function;
16051708Sstevel
16061708Sstevel /*
16071708Sstevel * read the pcf8574_function property. If this property is not
16081708Sstevel * found, return ERROR. Else, make sure it's either powersupply
16091708Sstevel * or fantray.
16101708Sstevel */
16111708Sstevel
16121708Sstevel if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
16137656SSherry.Moore@Sun.COM "pcf8574_function", &function) != DDI_SUCCESS) {
16141708Sstevel dbg_print(CE_WARN, "Couldn't find pcf8574_function property");
16151708Sstevel
16161708Sstevel return (DDI_FAILURE);
16171708Sstevel }
16181708Sstevel
16191708Sstevel if (strcmp(function, "fantray") == 0) {
16201708Sstevel unitp->pcf8574_type = PCF8574_TYPE_FANTRAY;
16211708Sstevel /*
16221708Sstevel * Will fail the fantray attach if patch - 1.
16231708Sstevel */
16241708Sstevel if (nct_p10fan_patch) {
16251708Sstevel #ifdef DEBUG
16261708Sstevel cmn_err(CE_WARN, "nct_p10fan_patch set: will not load "
16277656SSherry.Moore@Sun.COM "fantary:address %x,%x", unitp->props.i2c_bus,
16287656SSherry.Moore@Sun.COM unitp->props.slave_address);
16291708Sstevel #endif
16301708Sstevel ddi_prop_free(function);
16311708Sstevel return (DDI_FAILURE);
16321708Sstevel }
16331708Sstevel } else
16341708Sstevel if (strcmp(function, "powersupply") == 0) {
16351708Sstevel unitp->pcf8574_type = PCF8574_TYPE_PWRSUPP;
16361708Sstevel } else {
16371708Sstevel dbg_print(CE_WARN, "Neither powersupply nor fantray");
16381708Sstevel ddi_prop_free(function);
16391708Sstevel
16401708Sstevel return (DDI_FAILURE);
16411708Sstevel }
16421708Sstevel
16431708Sstevel ddi_prop_free(function);
16441708Sstevel
16451708Sstevel retval = ddi_getlongprop(DDI_DEV_T_ANY, dip,
16467656SSherry.Moore@Sun.COM DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP,
16477656SSherry.Moore@Sun.COM "reg", (caddr_t)&prop_value, &prop_len);
16481708Sstevel if (retval == DDI_PROP_SUCCESS) {
16491708Sstevel unitp->props.i2c_bus = (uint16_t)prop_value[0];
16501708Sstevel unitp->props.slave_address = i2c_address =
16517656SSherry.Moore@Sun.COM (uint8_t)prop_value[1];
16521708Sstevel kmem_free(prop_value, prop_len);
16531708Sstevel
16541708Sstevel if (i2c_address>>4 == 7)
16551708Sstevel unitp->sensor_type = PCF8574A;
16561708Sstevel else if (i2c_address>>4 == 4)
16571708Sstevel unitp->sensor_type = PCF8574;
16581708Sstevel else {
16591708Sstevel unitp->sensor_type = PCF8574A;
16601708Sstevel dbg_print(CE_WARN, "Not a pcf8574/a device");
16611708Sstevel }
16621708Sstevel
16631708Sstevel } else {
16641708Sstevel unitp->props.i2c_bus = (uint16_t)-1;
16651708Sstevel unitp->props.slave_address = (uint16_t)-1;
16661708Sstevel }
16671708Sstevel
16681708Sstevel /*
16691708Sstevel * Get the Property information that the driver will be using
16701708Sstevel * see typedef struct pcf8574_properties_t;
16711708Sstevel */
16721708Sstevel
16731708Sstevel unitp->pcf8574_canintr = 0;
16741708Sstevel retval = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
16757656SSherry.Moore@Sun.COM "interrupts", -1);
16761708Sstevel if (retval >= 0) {
16771708Sstevel int prop_len, intr_pri = 4;
16781708Sstevel unitp->pcf8574_canintr |= PCF8574_INTR_ON;
16791708Sstevel if (ddi_getproplen(DDI_DEV_T_ANY, dip,
16807656SSherry.Moore@Sun.COM DDI_PROP_DONTPASS, "interrupt-priorities",
16817656SSherry.Moore@Sun.COM &prop_len) == DDI_PROP_NOT_FOUND) {
16821708Sstevel retval = ddi_prop_create(DDI_DEV_T_NONE, dip,
16837656SSherry.Moore@Sun.COM DDI_PROP_CANSLEEP, "interrupt-priorities",
16847656SSherry.Moore@Sun.COM (caddr_t)&intr_pri, sizeof (int));
16851708Sstevel #ifdef DEBUG
16861708Sstevel if (retval != DDI_PROP_SUCCESS) {
16871708Sstevel cmn_err(CE_WARN, "Failed to create interrupt- \
16881708Sstevel priorities property, retval %d", retval);
16891708Sstevel }
16901708Sstevel #endif /* DEBUG */
16911708Sstevel }
16921708Sstevel }
16931708Sstevel
16941708Sstevel /*
16951708Sstevel * No channels-in-use property for the fan and powersupplies.
16961708Sstevel */
16971708Sstevel unitp->props.num_chans_used = 0;
16981708Sstevel if (i2c_address == PCF8574_ADR_CPUVOLTAGE) {
16991708Sstevel if (ddi_getproplen(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
17007656SSherry.Moore@Sun.COM "channels-in-use", &prop_len) == DDI_PROP_SUCCESS) {
17011708Sstevel retval = ddi_prop_lookup_byte_array(DDI_DEV_T_ANY,
17027656SSherry.Moore@Sun.COM dip, DDI_PROP_DONTPASS,
17037656SSherry.Moore@Sun.COM "channels-in-use",
17047656SSherry.Moore@Sun.COM (uchar_t **)&unitp->props.channels_in_use,
17057656SSherry.Moore@Sun.COM &unitp->props.num_chans_used);
17061708Sstevel if (retval != DDI_PROP_SUCCESS) {
17071708Sstevel unitp->props.num_chans_used = 0;
17081708Sstevel } else {
17091708Sstevel unitp->props.num_chans_used /=
17107656SSherry.Moore@Sun.COM sizeof (pcf8574_channel_t);
17111708Sstevel }
17121708Sstevel }
17131708Sstevel }
17141708Sstevel
17151708Sstevel return (DDI_PROP_SUCCESS);
17161708Sstevel }
17171708Sstevel
17181708Sstevel /*
17191708Sstevel * callback function to register with the SCSB driver in order to be
17201708Sstevel * informed about changes in device instance presence.
17211708Sstevel */
17221708Sstevel /*ARGSUSED*/
17231708Sstevel void
pcf8574_callback(void * softstate,scsb_fru_event_t cb_event,scsb_fru_status_t dev_presence)17241708Sstevel pcf8574_callback(void *softstate, scsb_fru_event_t cb_event,
17251708Sstevel scsb_fru_status_t dev_presence)
17261708Sstevel {
17271708Sstevel struct pcf8574_unit *unitp = (struct pcf8574_unit *)softstate;
17281708Sstevel #ifdef DEBUG
17291708Sstevel if (pcf8574_debug & 0x00800001)
17301708Sstevel cmn_err(CE_NOTE, "pcf8574_callback(unitp,%d,%d)",
17317656SSherry.Moore@Sun.COM (int)cb_event, (int)dev_presence);
17321708Sstevel #endif /* DEBUG */
17331708Sstevel
17341708Sstevel switch (unitp->pcf8574_type) {
17351708Sstevel case PCF8574_TYPE_CPUVOLTAGE: {
17361708Sstevel /*
17371708Sstevel * This Unit is not Field Replacable and will not
17381708Sstevel * generate any events at the SCB.
17391708Sstevel */
17401708Sstevel break;
17411708Sstevel }
17421708Sstevel case PCF8574_TYPE_PWRSUPP: {
17431708Sstevel envctrl_pwrsupp_t *envp;
17441708Sstevel
17451708Sstevel envp = (envctrl_pwrsupp_t *)unitp->envctrl_kstat;
17461708Sstevel if (dev_presence == FRU_NOT_PRESENT) {
17471708Sstevel envp->ps_ok = 0;
17481708Sstevel envp->temp_ok = 0;
17491708Sstevel envp->psfan_ok = 0;
17501708Sstevel envp->on_state = 0;
17511708Sstevel envp->ps_ver = 0;
17521708Sstevel } else
17531708Sstevel if (dev_presence == FRU_PRESENT &&
17547656SSherry.Moore@Sun.COM envp->ps_present == FRU_NOT_PRESENT) {
1755*11311SSurya.Prakki@Sun.COM (void) pcf8574_init_chip(unitp, 0);
17561708Sstevel }
17571708Sstevel envp->ps_present = dev_presence;
17581708Sstevel unitp->poll_event = POLLIN;
17591708Sstevel pollwakeup(&unitp->poll, POLLIN);
17601708Sstevel break;
17611708Sstevel }
17621708Sstevel case PCF8574_TYPE_FANTRAY: {
17631708Sstevel envctrl_fantray_t *envp;
17641708Sstevel
17651708Sstevel envp = (envctrl_fantray_t *)unitp->envctrl_kstat;
17661708Sstevel
17671708Sstevel if (dev_presence == FRU_NOT_PRESENT) {
17681708Sstevel envp->fan_ok = 0;
17691708Sstevel envp->fanspeed = PCF8574_FAN_SPEED60;
17701708Sstevel envp->fan_ver = 0;
17711708Sstevel } else
17721708Sstevel if (dev_presence == FRU_PRESENT &&
17737656SSherry.Moore@Sun.COM envp->fan_present == FRU_NOT_PRESENT) {
1774*11311SSurya.Prakki@Sun.COM (void) pcf8574_init_chip(unitp, 0);
17751708Sstevel }
17761708Sstevel envp->fan_present = dev_presence;
17771708Sstevel unitp->poll_event = POLLIN;
17781708Sstevel pollwakeup(&unitp->poll, POLLIN);
17791708Sstevel break;
17801708Sstevel }
17811708Sstevel }
17821708Sstevel }
17831708Sstevel
17841708Sstevel /*
17851708Sstevel * Initializes the chip after attach or after being inserted.
17861708Sstevel * intron = 0 => disable interrupt.
17871708Sstevel * intron = 1 => read register, enable interrupt if no fault.
17881708Sstevel */
17891708Sstevel
17901708Sstevel static int
pcf8574_init_chip(struct pcf8574_unit * unitp,int intron)17911708Sstevel pcf8574_init_chip(struct pcf8574_unit *unitp, int intron)
17921708Sstevel {
17931708Sstevel int ret = I2C_SUCCESS;
17941708Sstevel i2c_transfer_t *tp = unitp->i2c_tran;
17951708Sstevel uint8_t value = 0;
17961708Sstevel boolean_t device_faulty = B_FALSE; /* true is faulty */
17971708Sstevel
17981708Sstevel if (unitp->pcf8574_type != PCF8574_TYPE_PWRSUPP &&
17997656SSherry.Moore@Sun.COM unitp->pcf8574_type != PCF8574_TYPE_FANTRAY) {
18001708Sstevel return (ret);
18011708Sstevel }
18021708Sstevel switch (unitp->pcf8574_type) {
18031708Sstevel case PCF8574_TYPE_PWRSUPP:
18041708Sstevel tp->i2c_wbuf[0] = PCF8574_PS_DEFAULT;
18051708Sstevel
18061708Sstevel break;
18071708Sstevel case PCF8574_TYPE_FANTRAY:
18081708Sstevel tp->i2c_wbuf[0] = PCF8574_FAN_DEFAULT;
18091708Sstevel
18101708Sstevel break;
18111708Sstevel default:
18121708Sstevel break;
18131708Sstevel }
18141708Sstevel
18151708Sstevel /*
18161708Sstevel * First, read the device. If the device is faulty, it does
18171708Sstevel * not make sense to enable the interrupt, so in this case
18181708Sstevel * keep interrupt maskked inspite of what "intron" says.
18191708Sstevel */
18201708Sstevel
18211708Sstevel tp->i2c_wlen = 0;
18221708Sstevel tp->i2c_rlen = 1;
18231708Sstevel tp->i2c_flags = I2C_RD;
18241708Sstevel
18251708Sstevel unitp->i2c_status = ret = nct_i2c_transfer(unitp->pcf8574_hdl, tp);
18261708Sstevel
18271708Sstevel if (ret != I2C_SUCCESS) {
18281708Sstevel return (ret);
18291708Sstevel }
18301708Sstevel
18311708Sstevel value = tp->i2c_rbuf[0];
18321708Sstevel
18331708Sstevel switch (unitp->pcf8574_type) {
18341708Sstevel case PCF8574_TYPE_PWRSUPP:
18351708Sstevel {
18361708Sstevel envctrl_pwrsupp_t *envp =
18377656SSherry.Moore@Sun.COM (envctrl_pwrsupp_t *)unitp->envctrl_kstat;
18381708Sstevel
18391708Sstevel envp->ps_ok = PCF8574_PS_FAULT(value);
18401708Sstevel envp->temp_ok = PCF8574_PS_TEMPOK(value);
18411708Sstevel envp->psfan_ok = PCF8574_PS_FANOK(value);
18421708Sstevel envp->on_state = PCF8574_PS_ONOFF(value);
18431708Sstevel envp->ps_ver = PCF8574_PS_TYPE(value);
18441708Sstevel
18451708Sstevel if (envp->ps_ok || envp->temp_ok ||
18467656SSherry.Moore@Sun.COM envp->psfan_ok || envp->on_state)
18471708Sstevel device_faulty = B_TRUE;
18481708Sstevel
18491708Sstevel break;
18501708Sstevel }
18511708Sstevel case PCF8574_TYPE_FANTRAY:
18521708Sstevel {
18531708Sstevel envctrl_fantray_t *envp =
18547656SSherry.Moore@Sun.COM (envctrl_fantray_t *)unitp->envctrl_kstat;
18551708Sstevel
18561708Sstevel envp->fan_ver = PCF8574_FAN_TYPE(value);
18571708Sstevel envp->fan_ok = PCF8574_FAN_FAULT(value);
18581708Sstevel envp->fanspeed = PCF8574_FAN_FANSPD(value);
18591708Sstevel
18601708Sstevel if (!envp->fan_ok)
18611708Sstevel device_faulty = B_TRUE; /* remember, 0 is faulty */
18621708Sstevel
18631708Sstevel break;
18641708Sstevel }
18651708Sstevel default:
18661708Sstevel break;
18671708Sstevel }
18681708Sstevel /*
18691708Sstevel * Mask interrupt, if intron = 0.
18701708Sstevel */
18711708Sstevel if (!intron || device_faulty == B_TRUE) {
18721708Sstevel tp->i2c_wbuf[0] |= PCF8574_INTRMASK_BIT;
18731708Sstevel }
18741708Sstevel
18751708Sstevel tp->i2c_wlen = 1;
18761708Sstevel tp->i2c_rlen = 0;
18771708Sstevel tp->i2c_flags = I2C_WR;
18781708Sstevel
18791708Sstevel unitp->i2c_status = nct_i2c_transfer(unitp->pcf8574_hdl, tp);
18801708Sstevel
18811708Sstevel return (unitp->i2c_status);
18821708Sstevel }
1883