/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * i2bsc.c is the nexus driver i2c traffic against devices hidden behind the * Blade Support Chip (BSC). It supports both interrupt and polled * mode operation, but defaults to interrupt. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * static function declarations */ static void i2bsc_resume(dev_info_t *dip); static void i2bsc_suspend(dev_info_t *dip); static int i2bsc_bus_ctl(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t op, void *arg, void *result); static void i2bsc_acquire(i2bsc_t *, dev_info_t *dip, i2c_transfer_t *tp); static void i2bsc_release(i2bsc_t *); static int i2bsc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); static int i2bsc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); static int i2bsc_open(dev_t *devp, int flag, int otyp, cred_t *cred_p); static int i2bsc_close(dev_t dev, int flag, int otyp, cred_t *cred_p); static int i2bsc_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); static int i2bsc_initchild(dev_info_t *dip, dev_info_t *cdip); static int i2bsc_uninitchild(dev_info_t *dip, dev_info_t *cdip); static int i2bsc_setup_regs(i2bsc_t *); static void i2bsc_start_session(i2bsc_t *); static void i2bsc_fail_session(i2bsc_t *); static int i2bsc_end_session(i2bsc_t *); static void i2bsc_free_regs(i2bsc_t *); static int i2bsc_reportdev(dev_info_t *dip, dev_info_t *rdip); int i2bsc_transfer(dev_info_t *dip, i2c_transfer_t *tp); static void i2bsc_trace(i2bsc_t *, char, const char *, const char *, ...); static int i2bsc_notify_max_transfer_size(i2bsc_t *); static int i2bsc_discover_capability(i2bsc_t *); static void i2bsc_put8(i2bsc_t *, uint8_t, uint8_t, uint8_t); static uint8_t i2bsc_get8(i2bsc_t *, uint8_t, uint8_t); static int i2bsc_safe_upload(i2bsc_t *, i2c_transfer_t *); static boolean_t i2bsc_is_firmware_broken(i2bsc_t *); static struct bus_ops i2bsc_busops = { BUSO_REV, nullbusmap, /* bus_map */ NULL, /* bus_get_intrspec */ NULL, /* bus_add_intrspec */ NULL, /* bus_remove_intrspec */ NULL, /* bus_map_fault */ ddi_no_dma_map, /* bus_dma_map */ ddi_no_dma_allochdl, /* bus_dma_allochdl */ ddi_no_dma_freehdl, /* bus_dma_freehdl */ ddi_no_dma_bindhdl, /* bus_dma_bindhdl */ ddi_no_dma_unbindhdl, /* bus_unbindhdl */ ddi_no_dma_flush, /* bus_dma_flush */ ddi_no_dma_win, /* bus_dma_win */ ddi_no_dma_mctl, /* bus_dma_ctl */ i2bsc_bus_ctl, /* bus_ctl */ ddi_bus_prop_op, /* bus_prop_op */ NULL, /* bus_get_eventcookie */ NULL, /* bus_add_eventcall */ NULL, /* bus_remove_eventcall */ NULL, /* bus_post_event */ 0, /* bus_intr_ctl */ 0, /* bus_config */ 0, /* bus_unconfig */ 0, /* bus_fm_init */ 0, /* bus_fm_fini */ 0, /* bus_fm_access_enter */ 0, /* bus_fm_access_exit */ 0, /* bus_power */ i_ddi_intr_ops /* bus_intr_op */ }; struct cb_ops i2bsc_cb_ops = { i2bsc_open, /* open */ i2bsc_close, /* close */ nodev, /* strategy */ nodev, /* print */ nodev, /* dump */ nodev, /* read */ nodev, /* write */ i2bsc_ioctl, /* ioctl */ nodev, /* devmap */ nodev, /* mmap */ nodev, /* segmap */ nochpoll, /* poll */ ddi_prop_op, /* cb_prop_op */ 0, /* streamtab */ D_MP | D_NEW /* Driver compatibility flag */ }; static struct dev_ops i2bsc_ops = { DEVO_REV, 0, ddi_getinfo_1to1, nulldev, nulldev, i2bsc_attach, i2bsc_detach, nodev, &i2bsc_cb_ops, &i2bsc_busops }; #ifdef DEBUG #define I2BSC_VERSION_STRING "i2bsc driver - Debug v%I%" #else #define I2BSC_VERSION_STRING "i2bsc driver v%I%" #endif static struct modldrv modldrv = { &mod_driverops, /* Type of module. This one is a driver */ I2BSC_VERSION_STRING, /* Name of the module. */ &i2bsc_ops, /* driver ops */ }; static struct modlinkage modlinkage = { MODREV_1, &modldrv, NULL }; /* * i2bsc soft state */ static void *i2bsc_state; i2c_nexus_reg_t i2bsc_regvec = { I2C_NEXUS_REV, i2bsc_transfer, }; int _init(void) { int status; status = ddi_soft_state_init(&i2bsc_state, sizeof (i2bsc_t), I2BSC_INITIAL_SOFT_SPACE); if (status != 0) { return (status); } if ((status = mod_install(&modlinkage)) != 0) { ddi_soft_state_fini(&i2bsc_state); } return (status); } int _fini(void) { int status; if ((status = mod_remove(&modlinkage)) == 0) { ddi_soft_state_fini(&i2bsc_state); } return (status); } /* * The loadable-module _info(9E) entry point */ int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } static void i2bsc_dodetach(dev_info_t *dip) { i2bsc_t *i2c; int instance = ddi_get_instance(dip); i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, instance); if ((i2c->i2bsc_attachflags & IMUTEX) != 0) { mutex_destroy(&i2c->i2bsc_imutex); cv_destroy(&i2c->i2bsc_icv); } if ((i2c->i2bsc_attachflags & SETUP_REGS) != 0) { i2bsc_free_regs(i2c); } if ((i2c->i2bsc_attachflags & NEXUS_REGISTER) != 0) { i2c_nexus_unregister(dip); } if ((i2c->i2bsc_attachflags & MINOR_NODE) != 0) { ddi_remove_minor_node(dip, NULL); } ddi_soft_state_free(i2bsc_state, instance); } static int i2bsc_doattach(dev_info_t *dip) { i2bsc_t *i2c; int instance = ddi_get_instance(dip); /* * Allocate soft state structure. */ if (ddi_soft_state_zalloc(i2bsc_state, instance) != DDI_SUCCESS) { return (DDI_FAILURE); } i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, instance); i2c->majornum = ddi_driver_major(dip); i2c->minornum = instance; i2c->i2bsc_dip = dip; i2c->debug = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "debug", 0); (void) snprintf(i2c->i2bsc_name, sizeof (i2c->i2bsc_name), "%s_%d", ddi_node_name(dip), instance); if (i2bsc_setup_regs(i2c) != DDI_SUCCESS) { goto bad; } i2c->i2bsc_attachflags |= SETUP_REGS; mutex_init(&i2c->i2bsc_imutex, NULL, MUTEX_DRIVER, (void *) 0); cv_init(&i2c->i2bsc_icv, NULL, CV_DRIVER, NULL); i2c->i2bsc_attachflags |= IMUTEX; i2c_nexus_register(dip, &i2bsc_regvec); i2c->i2bsc_attachflags |= NEXUS_REGISTER; if (ddi_create_minor_node(dip, "devctl", S_IFCHR, instance, DDI_NT_NEXUS, 0) == DDI_FAILURE) { cmn_err(CE_WARN, "%s ddi_create_minor_node failed", i2c->i2bsc_name); goto bad; } i2c->i2bsc_attachflags |= MINOR_NODE; /* * Now actually start talking to the microcontroller. The first * thing to check is whether the firmware is broken. */ if (i2bsc_is_firmware_broken(i2c)) { cmn_err(CE_WARN, "Underlying BSC hardware not communicating;" " shutting down my i2c services"); goto bad; } i2c->i2bsc_attachflags |= FIRMWARE_ALIVE; /* * Now see if the BSC chip supports the i2c service we rely upon. */ (void) i2bsc_discover_capability(i2c); if (i2bsc_notify_max_transfer_size(i2c) == DDI_SUCCESS) i2c->i2bsc_attachflags |= TRANSFER_SZ; i2bsc_trace(i2c, 'A', "i2bsc_doattach", "attachflags %d", i2c->i2bsc_attachflags); return (DDI_SUCCESS); bad: i2bsc_dodetach(dip); return (DDI_FAILURE); } static int i2bsc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { switch (cmd) { case DDI_ATTACH: return (i2bsc_doattach(dip)); case DDI_RESUME: i2bsc_resume(dip); return (DDI_SUCCESS); default: return (DDI_FAILURE); } } static int i2bsc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { switch (cmd) { case DDI_DETACH: i2bsc_dodetach(dip); return (DDI_SUCCESS); case DDI_SUSPEND: i2bsc_suspend(dip); return (DDI_SUCCESS); default: return (DDI_FAILURE); } } /*ARGSUSED*/ static int i2bsc_open(dev_t *devp, int flag, int otyp, cred_t *cred_p) { int instance; i2bsc_t *i2c; /* * Make sure the open is for the right file type */ if (otyp != OTYP_CHR) return (EINVAL); instance = getminor(*devp); i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, instance); if (i2c == NULL) return (ENXIO); /* * Enforce exclusive access */ mutex_enter(&i2c->i2bsc_imutex); if (i2c->i2bsc_open) { mutex_exit(&i2c->i2bsc_imutex); return (EBUSY); } else i2c->i2bsc_open = 1; mutex_exit(&i2c->i2bsc_imutex); return (0); } /*ARGSUSED*/ static int i2bsc_close(dev_t dev, int flag, int otyp, cred_t *cred_p) { int instance; i2bsc_t *i2c; /* * Make sure the close is for the right file type */ if (otyp != OTYP_CHR) return (EINVAL); instance = getminor(dev); i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, instance); if (i2c == NULL) return (ENXIO); mutex_enter(&i2c->i2bsc_imutex); i2c->i2bsc_open = 0; mutex_exit(&i2c->i2bsc_imutex); return (0); } /*ARGSUSED*/ static int i2bsc_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { i2bsc_t *i2c; dev_info_t *self; dev_info_t *child; struct devctl_iocdata *dcp; int rv; i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, getminor(dev)); if (i2c == NULL) return (ENXIO); self = (dev_info_t *)i2c->i2bsc_dip; /* * read devctl ioctl data */ if (ndi_dc_allochdl((void *)arg, &dcp) != NDI_SUCCESS) { return (EFAULT); } switch (cmd) { case DEVCTL_BUS_DEV_CREATE: rv = ndi_dc_devi_create(dcp, self, 0, NULL); break; case DEVCTL_DEVICE_REMOVE: if (ndi_dc_getname(dcp) == NULL || ndi_dc_getaddr(dcp) == NULL) { rv = EINVAL; break; } /* * lookup and hold child device */ child = ndi_devi_find(self, ndi_dc_getname(dcp), ndi_dc_getaddr(dcp)); if (child == NULL) { rv = ENXIO; break; } if ((rv = ndi_devi_offline(child, NDI_DEVI_REMOVE)) != NDI_SUCCESS) { rv = (rv == NDI_BUSY) ? EBUSY : EIO; } break; default: rv = ENOTSUP; } ndi_dc_freehdl(dcp); return (rv); } static int i2bsc_bus_ctl(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t op, void *arg, void *result) { i2bsc_t *i2c; int instance = ddi_get_instance(dip); i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, instance); i2bsc_trace(i2c, 'A', "i2bsc_bus_ctl", "dip/rdip,op/arg" " %p/%p,%d/%p", dip, rdip, (int)op, arg); switch (op) { case DDI_CTLOPS_INITCHILD: return (i2bsc_initchild(dip, (dev_info_t *)arg)); case DDI_CTLOPS_UNINITCHILD: return (i2bsc_uninitchild(dip, (dev_info_t *)arg)); case DDI_CTLOPS_REPORTDEV: return (i2bsc_reportdev(dip, rdip)); case DDI_CTLOPS_DMAPMAPC: case DDI_CTLOPS_POKE: case DDI_CTLOPS_PEEK: case DDI_CTLOPS_IOMIN: case DDI_CTLOPS_REPORTINT: case DDI_CTLOPS_SIDDEV: case DDI_CTLOPS_SLAVEONLY: case DDI_CTLOPS_AFFINITY: case DDI_CTLOPS_PTOB: case DDI_CTLOPS_BTOP: case DDI_CTLOPS_BTOPR: case DDI_CTLOPS_DVMAPAGESIZE: return (DDI_FAILURE); default: return (ddi_ctlops(dip, rdip, op, arg, result)); } } /* * i2bsc_suspend() is called before the system suspends. Existing * transfer in progress or waiting will complete, but new transfers are * effectively blocked by "acquiring" the bus. */ static void i2bsc_suspend(dev_info_t *dip) { i2bsc_t *i2c; int instance; instance = ddi_get_instance(dip); i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, instance); i2bsc_acquire(i2c, NULL, NULL); } /* * i2bsc_resume() is called when the system resumes from CPR. It releases * the hold that was placed on the i2c bus, which allows any real * transfers to continue. */ static void i2bsc_resume(dev_info_t *dip) { i2bsc_t *i2c; int instance; instance = ddi_get_instance(dip); i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, instance); i2bsc_release(i2c); } /* * i2bsc_acquire() is called by a thread wishing to "own" the I2C bus. * It should not be held across multiple transfers. */ static void i2bsc_acquire(i2bsc_t *i2c, dev_info_t *dip, i2c_transfer_t *tp) { mutex_enter(&i2c->i2bsc_imutex); while (i2c->i2bsc_busy) { cv_wait(&i2c->i2bsc_icv, &i2c->i2bsc_imutex); } i2c->i2bsc_busy = 1; i2c->i2bsc_cur_tran = tp; i2c->i2bsc_cur_dip = dip; mutex_exit(&i2c->i2bsc_imutex); } /* * i2bsc_release() is called to release a hold made by i2bsc_acquire(). */ static void i2bsc_release(i2bsc_t *i2c) { mutex_enter(&i2c->i2bsc_imutex); i2c->i2bsc_busy = 0; i2c->i2bsc_cur_tran = NULL; cv_signal(&i2c->i2bsc_icv); mutex_exit(&i2c->i2bsc_imutex); } static int i2bsc_initchild(dev_info_t *dip, dev_info_t *cdip) { i2bsc_t *i2c; int32_t address_cells; int len; int32_t regs[3]; int err; i2bsc_ppvt_t *ppvt; char name[30]; i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, ddi_get_instance(dip)); i2bsc_trace(i2c, 'A', "i2bsc_initchild", "dip/cdip %p/%p", dip, cdip); ppvt = kmem_alloc(sizeof (i2bsc_ppvt_t), KM_SLEEP); len = sizeof (address_cells); err = ddi_getlongprop_buf(DDI_DEV_T_ANY, cdip, DDI_PROP_CANSLEEP, "#address-cells", (caddr_t)&address_cells, &len); if (err != DDI_PROP_SUCCESS || len != sizeof (address_cells)) { return (DDI_FAILURE); } len = sizeof (regs); err = ddi_getlongprop_buf(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP, "reg", (caddr_t)regs, &len); if (err != DDI_PROP_SUCCESS) return (DDI_FAILURE); if (address_cells == 1) { ppvt->i2bsc_ppvt_bus = I2BSC_DEFAULT_BUS; ppvt->i2bsc_ppvt_addr = regs[0]; (void) sprintf(name, "%x", regs[0]); i2bsc_trace(i2c, 'A', "i2bsc_initchild", "#address-cells = 1" " regs[0] = %d", regs[0]); } else if (address_cells == 2) { ppvt->i2bsc_ppvt_bus = regs[0]; ppvt->i2bsc_ppvt_addr = regs[1]; (void) sprintf(name, "%x,%x", regs[0], regs[1]); i2bsc_trace(i2c, 'A', "i2bsc_initchild", "#address-cells = 2" " regs[0] = %d, regs[1] = %d", regs[0], regs[1]); } else { return (DDI_FAILURE); } /* * Attach the parent's private data structure to the child's devinfo * node, and store the child's address on the nexus in the child's * devinfo node. */ ddi_set_parent_data(cdip, ppvt); ddi_set_name_addr(cdip, name); i2bsc_trace(i2c, 'A', "i2bsc_initchild", "success(%s)", ddi_node_name(cdip)); return (DDI_SUCCESS); } static int i2bsc_uninitchild(dev_info_t *dip, dev_info_t *cdip) { i2bsc_t *i2c; i2bsc_ppvt_t *ppvt; i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, ddi_get_instance(dip)); i2bsc_trace(i2c, 'D', "i2bsc_uninitchild", "dip/cdip %p/%p", dip, cdip); ppvt = ddi_get_parent_data(cdip); kmem_free(ppvt, sizeof (i2bsc_ppvt_t)); ddi_set_parent_data(cdip, NULL); ddi_set_name_addr(cdip, NULL); i2bsc_trace(i2c, 'D', "i2bsc_uninitchild", "success(%s)", ddi_node_name(cdip)); return (DDI_SUCCESS); } /* * i2bsc_setup_regs() is called to map in registers specific to * the i2bsc. */ static int i2bsc_setup_regs(i2bsc_t *i2c) { int nregs; i2c->bscbus_attr.devacc_attr_version = DDI_DEVICE_ATTR_V0; i2c->bscbus_attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC; i2c->bscbus_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC; if (ddi_dev_nregs(i2c->i2bsc_dip, &nregs) != DDI_SUCCESS) { return (DDI_FAILURE); } if (nregs < 1) { return (DDI_FAILURE); } if (ddi_regs_map_setup(i2c->i2bsc_dip, 0, (caddr_t *)&i2c->bscbus_regs, 0, 0, &i2c->bscbus_attr, &i2c->bscbus_handle) != DDI_SUCCESS) { return (DDI_FAILURE); } return (DDI_SUCCESS); } /* * i2bsc_free_regs() frees any registers previously * allocated. */ static void i2bsc_free_regs(i2bsc_t *i2c) { if (i2c->bscbus_regs != NULL) { ddi_regs_map_free(&i2c->bscbus_handle); } } /*ARGSUSED*/ static int i2bsc_reportdev(dev_info_t *dip, dev_info_t *rdip) { if (rdip == (dev_info_t *)0) return (DDI_FAILURE); cmn_err(CE_CONT, "?i2bsc-device: %s@%s, %s%d\n", ddi_node_name(rdip), ddi_get_name_addr(rdip), ddi_driver_name(rdip), ddi_get_instance(rdip)); return (DDI_SUCCESS); } /* * I/O Functions * * i2bsc_{put,get}8_once are wrapper functions to ddi_{get,put}8. * i2bsc_{put,get}8 are equivalent functions but with retry code. * i2bsc_bscbus_state determines underlying bus error status. * i2bsc_clear_acc_fault clears the underlying bus error status. * * I/O Flags * * bscbus_fault - Error register in underlying bus for last IO operation. * session_failure - Set by any failed IO command. This is a sticky flag * reset explicitly using i2bsc_start_session * * Session Management * * i2bsc_{start,end}_session need to be used to detect an error across multiple * gets/puts rather than having to test for an error on each get/put. */ static int i2bsc_bscbus_state(i2bsc_t *i2c) { uint32_t retval; retval = ddi_get32(i2c->bscbus_handle, (uint32_t *)I2BSC_NEXUS_ADDR(i2c, EBUS_CMD_SPACE_GENERIC, LOMBUS_FAULT_REG)); i2c->bscbus_fault = retval; return ((retval == 0) ? DDI_SUCCESS : DDI_FAILURE); } static void i2bsc_clear_acc_fault(i2bsc_t *i2c) { i2bsc_trace(i2c, '@', "i2bsc_clear_acc_fault", "clearing acc fault"); ddi_put32(i2c->bscbus_handle, (uint32_t *)I2BSC_NEXUS_ADDR(i2c, EBUS_CMD_SPACE_GENERIC, LOMBUS_FAULT_REG), 0); } static void i2bsc_start_session(i2bsc_t *i2c) { i2bsc_trace(i2c, 'S', "i2bsc_start_session", "session started"); i2c->bscbus_session_failure = 0; } static void i2bsc_fail_session(i2bsc_t *i2c) { i2bsc_trace(i2c, 'S', "i2bsc_fail_session", "session failed"); i2c->bscbus_session_failure = 1; } static int i2bsc_end_session(i2bsc_t *i2c) { /* * The ONLY way to get the session status is to end the session. * If clients of the session interface ever wanted the status mid-way * then they are really working with multiple contigious sessions. */ i2bsc_trace(i2c, 'S', "i2bsc_end_session", "session ended with %d", i2c->bscbus_session_failure); return ((i2c->bscbus_session_failure) ? DDI_FAILURE : DDI_SUCCESS); } static boolean_t i2bsc_is_firmware_broken(i2bsc_t *i2c) { int i; int niterations = I2BSC_SHORT_RETRY_LIMIT; i2bsc_trace(i2c, 'A', "i2bsc_is_firmware_broken", "called"); for (i = 0; i < niterations; i++) { (void) ddi_get8(i2c->bscbus_handle, I2BSC_NEXUS_ADDR(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_RESULT)); if (i2bsc_bscbus_state(i2c) != DDI_SUCCESS) { i2bsc_clear_acc_fault(i2c); continue; } else { /* * Firmware communication succeeded. */ i2bsc_trace(i2c, 'A', "i2bsc_is_firmware_broken", "firmware communications okay"); return (B_FALSE); } } /* * Firmware is not communicative. Some possible causes : * Broken hardware * BSC held in reset * Corrupt BSC image * OBP incompatiblity preventing drivers loading properly */ i2bsc_trace(i2c, 'A', "i2bsc_is_firmware_broken", "%d read fails", niterations); return (B_TRUE); } static void i2bsc_put8(i2bsc_t *i2c, uint8_t space, uint8_t index, uint8_t value) { int retryable = I2BSC_RETRY_LIMIT; i2bsc_trace(i2c, '@', "i2bsc_put8", "(space,index)<-val (%d,%d)<-%d", space, index, value); i2bsc_clear_acc_fault(i2c); /* * If a session failure has already occurred, reduce the level of * retries to a minimum. This is a optimization of the failure * recovery strategy. */ if (i2c->bscbus_session_failure) retryable = 1; while (retryable--) { ddi_put8(i2c->bscbus_handle, I2BSC_NEXUS_ADDR(i2c, space, index), value); if (i2bsc_bscbus_state(i2c) != DDI_SUCCESS) { i2bsc_clear_acc_fault(i2c); } else break; } if (i2bsc_bscbus_state(i2c) != DDI_SUCCESS) i2bsc_fail_session(i2c); i2bsc_trace(i2c, '@', "i2bsc_put8", "tried %d time(s)", I2BSC_RETRY_LIMIT - retryable); } static uint8_t i2bsc_get8(i2bsc_t *i2c, uint8_t space, uint8_t index) { uint8_t value; int retryable = I2BSC_RETRY_LIMIT; i2bsc_clear_acc_fault(i2c); /* * If a session failure has already occurred, reduce the level of * retries to a minimum. This is a optimization of the failure * recovery strategy. */ if (i2c->bscbus_session_failure) retryable = 1; while (retryable--) { value = ddi_get8(i2c->bscbus_handle, I2BSC_NEXUS_ADDR(i2c, space, index)); if (i2bsc_bscbus_state(i2c) != DDI_SUCCESS) { i2bsc_clear_acc_fault(i2c); } else break; } if (i2bsc_bscbus_state(i2c) != DDI_SUCCESS) i2bsc_fail_session(i2c); i2bsc_trace(i2c, '@', "i2bsc_get8", "tried %d time(s)", I2BSC_RETRY_LIMIT - retryable); i2bsc_trace(i2c, '@', "i2bsc_get8", "(space,index)->val (%d,%d)->%d", space, index, value); return (value); } static void i2bsc_put8_once(i2bsc_t *i2c, uint8_t space, uint8_t index, uint8_t value) { i2bsc_trace(i2c, '@', "i2bsc_put8_once", "(space,index)<-val (%d,%d)<-%d", space, index, value); i2bsc_clear_acc_fault(i2c); ddi_put8(i2c->bscbus_handle, I2BSC_NEXUS_ADDR(i2c, space, index), value); if (i2bsc_bscbus_state(i2c) != DDI_SUCCESS) i2bsc_fail_session(i2c); } static uint8_t i2bsc_get8_once(i2bsc_t *i2c, uint8_t space, uint8_t index) { uint8_t value; i2bsc_clear_acc_fault(i2c); value = ddi_get8(i2c->bscbus_handle, I2BSC_NEXUS_ADDR(i2c, space, index)); if (i2bsc_bscbus_state(i2c) != DDI_SUCCESS) i2bsc_fail_session(i2c); i2bsc_trace(i2c, '@', "i2bsc_get8_once", "(space,index)->val (%d,%d)->%d", space, index, value); return (value); } static int i2bsc_notify_max_transfer_size(i2bsc_t *i2c) { /* * If the underlying hardware does not support the i2c service and * we are not running in fake_mode, then we cannot set the * MAX_TRANSFER_SZ. */ if (i2c->i2c_proxy_support == 0) return (DDI_FAILURE); i2bsc_start_session(i2c); i2bsc_put8(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_MAX_TRANSFER_SZ, I2BSC_MAX_TRANSFER_SZ); if (i2bsc_end_session(i2c) != DDI_SUCCESS) return (DDI_FAILURE); return (DDI_SUCCESS); } /* * Discover if the microcontroller implements the I2C Proxy Service this * driver requires. If it does not, i2c transactions will abort with * I2C_FAILURE, unless fake_mode is being used. */ static int i2bsc_discover_capability(i2bsc_t *i2c) { i2bsc_start_session(i2c); i2c->i2c_proxy_support = i2bsc_get8(i2c, EBUS_CMD_SPACE_GENERIC, EBUS_IDX_CAP0); i2c->i2c_proxy_support &= EBUS_CAP0_I2C_PROXY; if (i2bsc_end_session(i2c) != DDI_SUCCESS) return (DDI_FAILURE); return (DDI_SUCCESS); } static int i2bsc_upload_preamble(i2bsc_t *i2c, i2c_transfer_t *tp) { i2bsc_ppvt_t *ppvt; int wr_rd; ppvt = ddi_get_parent_data(i2c->i2bsc_cur_dip); /* Get a lock on the i2c devices owned by the microcontroller */ i2bsc_put8(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_TRANSACTION_LOCK, 1); if (!i2bsc_get8(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_TRANSACTION_LOCK)) { /* * i2c client driver must timeout retry, NOT this nexus */ tp->i2c_result = I2C_INCOMPLETE; i2bsc_trace(i2c, 'U', "i2bsc_upload_preamble", "Couldn't get transaction lock"); return (tp->i2c_result); } i2bsc_put8(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_BUS_ADDRESS, ppvt->i2bsc_ppvt_bus); /* * The Solaris architecture for I2C uses 10-bit I2C addresses where * bit-0 is zero (the read/write bit). The microcontroller uses 7 bit * I2C addresses (read/write bit excluded). Hence we need to convert * the address by bit-shifting. */ i2bsc_put8(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_CLIENT_ADDRESS, ppvt->i2bsc_ppvt_addr >> 1); i2bsc_put8(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_TRANSFER_TYPE, tp->i2c_flags); /* * We have only one register used for data input and output. When * a WR_RD is issued, this means we want to do a Random-Access-Read. * First a series of bytes are written which define the address to * read from. In hardware this sets an address pointer. Then a series * of bytes are read. The read/write boundary tells you how many * bytes are to be written before reads will be issued. */ if (tp->i2c_flags == I2C_WR_RD) wr_rd = tp->i2c_wlen; else wr_rd = 0; i2bsc_put8(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_WR_RD_BOUNDARY, wr_rd); return (I2C_SUCCESS); } /* * Function i2bsc_upload * * Description This function runs the i2c transfer protocol down to the * microcontroller. Its goal is to be as reliable as possible. * This is achieved by making all the state-less aspects * re-tryable. For stateful aspects, we take care to ensure the * counters are decremented only when data transfer has been * successful. */ static int i2bsc_upload(i2bsc_t *i2c, i2c_transfer_t *tp) { int quota = I2BSC_MAX_TRANSFER_SZ; uint8_t res; int residual; /* * Total amount of data outstanding */ residual = tp->i2c_w_resid + tp->i2c_r_resid; /* * Anything in this session *could* be re-tried without side-effects. * Therefore, error exit codes are I2C_INCOMPLETE rather than * I2C_FAILURE. */ i2bsc_start_session(i2c); if (i2bsc_upload_preamble(i2c, tp) != I2C_SUCCESS) return (I2C_INCOMPLETE); if (i2bsc_end_session(i2c) != DDI_SUCCESS) return (I2C_INCOMPLETE); /* The writes done here are not retryable */ while (tp->i2c_w_resid && quota) { i2bsc_put8_once(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_DATA_INOUT, tp->i2c_wbuf[tp->i2c_wlen - tp->i2c_w_resid]); if (i2bsc_bscbus_state(i2c) == DDI_SUCCESS) { tp->i2c_w_resid--; quota--; residual--; } else { i2bsc_trace(i2c, 'T', "i2bsc_upload", "write failed"); return (tp->i2c_result = I2C_INCOMPLETE); } } /* The reads done here are not retryable */ while (tp->i2c_r_resid && quota) { tp->i2c_rbuf[tp->i2c_rlen - tp->i2c_r_resid] = i2bsc_get8_once(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_DATA_INOUT); if (i2bsc_bscbus_state(i2c) == DDI_SUCCESS) { tp->i2c_r_resid--; quota--; residual--; } else { i2bsc_trace(i2c, 'T', "i2bsc_upload", "read failed"); return (tp->i2c_result = I2C_INCOMPLETE); } } i2bsc_start_session(i2c); /* * A possible future enhancement would be to allow early breakout of the * loops seen above. In such circumstances, "residual" would be non- * zero. This may be useful if we want to support the interruption of * transfer part way through an i2c_transfer_t. */ i2bsc_put8(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_RESIDUAL_DATA, residual); res = i2bsc_get8(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_RESULT); if (i2bsc_end_session(i2c) != DDI_SUCCESS) return (tp->i2c_result = I2C_INCOMPLETE); switch (res) { case EBUS_I2C_SUCCESS: tp->i2c_result = I2C_SUCCESS; break; case EBUS_I2C_FAILURE: /* * This is rare but possible. A retry may still fix this * so lets allow that by returning I2C_INCOMPLETE. * "hifTxRing still contains 1 bytes" is reported by the * microcontroller when this return value is seen. */ i2bsc_trace(i2c, 'T', "i2bsc_upload", "EBUS_I2C_FAILURE" " but returning I2C_INCOMPLETE for possible re-try"); tp->i2c_result = I2C_INCOMPLETE; break; case EBUS_I2C_INCOMPLETE: tp->i2c_result = I2C_INCOMPLETE; break; default: tp->i2c_result = I2C_FAILURE; } return (tp->i2c_result); } /* * Function i2bsc_safe_upload * * Description This function is called "safe"-upload because it attempts to * do transaction re-tries for cases where state is not spoiled * by a transaction-level retry. */ static int i2bsc_safe_upload(i2bsc_t *i2c, i2c_transfer_t *tp) { int retryable = I2BSC_RETRY_LIMIT; int result; i2bsc_trace(i2c, 'T', "i2bsc_safe_upload", "Transaction %s", (tp->i2c_flags == I2C_WR_RD) ? "retryable" : "single-shot"); /* * The only re-tryable transaction type is I2C_WR_RD. If we don't * have this we can only use session-based recovery offered by * i2bsc_upload. */ if (tp->i2c_flags != I2C_WR_RD) return (i2bsc_upload(i2c, tp)); while (retryable--) { result = i2bsc_upload(i2c, tp); if (result == I2C_INCOMPLETE) { /* Have another go */ tp->i2c_r_resid = tp->i2c_rlen; tp->i2c_w_resid = tp->i2c_wlen; tp->i2c_result = I2C_SUCCESS; i2bsc_trace(i2c, 'T', "i2bsc_safe_upload", "Retried (%d)", I2BSC_RETRY_LIMIT - retryable); continue; } else { i2bsc_trace(i2c, 'T', "i2bsc_safe_upload", "Exiting while loop on result %d", result); return (result); } } i2bsc_trace(i2c, 'T', "i2bsc_safe_upload", "Exiting on %d", result); return (result); } /* * Function i2bsc_transfer * * Description This is the entry-point that clients use via the Solaris i2c * framework. It kicks off the servicing of i2c transfer requests. */ int i2bsc_transfer(dev_info_t *dip, i2c_transfer_t *tp) { i2bsc_t *i2c; i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, ddi_get_instance(ddi_get_parent(dip))); i2bsc_acquire(i2c, dip, tp); tp->i2c_r_resid = tp->i2c_rlen; tp->i2c_w_resid = tp->i2c_wlen; tp->i2c_result = I2C_SUCCESS; i2bsc_trace(i2c, 'T', "i2bsc_transfer", "Transaction i2c_version/flags" " %d/%d", tp->i2c_version, tp->i2c_flags); i2bsc_trace(i2c, 'T', "i2bsc_transfer", "Transaction buffer rlen/wlen" " %d/%d", tp->i2c_rlen, tp->i2c_wlen); i2bsc_trace(i2c, 'T', "i2bsc_transfer", "Transaction ptrs wbuf/rbuf" " %p/%p", tp->i2c_wbuf, tp->i2c_rbuf); if (i2c->i2c_proxy_support) (void) i2bsc_safe_upload(i2c, tp); else tp->i2c_result = I2C_FAILURE; i2bsc_trace(i2c, 'T', "i2bsc_transfer", "Residual writes/reads" " %d/%d", tp->i2c_w_resid, tp->i2c_r_resid); i2bsc_trace(i2c, 'T', "i2bsc_transfer", "i2c_result" " %d", tp->i2c_result); i2bsc_release(i2c); return (tp->i2c_result); } /* * General utility routines ... */ #ifdef DEBUG static void i2bsc_trace(i2bsc_t *ssp, char code, const char *caller, const char *fmt, ...) { char buf[256]; char *p; va_list va; if (ssp->debug & (1 << (code-'@'))) { p = buf; (void) snprintf(p, sizeof (buf) - (p - buf), "%s/%s: ", ssp->i2bsc_name, caller); p += strlen(p); va_start(va, fmt); (void) vsnprintf(p, sizeof (buf) - (p - buf), fmt, va); va_end(va); buf[sizeof (buf) - 1] = '\0'; (void) strlog(ssp->majornum, ssp->minornum, code, SL_TRACE, buf); } } #else /* DEBUG */ _NOTE(ARGSUSED(0)) static void i2bsc_trace(i2bsc_t *ssp, char code, const char *caller, const char *fmt, ...) { } #endif /* DEBUG */