xref: /illumos-gate/usr/src/uts/sun4u/io/i2c/nexus/smbus.c (revision 19397407)
17c478bd9Sstevel@tonic-gate /*
27c478bd9Sstevel@tonic-gate  * CDDL HEADER START
37c478bd9Sstevel@tonic-gate  *
47c478bd9Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
5ecd343b6Sarutz  * Common Development and Distribution License (the "License").
6ecd343b6Sarutz  * You may not use this file except in compliance with the License.
77c478bd9Sstevel@tonic-gate  *
87c478bd9Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
97c478bd9Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
107c478bd9Sstevel@tonic-gate  * See the License for the specific language governing permissions
117c478bd9Sstevel@tonic-gate  * and limitations under the License.
127c478bd9Sstevel@tonic-gate  *
137c478bd9Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
147c478bd9Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
157c478bd9Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
167c478bd9Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
177c478bd9Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
187c478bd9Sstevel@tonic-gate  *
197c478bd9Sstevel@tonic-gate  * CDDL HEADER END
207c478bd9Sstevel@tonic-gate  */
217c478bd9Sstevel@tonic-gate /*
22*19397407SSherry Moore  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
237c478bd9Sstevel@tonic-gate  * Use is subject to license terms.
247c478bd9Sstevel@tonic-gate  */
257c478bd9Sstevel@tonic-gate 
267c478bd9Sstevel@tonic-gate 
277c478bd9Sstevel@tonic-gate /*
287c478bd9Sstevel@tonic-gate  * This is the nexus driver for SMBUS devices.  It mostly does not use
297c478bd9Sstevel@tonic-gate  * the SMBUS protocol so that it fits better into the solaris i2c
307c478bd9Sstevel@tonic-gate  * framework.
317c478bd9Sstevel@tonic-gate  */
327c478bd9Sstevel@tonic-gate 
337c478bd9Sstevel@tonic-gate #include <sys/types.h>
347c478bd9Sstevel@tonic-gate #include <sys/conf.h>
357c478bd9Sstevel@tonic-gate #include <sys/file.h>
367c478bd9Sstevel@tonic-gate #include <sys/open.h>
377c478bd9Sstevel@tonic-gate #include <sys/ddi.h>
387c478bd9Sstevel@tonic-gate #include <sys/sunddi.h>
397c478bd9Sstevel@tonic-gate #include <sys/modctl.h>
407c478bd9Sstevel@tonic-gate #include <sys/stat.h>
417c478bd9Sstevel@tonic-gate #include <sys/kmem.h>
427c478bd9Sstevel@tonic-gate #include <sys/archsystm.h>
437c478bd9Sstevel@tonic-gate #include <sys/platform_module.h>
447c478bd9Sstevel@tonic-gate 
457c478bd9Sstevel@tonic-gate #include <sys/i2c/clients/i2c_client.h>
467c478bd9Sstevel@tonic-gate #include <sys/i2c/misc/i2c_svc.h>
477c478bd9Sstevel@tonic-gate #include <sys/i2c/misc/i2c_svc_impl.h>
487c478bd9Sstevel@tonic-gate #include <sys/i2c/nexus/smbus.h>
497c478bd9Sstevel@tonic-gate 
507c478bd9Sstevel@tonic-gate /*
517c478bd9Sstevel@tonic-gate  * static function declarations
527c478bd9Sstevel@tonic-gate  */
537c478bd9Sstevel@tonic-gate static uint_t smbus_intr_cmn(smbus_t *smbus, char *src);
547c478bd9Sstevel@tonic-gate static void smbus_intr_timeout(void *arg);
557c478bd9Sstevel@tonic-gate static void smbus_resume(dev_info_t *dip);
567c478bd9Sstevel@tonic-gate static void smbus_suspend(dev_info_t *dip);
577c478bd9Sstevel@tonic-gate static int smbus_bus_ctl(dev_info_t *dip, dev_info_t *rdip,
587c478bd9Sstevel@tonic-gate 	ddi_ctl_enum_t op, void *arg, void *result);
597c478bd9Sstevel@tonic-gate static  int smbus_acquire(smbus_t *, dev_info_t *dip,
607c478bd9Sstevel@tonic-gate 	i2c_transfer_t *tp);
617c478bd9Sstevel@tonic-gate static  void smbus_release(smbus_t *);
627c478bd9Sstevel@tonic-gate static int smbus_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
637c478bd9Sstevel@tonic-gate static int smbus_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
647c478bd9Sstevel@tonic-gate static void smbus_free_regs(smbus_t *smbus);
657c478bd9Sstevel@tonic-gate static int smbus_setup_regs(dev_info_t *dip, smbus_t *smbus);
667c478bd9Sstevel@tonic-gate static void smbus_reportdev(dev_info_t *dip, dev_info_t *rdip);
677c478bd9Sstevel@tonic-gate static void smbus_uninitchild(dev_info_t *cdip);
687c478bd9Sstevel@tonic-gate static int smbus_initchild(dev_info_t *cdip);
697c478bd9Sstevel@tonic-gate static int smbus_rd(smbus_t *smbus);
707c478bd9Sstevel@tonic-gate static int smbus_wr(smbus_t *smbus);
717c478bd9Sstevel@tonic-gate static void smbus_put(smbus_t *smbus, uint8_t reg, uint8_t data, uint8_t flags);
727c478bd9Sstevel@tonic-gate static uint8_t smbus_get(smbus_t *smbus, uint8_t reg);
737c478bd9Sstevel@tonic-gate static int smbus_dip_to_addr(dev_info_t *dip);
747c478bd9Sstevel@tonic-gate static uint_t smbus_intr(caddr_t arg);
757c478bd9Sstevel@tonic-gate static int smbus_switch(smbus_t *smbus);
767c478bd9Sstevel@tonic-gate 
777c478bd9Sstevel@tonic-gate static struct bus_ops smbus_busops = {
787c478bd9Sstevel@tonic-gate 	BUSO_REV,
797c478bd9Sstevel@tonic-gate 	nullbusmap,			/* bus_map */
807c478bd9Sstevel@tonic-gate 	NULL,				/* bus_get_intrspec */
817c478bd9Sstevel@tonic-gate 	NULL,				/* bus_add_intrspec */
827c478bd9Sstevel@tonic-gate 	NULL,				/* bus_remove_intrspec */
837c478bd9Sstevel@tonic-gate 	NULL,				/* bus_map_fault */
847c478bd9Sstevel@tonic-gate 	ddi_no_dma_map,			/* bus_dma_map */
857c478bd9Sstevel@tonic-gate 	ddi_no_dma_allochdl,		/* bus_dma_allochdl */
867c478bd9Sstevel@tonic-gate 	ddi_no_dma_freehdl,		/* bus_dma_freehdl */
877c478bd9Sstevel@tonic-gate 	ddi_no_dma_bindhdl,		/* bus_dma_bindhdl */
887c478bd9Sstevel@tonic-gate 	ddi_no_dma_unbindhdl,		/* bus_unbindhdl */
897c478bd9Sstevel@tonic-gate 	ddi_no_dma_flush,		/* bus_dma_flush */
907c478bd9Sstevel@tonic-gate 	ddi_no_dma_win,			/* bus_dma_win */
917c478bd9Sstevel@tonic-gate 	ddi_no_dma_mctl,		/* bus_dma_ctl */
927c478bd9Sstevel@tonic-gate 	smbus_bus_ctl,			/* bus_ctl */
937c478bd9Sstevel@tonic-gate 	ddi_bus_prop_op,		/* bus_prop_op */
947c478bd9Sstevel@tonic-gate 	NULL,				/* bus_get_eventcookie */
957c478bd9Sstevel@tonic-gate 	NULL,				/* bus_add_eventcall */
967c478bd9Sstevel@tonic-gate 	NULL,				/* bus_remove_eventcall */
977c478bd9Sstevel@tonic-gate 	NULL,				/* bus_post_event */
987c478bd9Sstevel@tonic-gate 	0,				/* bus_intr_ctl 	*/
997c478bd9Sstevel@tonic-gate 	0,				/* bus_config		*/
1007c478bd9Sstevel@tonic-gate 	0,				/* bus_unconfig		*/
1017c478bd9Sstevel@tonic-gate 	0,				/* bus_fm_init		*/
1027c478bd9Sstevel@tonic-gate 	0,				/* bus_fm_fini		*/
1037c478bd9Sstevel@tonic-gate 	0,				/* bus_fm_access_enter	*/
1047c478bd9Sstevel@tonic-gate 	0,				/* bus_fm_access_exit	*/
1057c478bd9Sstevel@tonic-gate 	0,				/* bus_power		*/
1067c478bd9Sstevel@tonic-gate 	i_ddi_intr_ops			/* bus_intr_op		*/
1077c478bd9Sstevel@tonic-gate };
1087c478bd9Sstevel@tonic-gate 
1097c478bd9Sstevel@tonic-gate struct cb_ops smbus_cb_ops = {
1107c478bd9Sstevel@tonic-gate 	nodev,			/* open */
1117c478bd9Sstevel@tonic-gate 	nodev,			/* close */
1127c478bd9Sstevel@tonic-gate 	nodev,			/* strategy */
1137c478bd9Sstevel@tonic-gate 	nodev,			/* print */
1147c478bd9Sstevel@tonic-gate 	nodev,			/* dump */
1157c478bd9Sstevel@tonic-gate 	nodev,			/* read */
1167c478bd9Sstevel@tonic-gate 	nodev,			/* write */
1177c478bd9Sstevel@tonic-gate 	nodev,			/* ioctl */
1187c478bd9Sstevel@tonic-gate 	nodev,			/* devmap */
1197c478bd9Sstevel@tonic-gate 	nodev,			/* mmap */
1207c478bd9Sstevel@tonic-gate 	nodev,			/* segmap */
1217c478bd9Sstevel@tonic-gate 	nochpoll,		/* poll */
1227c478bd9Sstevel@tonic-gate 	ddi_prop_op,		/* cb_prop_op */
1237c478bd9Sstevel@tonic-gate 	0,			/* streamtab  */
1247c478bd9Sstevel@tonic-gate 	D_MP | D_NEW		/* Driver compatibility flag */
1257c478bd9Sstevel@tonic-gate };
1267c478bd9Sstevel@tonic-gate 
1277c478bd9Sstevel@tonic-gate static struct dev_ops smbus_ops = {
1287c478bd9Sstevel@tonic-gate 	DEVO_REV,
1297c478bd9Sstevel@tonic-gate 	0,
1307c478bd9Sstevel@tonic-gate 	ddi_no_info,
1317c478bd9Sstevel@tonic-gate 	nulldev,
1327c478bd9Sstevel@tonic-gate 	nulldev,
1337c478bd9Sstevel@tonic-gate 	smbus_attach,
1347c478bd9Sstevel@tonic-gate 	smbus_detach,
1357c478bd9Sstevel@tonic-gate 	nodev,
1367c478bd9Sstevel@tonic-gate 	&smbus_cb_ops,
137*19397407SSherry Moore 	&smbus_busops,
138*19397407SSherry Moore 	NULL,
139*19397407SSherry Moore 	ddi_quiesce_not_supported,	/* devo_quiesce */
1407c478bd9Sstevel@tonic-gate };
1417c478bd9Sstevel@tonic-gate 
1427c478bd9Sstevel@tonic-gate static struct modldrv modldrv = {
1437c478bd9Sstevel@tonic-gate 	&mod_driverops, /* Type of module. This one is a driver */
144*19397407SSherry Moore 	"SMBUS nexus Driver",	/* Name of the module. */
1457c478bd9Sstevel@tonic-gate 	&smbus_ops,		/* driver ops */
1467c478bd9Sstevel@tonic-gate };
1477c478bd9Sstevel@tonic-gate 
1487c478bd9Sstevel@tonic-gate static struct modlinkage modlinkage = {
1497c478bd9Sstevel@tonic-gate 	MODREV_1,
1507c478bd9Sstevel@tonic-gate 	&modldrv,
1517c478bd9Sstevel@tonic-gate 	NULL
1527c478bd9Sstevel@tonic-gate };
1537c478bd9Sstevel@tonic-gate 
1547c478bd9Sstevel@tonic-gate /*
1557c478bd9Sstevel@tonic-gate  * Globals
1567c478bd9Sstevel@tonic-gate  */
1577c478bd9Sstevel@tonic-gate static void	*smbus_state;
1587c478bd9Sstevel@tonic-gate 
1597c478bd9Sstevel@tonic-gate static int intr_timeout = INTR_TIMEOUT;
1607c478bd9Sstevel@tonic-gate 
1617c478bd9Sstevel@tonic-gate /*
1627c478bd9Sstevel@tonic-gate  * The "interrupt-priorities" property is how a driver can specify a SPARC
1637c478bd9Sstevel@tonic-gate  * PIL level to associate with each of its interrupt properties.  Most
1647c478bd9Sstevel@tonic-gate  * self-identifying busses have a better mechanism for managing this, but I2C
1657c478bd9Sstevel@tonic-gate  * doesn't.
1667c478bd9Sstevel@tonic-gate  */
1677c478bd9Sstevel@tonic-gate int smbus_pil = SMBUS_PIL;
1687c478bd9Sstevel@tonic-gate 
1697c478bd9Sstevel@tonic-gate i2c_nexus_reg_t smbus_regvec = {
1707c478bd9Sstevel@tonic-gate 	I2C_NEXUS_REV,
1717c478bd9Sstevel@tonic-gate 	smbus_transfer,
1727c478bd9Sstevel@tonic-gate };
1737c478bd9Sstevel@tonic-gate 
1747c478bd9Sstevel@tonic-gate #ifdef DEBUG
1757c478bd9Sstevel@tonic-gate 
1767c478bd9Sstevel@tonic-gate static int smbus_print_lvl = 0;
1777c478bd9Sstevel@tonic-gate static char msg_buff[1024];
1787c478bd9Sstevel@tonic-gate static kmutex_t msg_buf_lock;
1797c478bd9Sstevel@tonic-gate 
1807c478bd9Sstevel@tonic-gate void
smbus_print(int flags,const char * fmt,...)1817c478bd9Sstevel@tonic-gate smbus_print(int flags, const char *fmt, ...)
1827c478bd9Sstevel@tonic-gate {
1837c478bd9Sstevel@tonic-gate 	if (flags & smbus_print_lvl) {
1847c478bd9Sstevel@tonic-gate 		va_list ap;
1857c478bd9Sstevel@tonic-gate 
1867c478bd9Sstevel@tonic-gate 		va_start(ap, fmt);
1877c478bd9Sstevel@tonic-gate 
1887c478bd9Sstevel@tonic-gate 		if (smbus_print_lvl & PRT_PROM) {
1897c478bd9Sstevel@tonic-gate 			prom_vprintf(fmt, ap);
1907c478bd9Sstevel@tonic-gate 		} else {
1917c478bd9Sstevel@tonic-gate 
1927c478bd9Sstevel@tonic-gate 			mutex_enter(&msg_buf_lock);
1937c478bd9Sstevel@tonic-gate 			(void) vsprintf(msg_buff, fmt, ap);
1947c478bd9Sstevel@tonic-gate 			if (smbus_print_lvl & PRT_BUFFONLY) {
1957c478bd9Sstevel@tonic-gate 				cmn_err(CE_CONT, "?%s", msg_buff);
1967c478bd9Sstevel@tonic-gate 			} else {
1977c478bd9Sstevel@tonic-gate 				cmn_err(CE_CONT, "%s", msg_buff);
1987c478bd9Sstevel@tonic-gate 			}
1997c478bd9Sstevel@tonic-gate 			mutex_exit(&msg_buf_lock);
2007c478bd9Sstevel@tonic-gate 		}
2017c478bd9Sstevel@tonic-gate 		va_end(ap);
2027c478bd9Sstevel@tonic-gate 	}
2037c478bd9Sstevel@tonic-gate }
2047c478bd9Sstevel@tonic-gate #endif /* DEBUG */
2057c478bd9Sstevel@tonic-gate 
2067c478bd9Sstevel@tonic-gate int
_init(void)2077c478bd9Sstevel@tonic-gate _init(void)
2087c478bd9Sstevel@tonic-gate {
2097c478bd9Sstevel@tonic-gate 	int status;
2107c478bd9Sstevel@tonic-gate 
2117c478bd9Sstevel@tonic-gate 	status = ddi_soft_state_init(&smbus_state, sizeof (smbus_t),
212*19397407SSherry Moore 	    1);
2137c478bd9Sstevel@tonic-gate 	if (status != 0) {
2147c478bd9Sstevel@tonic-gate 
2157c478bd9Sstevel@tonic-gate 		return (status);
2167c478bd9Sstevel@tonic-gate 	}
2177c478bd9Sstevel@tonic-gate 
2187c478bd9Sstevel@tonic-gate 	if ((status = mod_install(&modlinkage)) != 0) {
2197c478bd9Sstevel@tonic-gate 		ddi_soft_state_fini(&smbus_state);
2207c478bd9Sstevel@tonic-gate 	} else {
2217c478bd9Sstevel@tonic-gate #ifdef DEBUG
2227c478bd9Sstevel@tonic-gate 		mutex_init(&msg_buf_lock, NULL, MUTEX_DRIVER, NULL);
2237c478bd9Sstevel@tonic-gate #endif
2247c478bd9Sstevel@tonic-gate 	}
2257c478bd9Sstevel@tonic-gate 	return (status);
2267c478bd9Sstevel@tonic-gate }
2277c478bd9Sstevel@tonic-gate 
2287c478bd9Sstevel@tonic-gate int
_fini(void)2297c478bd9Sstevel@tonic-gate _fini(void)
2307c478bd9Sstevel@tonic-gate {
2317c478bd9Sstevel@tonic-gate 	int status;
2327c478bd9Sstevel@tonic-gate 
2337c478bd9Sstevel@tonic-gate 	if ((status = mod_remove(&modlinkage)) == 0) {
2347c478bd9Sstevel@tonic-gate 		ddi_soft_state_fini(&smbus_state);
2357c478bd9Sstevel@tonic-gate #ifdef DEBUG
2367c478bd9Sstevel@tonic-gate 		mutex_destroy(&msg_buf_lock);
2377c478bd9Sstevel@tonic-gate #endif
2387c478bd9Sstevel@tonic-gate 	}
2397c478bd9Sstevel@tonic-gate 
2407c478bd9Sstevel@tonic-gate 	return (status);
2417c478bd9Sstevel@tonic-gate }
2427c478bd9Sstevel@tonic-gate 
2437c478bd9Sstevel@tonic-gate /*
2447c478bd9Sstevel@tonic-gate  * The loadable-module _info(9E) entry point
2457c478bd9Sstevel@tonic-gate  */
2467c478bd9Sstevel@tonic-gate int
_info(struct modinfo * modinfop)2477c478bd9Sstevel@tonic-gate _info(struct modinfo *modinfop)
2487c478bd9Sstevel@tonic-gate {
2497c478bd9Sstevel@tonic-gate 	return (mod_info(&modlinkage, modinfop));
2507c478bd9Sstevel@tonic-gate }
2517c478bd9Sstevel@tonic-gate 
2527c478bd9Sstevel@tonic-gate static void
smbus_interrupts_on(smbus_t * smbus)2537c478bd9Sstevel@tonic-gate smbus_interrupts_on(smbus_t *smbus)
2547c478bd9Sstevel@tonic-gate {
2557c478bd9Sstevel@tonic-gate 	int src_enable;
2567c478bd9Sstevel@tonic-gate 
2577c478bd9Sstevel@tonic-gate 	src_enable = ddi_get32(smbus->smbus_confighandle,
2587c478bd9Sstevel@tonic-gate 	    (uint32_t *)&smbus->smbus_configregaddr[SMBUS_SRC_ENA]);
2597c478bd9Sstevel@tonic-gate 	src_enable |= SMBUS_SMI;
2607c478bd9Sstevel@tonic-gate 	ddi_put32(smbus->smbus_confighandle,
2617c478bd9Sstevel@tonic-gate 	    (uint32_t *)&smbus->smbus_configregaddr[SMBUS_SRC_ENA],
2627c478bd9Sstevel@tonic-gate 	    src_enable);
2637c478bd9Sstevel@tonic-gate 	(void) ddi_get32(smbus->smbus_confighandle,
2647c478bd9Sstevel@tonic-gate 	    (uint32_t *)&smbus->smbus_configregaddr[SMBUS_SRC_ENA]);
2657c478bd9Sstevel@tonic-gate }
2667c478bd9Sstevel@tonic-gate 
2677c478bd9Sstevel@tonic-gate static void
smbus_interrupts_off(smbus_t * smbus)2687c478bd9Sstevel@tonic-gate smbus_interrupts_off(smbus_t *smbus)
2697c478bd9Sstevel@tonic-gate {
2707c478bd9Sstevel@tonic-gate 	int src_enable;
2717c478bd9Sstevel@tonic-gate 
2727c478bd9Sstevel@tonic-gate 	src_enable = ddi_get32(smbus->smbus_confighandle,
2737c478bd9Sstevel@tonic-gate 	    (uint32_t *)&smbus->smbus_configregaddr[SMBUS_SRC_ENA]);
2747c478bd9Sstevel@tonic-gate 	src_enable &= ~SMBUS_SMI;
2757c478bd9Sstevel@tonic-gate 	ddi_put32(smbus->smbus_confighandle,
2767c478bd9Sstevel@tonic-gate 	    (uint32_t *)&smbus->smbus_configregaddr[SMBUS_SRC_ENA],
2777c478bd9Sstevel@tonic-gate 	    src_enable);
2787c478bd9Sstevel@tonic-gate 	(void) ddi_get32(smbus->smbus_confighandle,
2797c478bd9Sstevel@tonic-gate 	    (uint32_t *)&smbus->smbus_configregaddr[SMBUS_SRC_ENA]);
2807c478bd9Sstevel@tonic-gate }
2817c478bd9Sstevel@tonic-gate 
2827c478bd9Sstevel@tonic-gate static void
smbus_dodetach(dev_info_t * dip)2837c478bd9Sstevel@tonic-gate smbus_dodetach(dev_info_t *dip)
2847c478bd9Sstevel@tonic-gate {
2857c478bd9Sstevel@tonic-gate 	smbus_t *smbus;
2867c478bd9Sstevel@tonic-gate 	int instance = ddi_get_instance(dip);
2877c478bd9Sstevel@tonic-gate 
2887c478bd9Sstevel@tonic-gate 	smbus = ddi_get_soft_state(smbus_state, instance);
2897c478bd9Sstevel@tonic-gate 
2907c478bd9Sstevel@tonic-gate 	if (smbus == NULL) {
2917c478bd9Sstevel@tonic-gate 
2927c478bd9Sstevel@tonic-gate 		return;
2937c478bd9Sstevel@tonic-gate 	}
2947c478bd9Sstevel@tonic-gate 
2957c478bd9Sstevel@tonic-gate 	cv_destroy(&smbus->smbus_cv);
2967c478bd9Sstevel@tonic-gate 	mutex_destroy(&smbus->smbus_mutex);
2977c478bd9Sstevel@tonic-gate 
2987c478bd9Sstevel@tonic-gate 	if ((smbus->smbus_attachflags & INTERRUPT_PRI) != 0) {
2997c478bd9Sstevel@tonic-gate 		(void) ddi_prop_remove(DDI_DEV_T_NONE, dip,
3007c478bd9Sstevel@tonic-gate 		    "interrupt-priorities");
3017c478bd9Sstevel@tonic-gate 	}
3027c478bd9Sstevel@tonic-gate 
3037c478bd9Sstevel@tonic-gate 	smbus_free_regs(smbus);
3047c478bd9Sstevel@tonic-gate 
3057c478bd9Sstevel@tonic-gate 	if ((smbus->smbus_attachflags & NEXUS_REGISTER) != 0) {
3067c478bd9Sstevel@tonic-gate 		i2c_nexus_unregister(dip);
3077c478bd9Sstevel@tonic-gate 	}
3087c478bd9Sstevel@tonic-gate 	if ((smbus->smbus_attachflags & IMUTEX) != 0) {
3097c478bd9Sstevel@tonic-gate 		mutex_destroy(&smbus->smbus_imutex);
3107c478bd9Sstevel@tonic-gate 		cv_destroy(&smbus->smbus_icv);
3117c478bd9Sstevel@tonic-gate 	}
3127c478bd9Sstevel@tonic-gate 
3137c478bd9Sstevel@tonic-gate 	if (smbus->smbus_timeout != 0) {
3147c478bd9Sstevel@tonic-gate 		(void) untimeout(smbus->smbus_timeout);
3157c478bd9Sstevel@tonic-gate 	}
3167c478bd9Sstevel@tonic-gate 
3177c478bd9Sstevel@tonic-gate 	if ((smbus->smbus_attachflags & ADD_INTR) != 0) {
3187c478bd9Sstevel@tonic-gate 		ddi_remove_intr(dip, 0, smbus->smbus_icookie);
3197c478bd9Sstevel@tonic-gate 	}
3207c478bd9Sstevel@tonic-gate 
3217c478bd9Sstevel@tonic-gate 	ddi_soft_state_free(smbus_state, instance);
3227c478bd9Sstevel@tonic-gate }
3237c478bd9Sstevel@tonic-gate 
3247c478bd9Sstevel@tonic-gate static int
smbus_doattach(dev_info_t * dip)3257c478bd9Sstevel@tonic-gate smbus_doattach(dev_info_t *dip)
3267c478bd9Sstevel@tonic-gate {
3277c478bd9Sstevel@tonic-gate 	smbus_t *smbus;
3287c478bd9Sstevel@tonic-gate 	int instance = ddi_get_instance(dip);
3297c478bd9Sstevel@tonic-gate 
3307c478bd9Sstevel@tonic-gate 	/*
3317c478bd9Sstevel@tonic-gate 	 * Allocate soft state structure.
3327c478bd9Sstevel@tonic-gate 	 */
3337c478bd9Sstevel@tonic-gate 	if (ddi_soft_state_zalloc(smbus_state, instance) != DDI_SUCCESS) {
3347c478bd9Sstevel@tonic-gate 
3357c478bd9Sstevel@tonic-gate 		goto bad;
3367c478bd9Sstevel@tonic-gate 	}
3377c478bd9Sstevel@tonic-gate 
3387c478bd9Sstevel@tonic-gate 	smbus = ddi_get_soft_state(smbus_state, instance);
3397c478bd9Sstevel@tonic-gate 
3407c478bd9Sstevel@tonic-gate 	(void) snprintf(smbus->smbus_name, sizeof (smbus->smbus_name),
3417c478bd9Sstevel@tonic-gate 	    "%s%d", ddi_node_name(dip), instance);
3427c478bd9Sstevel@tonic-gate 
3437c478bd9Sstevel@tonic-gate 	smbus->smbus_dip = dip;
3447c478bd9Sstevel@tonic-gate 
3457c478bd9Sstevel@tonic-gate 	mutex_init(&smbus->smbus_mutex, NULL, MUTEX_DRIVER, NULL);
3467c478bd9Sstevel@tonic-gate 	mutex_init(&smbus->smbus_imutex, NULL, MUTEX_DRIVER, NULL);
3477c478bd9Sstevel@tonic-gate 	cv_init(&smbus->smbus_cv, NULL, CV_DRIVER, NULL);
3487c478bd9Sstevel@tonic-gate 	cv_init(&smbus->smbus_intr_cv, NULL, CV_DRIVER, NULL);
3497c478bd9Sstevel@tonic-gate 
3507c478bd9Sstevel@tonic-gate 	if (smbus_setup_regs(dip, smbus) != DDI_SUCCESS) {
3517c478bd9Sstevel@tonic-gate 		goto bad;
3527c478bd9Sstevel@tonic-gate 	}
3537c478bd9Sstevel@tonic-gate 
3547c478bd9Sstevel@tonic-gate 	if (ddi_prop_exists(DDI_DEV_T_ANY, dip,  DDI_PROP_DONTPASS,
3557c478bd9Sstevel@tonic-gate 	    "interrupts") == 1) {
3567c478bd9Sstevel@tonic-gate 		smbus->smbus_polling = 0;
3577c478bd9Sstevel@tonic-gate 		/*
3587c478bd9Sstevel@tonic-gate 		 * The "interrupt-priorities" property is how a driver can
3597c478bd9Sstevel@tonic-gate 		 * specify a SPARC PIL level to associate with each of its
3607c478bd9Sstevel@tonic-gate 		 * interrupt properties.  Most self-identifying busses have
3617c478bd9Sstevel@tonic-gate 		 * a better mechanism for managing this, but I2C doesn't.
3627c478bd9Sstevel@tonic-gate 		 */
3637c478bd9Sstevel@tonic-gate 		if (ddi_prop_exists(DDI_DEV_T_ANY, dip,
3647c478bd9Sstevel@tonic-gate 		    DDI_PROP_NOTPROM | DDI_PROP_DONTPASS,
3657c478bd9Sstevel@tonic-gate 		    "interrupt-priorities") != 1) {
3667c478bd9Sstevel@tonic-gate 			(void) ddi_prop_create(DDI_DEV_T_NONE, dip,
3677c478bd9Sstevel@tonic-gate 			    DDI_PROP_CANSLEEP, "interrupt-priorities",
3687c478bd9Sstevel@tonic-gate 			    (caddr_t)&smbus_pil,
3697c478bd9Sstevel@tonic-gate 			    sizeof (smbus_pil));
3707c478bd9Sstevel@tonic-gate 			smbus->smbus_attachflags |= INTERRUPT_PRI;
3717c478bd9Sstevel@tonic-gate 		}
3727c478bd9Sstevel@tonic-gate 
3737c478bd9Sstevel@tonic-gate 		/*
3747c478bd9Sstevel@tonic-gate 		 * Clear status to clear any possible interrupt
3757c478bd9Sstevel@tonic-gate 		 */
3767c478bd9Sstevel@tonic-gate 		smbus_put(smbus, SMB_STS, 0xff, SMBUS_FLUSH);
3777c478bd9Sstevel@tonic-gate 
3787c478bd9Sstevel@tonic-gate 		if (ddi_get_iblock_cookie(dip, 0, &smbus->smbus_icookie) !=
3797c478bd9Sstevel@tonic-gate 		    DDI_SUCCESS) {
3807c478bd9Sstevel@tonic-gate 			goto bad;
3817c478bd9Sstevel@tonic-gate 		}
3827c478bd9Sstevel@tonic-gate 
3837c478bd9Sstevel@tonic-gate 		if (ddi_add_intr(dip, 0, NULL, NULL, smbus_intr,
3847c478bd9Sstevel@tonic-gate 		    (caddr_t)smbus) != DDI_SUCCESS) {
3857c478bd9Sstevel@tonic-gate 			cmn_err(CE_WARN, "%s failed to add interrupt",
3867c478bd9Sstevel@tonic-gate 			    smbus->smbus_name);
3877c478bd9Sstevel@tonic-gate 			goto bad;
3887c478bd9Sstevel@tonic-gate 		}
3897c478bd9Sstevel@tonic-gate 		smbus->smbus_attachflags |= ADD_INTR;
3907c478bd9Sstevel@tonic-gate 	} else {
3917c478bd9Sstevel@tonic-gate 		smbus->smbus_polling = 1;
3927c478bd9Sstevel@tonic-gate 		/* Clear status */
3937c478bd9Sstevel@tonic-gate 		smbus_put(smbus, SMB_STS, 0xff, SMBUS_FLUSH);
3947c478bd9Sstevel@tonic-gate 	}
3957c478bd9Sstevel@tonic-gate 
3967c478bd9Sstevel@tonic-gate 	/*
3977c478bd9Sstevel@tonic-gate 	 * initialize a cv and mutex
3987c478bd9Sstevel@tonic-gate 	 */
3997c478bd9Sstevel@tonic-gate 	cv_init(&smbus->smbus_icv, NULL, CV_DRIVER, NULL);
4007c478bd9Sstevel@tonic-gate 	mutex_init(&smbus->smbus_imutex, NULL, MUTEX_DRIVER,
4017c478bd9Sstevel@tonic-gate 	    (void *)smbus->smbus_icookie);
4027c478bd9Sstevel@tonic-gate 	smbus->smbus_attachflags |= IMUTEX;
4037c478bd9Sstevel@tonic-gate 
4047c478bd9Sstevel@tonic-gate 	/*
4057c478bd9Sstevel@tonic-gate 	 * Register with the i2c framework
4067c478bd9Sstevel@tonic-gate 	 */
4077c478bd9Sstevel@tonic-gate 	i2c_nexus_register(dip, &smbus_regvec);
4087c478bd9Sstevel@tonic-gate 	smbus->smbus_attachflags |= NEXUS_REGISTER;
4097c478bd9Sstevel@tonic-gate 
4107c478bd9Sstevel@tonic-gate 	return (DDI_SUCCESS);
4117c478bd9Sstevel@tonic-gate 
4127c478bd9Sstevel@tonic-gate bad:
4137c478bd9Sstevel@tonic-gate 	smbus_dodetach(dip);
4147c478bd9Sstevel@tonic-gate 
4157c478bd9Sstevel@tonic-gate 	return (DDI_FAILURE);
4167c478bd9Sstevel@tonic-gate }
4177c478bd9Sstevel@tonic-gate 
4187c478bd9Sstevel@tonic-gate static int
smbus_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)4197c478bd9Sstevel@tonic-gate smbus_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
4207c478bd9Sstevel@tonic-gate {
4217c478bd9Sstevel@tonic-gate 	switch (cmd) {
4227c478bd9Sstevel@tonic-gate 	case DDI_ATTACH:
4237c478bd9Sstevel@tonic-gate 
4247c478bd9Sstevel@tonic-gate 		return (smbus_doattach(dip));
4257c478bd9Sstevel@tonic-gate 	case DDI_RESUME:
4267c478bd9Sstevel@tonic-gate 		smbus_resume(dip);
4277c478bd9Sstevel@tonic-gate 
4287c478bd9Sstevel@tonic-gate 		return (DDI_SUCCESS);
4297c478bd9Sstevel@tonic-gate 	default:
4307c478bd9Sstevel@tonic-gate 
4317c478bd9Sstevel@tonic-gate 		return (DDI_FAILURE);
4327c478bd9Sstevel@tonic-gate 	}
4337c478bd9Sstevel@tonic-gate }
4347c478bd9Sstevel@tonic-gate 
4357c478bd9Sstevel@tonic-gate static int
smbus_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)4367c478bd9Sstevel@tonic-gate smbus_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
4377c478bd9Sstevel@tonic-gate {
4387c478bd9Sstevel@tonic-gate 	switch (cmd) {
4397c478bd9Sstevel@tonic-gate 	case DDI_DETACH:
4407c478bd9Sstevel@tonic-gate 		smbus_dodetach(dip);
4417c478bd9Sstevel@tonic-gate 
4427c478bd9Sstevel@tonic-gate 		return (DDI_SUCCESS);
4437c478bd9Sstevel@tonic-gate 	case DDI_SUSPEND:
4447c478bd9Sstevel@tonic-gate 		smbus_suspend(dip);
4457c478bd9Sstevel@tonic-gate 
4467c478bd9Sstevel@tonic-gate 		return (DDI_SUCCESS);
4477c478bd9Sstevel@tonic-gate 	default:
4487c478bd9Sstevel@tonic-gate 
4497c478bd9Sstevel@tonic-gate 		return (DDI_FAILURE);
4507c478bd9Sstevel@tonic-gate 	}
4517c478bd9Sstevel@tonic-gate }
4527c478bd9Sstevel@tonic-gate 
4537c478bd9Sstevel@tonic-gate static int
smbus_bus_ctl(dev_info_t * dip,dev_info_t * rdip,ddi_ctl_enum_t op,void * arg,void * result)4547c478bd9Sstevel@tonic-gate smbus_bus_ctl(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t op,
4557c478bd9Sstevel@tonic-gate     void *arg, void *result)
4567c478bd9Sstevel@tonic-gate {
4577c478bd9Sstevel@tonic-gate 	switch (op) {
4587c478bd9Sstevel@tonic-gate 	case DDI_CTLOPS_INITCHILD:
4597c478bd9Sstevel@tonic-gate 
4607c478bd9Sstevel@tonic-gate 		return (smbus_initchild((dev_info_t *)arg));
4617c478bd9Sstevel@tonic-gate 	case DDI_CTLOPS_UNINITCHILD:
4627c478bd9Sstevel@tonic-gate 		smbus_uninitchild((dev_info_t *)arg);
4637c478bd9Sstevel@tonic-gate 
4647c478bd9Sstevel@tonic-gate 		return (DDI_SUCCESS);
465ecd343b6Sarutz 	case DDI_CTLOPS_REPORTDEV:
4667c478bd9Sstevel@tonic-gate 		smbus_reportdev(dip, rdip);
4677c478bd9Sstevel@tonic-gate 
4687c478bd9Sstevel@tonic-gate 		return (DDI_SUCCESS);
4697c478bd9Sstevel@tonic-gate 	case DDI_CTLOPS_DMAPMAPC:
4707c478bd9Sstevel@tonic-gate 	case DDI_CTLOPS_POKE:
4717c478bd9Sstevel@tonic-gate 	case DDI_CTLOPS_PEEK:
4727c478bd9Sstevel@tonic-gate 	case DDI_CTLOPS_IOMIN:
4737c478bd9Sstevel@tonic-gate 	case DDI_CTLOPS_REPORTINT:
4747c478bd9Sstevel@tonic-gate 	case DDI_CTLOPS_SIDDEV:
4757c478bd9Sstevel@tonic-gate 	case DDI_CTLOPS_SLAVEONLY:
4767c478bd9Sstevel@tonic-gate 	case DDI_CTLOPS_AFFINITY:
4777c478bd9Sstevel@tonic-gate 	case DDI_CTLOPS_PTOB:
4787c478bd9Sstevel@tonic-gate 	case DDI_CTLOPS_BTOP:
4797c478bd9Sstevel@tonic-gate 	case DDI_CTLOPS_BTOPR:
4807c478bd9Sstevel@tonic-gate 	case DDI_CTLOPS_DVMAPAGESIZE:
4817c478bd9Sstevel@tonic-gate 
4827c478bd9Sstevel@tonic-gate 		return (DDI_FAILURE);
4837c478bd9Sstevel@tonic-gate 	default:
4847c478bd9Sstevel@tonic-gate 
4857c478bd9Sstevel@tonic-gate 		return (ddi_ctlops(dip, rdip, op, arg, result));
4867c478bd9Sstevel@tonic-gate 	}
4877c478bd9Sstevel@tonic-gate }
4887c478bd9Sstevel@tonic-gate 
4897c478bd9Sstevel@tonic-gate static int
smbus_initchild(dev_info_t * cdip)4907c478bd9Sstevel@tonic-gate smbus_initchild(dev_info_t *cdip)
4917c478bd9Sstevel@tonic-gate {
4927c478bd9Sstevel@tonic-gate 	int32_t cell_size;
4937c478bd9Sstevel@tonic-gate 	int len;
4947c478bd9Sstevel@tonic-gate 	int32_t regs[2];
4957c478bd9Sstevel@tonic-gate 	int err;
4967c478bd9Sstevel@tonic-gate 	smbus_ppvt_t *ppvt;
4977c478bd9Sstevel@tonic-gate 	char name[30];
4987c478bd9Sstevel@tonic-gate 
4997c478bd9Sstevel@tonic-gate 	SMBUS_PRINT((PRT_INIT, "smbus_initchild ENTER: %s\n",
5007c478bd9Sstevel@tonic-gate 	    ddi_node_name(cdip)));
5017c478bd9Sstevel@tonic-gate 
5027c478bd9Sstevel@tonic-gate 	len = sizeof (cell_size);
5037c478bd9Sstevel@tonic-gate 	err = ddi_getlongprop_buf(DDI_DEV_T_ANY, cdip,
504*19397407SSherry Moore 	    DDI_PROP_CANSLEEP, "#address-cells",
505*19397407SSherry Moore 	    (caddr_t)&cell_size, &len);
5067c478bd9Sstevel@tonic-gate 	if (err != DDI_PROP_SUCCESS || len != sizeof (cell_size)) {
5077c478bd9Sstevel@tonic-gate 		cmn_err(CE_WARN, "cannot find address-cells");
5087c478bd9Sstevel@tonic-gate 
5097c478bd9Sstevel@tonic-gate 		return (DDI_FAILURE);
5107c478bd9Sstevel@tonic-gate 	}
5117c478bd9Sstevel@tonic-gate 
5127c478bd9Sstevel@tonic-gate 	len = sizeof (regs);
5137c478bd9Sstevel@tonic-gate 	err = ddi_getlongprop_buf(DDI_DEV_T_ANY, cdip,
5147c478bd9Sstevel@tonic-gate 	    DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP,
5157c478bd9Sstevel@tonic-gate 	    "reg", (caddr_t)regs, &len);
5167c478bd9Sstevel@tonic-gate 
5177c478bd9Sstevel@tonic-gate 	if (err != DDI_PROP_SUCCESS) {
5187c478bd9Sstevel@tonic-gate 		cmn_err(CE_WARN, "cannot get reg property");
5197c478bd9Sstevel@tonic-gate 
5207c478bd9Sstevel@tonic-gate 		return (DDI_FAILURE);
5217c478bd9Sstevel@tonic-gate 	}
5227c478bd9Sstevel@tonic-gate 
5237c478bd9Sstevel@tonic-gate 	ppvt = kmem_zalloc(sizeof (smbus_ppvt_t), KM_SLEEP);
5247c478bd9Sstevel@tonic-gate 	ddi_set_parent_data(cdip, ppvt);
5257c478bd9Sstevel@tonic-gate 
5267c478bd9Sstevel@tonic-gate 	/*
5277c478bd9Sstevel@tonic-gate 	 * The reg property contains an unused first element (which is
5287c478bd9Sstevel@tonic-gate 	 * the mux addr on xcal), and the second element is the i2c bus
5297c478bd9Sstevel@tonic-gate 	 * address of the device.
5307c478bd9Sstevel@tonic-gate 	 */
5317c478bd9Sstevel@tonic-gate 	ppvt->smbus_ppvt_addr = regs[1];
5327c478bd9Sstevel@tonic-gate 	(void) sprintf(name, "%x", regs[1]);
5337c478bd9Sstevel@tonic-gate 
5347c478bd9Sstevel@tonic-gate 	ddi_set_name_addr(cdip, name);
5357c478bd9Sstevel@tonic-gate 
5367c478bd9Sstevel@tonic-gate 	SMBUS_PRINT((PRT_INIT, "smbus_initchild SUCCESS: %s\n",
5377c478bd9Sstevel@tonic-gate 	    ddi_node_name(cdip)));
5387c478bd9Sstevel@tonic-gate 
5397c478bd9Sstevel@tonic-gate 	return (DDI_SUCCESS);
5407c478bd9Sstevel@tonic-gate }
5417c478bd9Sstevel@tonic-gate 
5427c478bd9Sstevel@tonic-gate static void
smbus_uninitchild(dev_info_t * cdip)5437c478bd9Sstevel@tonic-gate smbus_uninitchild(dev_info_t *cdip)
5447c478bd9Sstevel@tonic-gate {
5457c478bd9Sstevel@tonic-gate 	smbus_ppvt_t *ppvt;
5467c478bd9Sstevel@tonic-gate 
5477c478bd9Sstevel@tonic-gate 	ppvt = ddi_get_parent_data(cdip);
5487c478bd9Sstevel@tonic-gate 	ddi_set_parent_data(cdip, NULL);
5497c478bd9Sstevel@tonic-gate 
5507c478bd9Sstevel@tonic-gate 	ddi_set_name_addr(cdip, NULL);
5517c478bd9Sstevel@tonic-gate 
5527c478bd9Sstevel@tonic-gate 	kmem_free(ppvt, sizeof (smbus_ppvt_t));
5537c478bd9Sstevel@tonic-gate 
5547c478bd9Sstevel@tonic-gate 	SMBUS_PRINT((PRT_INIT, "smbus_uninitchild: %s\n", ddi_node_name(cdip)));
5557c478bd9Sstevel@tonic-gate }
5567c478bd9Sstevel@tonic-gate 
5577c478bd9Sstevel@tonic-gate static void
smbus_reportdev(dev_info_t * dip,dev_info_t * rdip)5587c478bd9Sstevel@tonic-gate smbus_reportdev(dev_info_t *dip, dev_info_t *rdip)
5597c478bd9Sstevel@tonic-gate {
5607c478bd9Sstevel@tonic-gate 	smbus_ppvt_t *ppvt;
5617c478bd9Sstevel@tonic-gate 
5627c478bd9Sstevel@tonic-gate 	ppvt = ddi_get_parent_data(rdip);
5637c478bd9Sstevel@tonic-gate 
5647c478bd9Sstevel@tonic-gate 	cmn_err(CE_CONT, "?%s%d at %s%d: addr 0x%x",
5657c478bd9Sstevel@tonic-gate 	    ddi_driver_name(rdip), ddi_get_instance(rdip),
5667c478bd9Sstevel@tonic-gate 	    ddi_driver_name(dip), ddi_get_instance(dip),
5677c478bd9Sstevel@tonic-gate 	    ppvt->smbus_ppvt_addr);
5687c478bd9Sstevel@tonic-gate }
5697c478bd9Sstevel@tonic-gate 
5707c478bd9Sstevel@tonic-gate /*
5717c478bd9Sstevel@tonic-gate  * smbus_setup_regs() is called to map in the registers
5727c478bd9Sstevel@tonic-gate  * specific to the smbus.
5737c478bd9Sstevel@tonic-gate  */
5747c478bd9Sstevel@tonic-gate static int
smbus_setup_regs(dev_info_t * dip,smbus_t * smbus)5757c478bd9Sstevel@tonic-gate smbus_setup_regs(dev_info_t *dip, smbus_t *smbus)
5767c478bd9Sstevel@tonic-gate {
5777c478bd9Sstevel@tonic-gate 	ddi_device_acc_attr_t attr;
5787c478bd9Sstevel@tonic-gate 	int ret;
5797c478bd9Sstevel@tonic-gate 
5807c478bd9Sstevel@tonic-gate 	attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
5817c478bd9Sstevel@tonic-gate 	attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
5827c478bd9Sstevel@tonic-gate 	attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
5837c478bd9Sstevel@tonic-gate 
5847c478bd9Sstevel@tonic-gate 	ret = ddi_regs_map_setup(dip, 1, (caddr_t *)&smbus->smbus_regaddr,
5857c478bd9Sstevel@tonic-gate 	    0, 0, &attr, &smbus->smbus_rhandle);
5867c478bd9Sstevel@tonic-gate 
5877c478bd9Sstevel@tonic-gate 	if (ret == DDI_FAILURE) {
5887c478bd9Sstevel@tonic-gate 		cmn_err(CE_WARN, "%s unable to map regs", smbus->smbus_name);
5897c478bd9Sstevel@tonic-gate 
5907c478bd9Sstevel@tonic-gate 	} else if (ret == DDI_REGS_ACC_CONFLICT) {
5917c478bd9Sstevel@tonic-gate 		cmn_err(CE_WARN,
5927c478bd9Sstevel@tonic-gate 		    "%s unable to map regs because of conflict",
5937c478bd9Sstevel@tonic-gate 		    smbus->smbus_name);
5947c478bd9Sstevel@tonic-gate 		ret = DDI_FAILURE;
5957c478bd9Sstevel@tonic-gate 	}
5967c478bd9Sstevel@tonic-gate 
5977c478bd9Sstevel@tonic-gate 	if (ret == DDI_FAILURE) {
5987c478bd9Sstevel@tonic-gate 
5997c478bd9Sstevel@tonic-gate 		return (ret);
6007c478bd9Sstevel@tonic-gate 	}
6017c478bd9Sstevel@tonic-gate 
6027c478bd9Sstevel@tonic-gate 	ret = ddi_regs_map_setup(dip, 0, (caddr_t *)&smbus->smbus_configregaddr,
6037c478bd9Sstevel@tonic-gate 	    0, 0, &attr, &smbus->smbus_confighandle);
6047c478bd9Sstevel@tonic-gate 
6057c478bd9Sstevel@tonic-gate 	if (ret == DDI_FAILURE) {
6067c478bd9Sstevel@tonic-gate 		cmn_err(CE_WARN, "%s unable to map config regs",
6077c478bd9Sstevel@tonic-gate 		    smbus->smbus_name);
6087c478bd9Sstevel@tonic-gate 
6097c478bd9Sstevel@tonic-gate 	} else if (ret == DDI_REGS_ACC_CONFLICT) {
6107c478bd9Sstevel@tonic-gate 		cmn_err(CE_WARN,
6117c478bd9Sstevel@tonic-gate 		    "%s unable to map config regs because of conflict",
6127c478bd9Sstevel@tonic-gate 		    smbus->smbus_name);
6137c478bd9Sstevel@tonic-gate 		ret = DDI_FAILURE;
6147c478bd9Sstevel@tonic-gate 	}
6157c478bd9Sstevel@tonic-gate 
6167c478bd9Sstevel@tonic-gate 	return (ret);
6177c478bd9Sstevel@tonic-gate }
6187c478bd9Sstevel@tonic-gate 
6197c478bd9Sstevel@tonic-gate /*
6207c478bd9Sstevel@tonic-gate  * smbus_free_regs() frees any registers previously allocated.
6217c478bd9Sstevel@tonic-gate  */
6227c478bd9Sstevel@tonic-gate static void
smbus_free_regs(smbus_t * smbus)6237c478bd9Sstevel@tonic-gate smbus_free_regs(smbus_t *smbus)
6247c478bd9Sstevel@tonic-gate {
6257c478bd9Sstevel@tonic-gate 	if (smbus->smbus_regaddr != NULL) {
6267c478bd9Sstevel@tonic-gate 		ddi_regs_map_free(&smbus->smbus_rhandle);
6277c478bd9Sstevel@tonic-gate 	}
6287c478bd9Sstevel@tonic-gate 
6297c478bd9Sstevel@tonic-gate 	if (smbus->smbus_configregaddr != NULL) {
6307c478bd9Sstevel@tonic-gate 		ddi_regs_map_free(&smbus->smbus_confighandle);
6317c478bd9Sstevel@tonic-gate 	}
6327c478bd9Sstevel@tonic-gate }
6337c478bd9Sstevel@tonic-gate 
6347c478bd9Sstevel@tonic-gate /*
6357c478bd9Sstevel@tonic-gate  * smbus_dip_to_addr() takes a dip and returns an I2C address.
6367c478bd9Sstevel@tonic-gate  */
6377c478bd9Sstevel@tonic-gate static int
smbus_dip_to_addr(dev_info_t * cdip)6387c478bd9Sstevel@tonic-gate smbus_dip_to_addr(dev_info_t *cdip)
6397c478bd9Sstevel@tonic-gate {
6407c478bd9Sstevel@tonic-gate 	smbus_ppvt_t *ppvt;
6417c478bd9Sstevel@tonic-gate 
6427c478bd9Sstevel@tonic-gate 	ppvt = ddi_get_parent_data(cdip);
6437c478bd9Sstevel@tonic-gate 
6447c478bd9Sstevel@tonic-gate 	return (ppvt->smbus_ppvt_addr);
6457c478bd9Sstevel@tonic-gate }
6467c478bd9Sstevel@tonic-gate 
6477c478bd9Sstevel@tonic-gate /*
6487c478bd9Sstevel@tonic-gate  * smbus_suspend() is called before the system suspends.  Existing
6497c478bd9Sstevel@tonic-gate  * transfer in progress or waiting will complete, but new transfers are
6507c478bd9Sstevel@tonic-gate  * effectively blocked by "acquiring" the bus.
6517c478bd9Sstevel@tonic-gate  */
6527c478bd9Sstevel@tonic-gate static void
smbus_suspend(dev_info_t * dip)6537c478bd9Sstevel@tonic-gate smbus_suspend(dev_info_t *dip)
6547c478bd9Sstevel@tonic-gate {
6557c478bd9Sstevel@tonic-gate 	smbus_t *smbus;
6567c478bd9Sstevel@tonic-gate 	int instance;
6577c478bd9Sstevel@tonic-gate 
6587c478bd9Sstevel@tonic-gate 	instance = ddi_get_instance(dip);
6597c478bd9Sstevel@tonic-gate 	smbus = ddi_get_soft_state(smbus_state, instance);
6607c478bd9Sstevel@tonic-gate 
6617c478bd9Sstevel@tonic-gate 	(void) smbus_acquire(smbus, NULL, NULL);
6627c478bd9Sstevel@tonic-gate }
6637c478bd9Sstevel@tonic-gate 
6647c478bd9Sstevel@tonic-gate /*
6657c478bd9Sstevel@tonic-gate  * smbus_resume() is called when the system resumes from CPR.  It releases
6667c478bd9Sstevel@tonic-gate  * the hold that was placed on the i2c bus, which allows any real
6677c478bd9Sstevel@tonic-gate  * transfers to continue.
6687c478bd9Sstevel@tonic-gate  */
6697c478bd9Sstevel@tonic-gate static void
smbus_resume(dev_info_t * dip)6707c478bd9Sstevel@tonic-gate smbus_resume(dev_info_t *dip)
6717c478bd9Sstevel@tonic-gate {
6727c478bd9Sstevel@tonic-gate 	smbus_t *smbus;
6737c478bd9Sstevel@tonic-gate 	int instance;
6747c478bd9Sstevel@tonic-gate 
6757c478bd9Sstevel@tonic-gate 	instance = ddi_get_instance(dip);
6767c478bd9Sstevel@tonic-gate 	smbus = ddi_get_soft_state(smbus_state, instance);
6777c478bd9Sstevel@tonic-gate 
6787c478bd9Sstevel@tonic-gate 	smbus_release(smbus);
6797c478bd9Sstevel@tonic-gate }
6807c478bd9Sstevel@tonic-gate 
6817c478bd9Sstevel@tonic-gate /*
6827c478bd9Sstevel@tonic-gate  * smbus_acquire() is called by a thread wishing to "own" the SMbus.
6837c478bd9Sstevel@tonic-gate  * It should not be held across multiple transfers.
6847c478bd9Sstevel@tonic-gate  */
6857c478bd9Sstevel@tonic-gate static int
smbus_acquire(smbus_t * smbus,dev_info_t * dip,i2c_transfer_t * tp)6867c478bd9Sstevel@tonic-gate smbus_acquire(smbus_t *smbus, dev_info_t *dip, i2c_transfer_t *tp)
6877c478bd9Sstevel@tonic-gate {
6887c478bd9Sstevel@tonic-gate 	mutex_enter(&smbus->smbus_mutex);
6897c478bd9Sstevel@tonic-gate 	while (smbus->smbus_busy) {
6907c478bd9Sstevel@tonic-gate 		cv_wait(&smbus->smbus_cv, &smbus->smbus_mutex);
6917c478bd9Sstevel@tonic-gate 	}
6927c478bd9Sstevel@tonic-gate 	smbus->smbus_busy = 1;
6937c478bd9Sstevel@tonic-gate 	mutex_exit(&smbus->smbus_mutex);
6947c478bd9Sstevel@tonic-gate 
6957c478bd9Sstevel@tonic-gate 	/*
6967c478bd9Sstevel@tonic-gate 	 * On systems where OBP shares a smbus controller with the
6977c478bd9Sstevel@tonic-gate 	 * OS, plat_shared_i2c_enter will serialize access to the
6987c478bd9Sstevel@tonic-gate 	 * smbus controller.  Do not grab this lock during CPR
6997c478bd9Sstevel@tonic-gate 	 * suspend as the CPR thread also acquires this muxex
7007c478bd9Sstevel@tonic-gate 	 * through through prom_setprop which causes recursive
7017c478bd9Sstevel@tonic-gate 	 * mutex enter.
7027c478bd9Sstevel@tonic-gate 	 *
7037c478bd9Sstevel@tonic-gate 	 * dip == NULL during CPR.
7047c478bd9Sstevel@tonic-gate 	 */
7057c478bd9Sstevel@tonic-gate 	if ((&plat_shared_i2c_enter != NULL) && (dip != NULL)) {
7067c478bd9Sstevel@tonic-gate 		plat_shared_i2c_enter(smbus->smbus_dip);
7077c478bd9Sstevel@tonic-gate 	}
7087c478bd9Sstevel@tonic-gate 
7097c478bd9Sstevel@tonic-gate 	smbus->smbus_cur_tran = tp;
7107c478bd9Sstevel@tonic-gate 	smbus->smbus_cur_dip = dip;
7117c478bd9Sstevel@tonic-gate 
7127c478bd9Sstevel@tonic-gate 	return (SMBUS_SUCCESS);
7137c478bd9Sstevel@tonic-gate }
7147c478bd9Sstevel@tonic-gate 
7157c478bd9Sstevel@tonic-gate /*
7167c478bd9Sstevel@tonic-gate  * smbus_release() is called to release a hold made by smbus_acquire().
7177c478bd9Sstevel@tonic-gate  */
7187c478bd9Sstevel@tonic-gate static void
smbus_release(smbus_t * smbus)7197c478bd9Sstevel@tonic-gate smbus_release(smbus_t *smbus)
7207c478bd9Sstevel@tonic-gate {
7217c478bd9Sstevel@tonic-gate 	mutex_enter(&smbus->smbus_mutex);
7227c478bd9Sstevel@tonic-gate 	smbus->smbus_busy = 0;
7237c478bd9Sstevel@tonic-gate 	cv_signal(&smbus->smbus_cv);
7247c478bd9Sstevel@tonic-gate 	smbus->smbus_cur_tran = NULL;
7257c478bd9Sstevel@tonic-gate 	smbus->smbus_cur_dip = NULL;
7267c478bd9Sstevel@tonic-gate 	mutex_exit(&smbus->smbus_mutex);
7277c478bd9Sstevel@tonic-gate 
7287c478bd9Sstevel@tonic-gate 	if ((&plat_shared_i2c_exit != NULL) && (smbus->smbus_cur_dip != NULL)) {
7297c478bd9Sstevel@tonic-gate 		plat_shared_i2c_exit(smbus->smbus_dip);
7307c478bd9Sstevel@tonic-gate 	}
7317c478bd9Sstevel@tonic-gate }
7327c478bd9Sstevel@tonic-gate 
7337c478bd9Sstevel@tonic-gate static void
smbus_put(smbus_t * smbus,uint8_t reg,uint8_t data,uint8_t flags)7347c478bd9Sstevel@tonic-gate smbus_put(smbus_t *smbus, uint8_t reg, uint8_t data, uint8_t flags)
7357c478bd9Sstevel@tonic-gate {
7367c478bd9Sstevel@tonic-gate 	ddi_acc_handle_t hp = smbus->smbus_rhandle;
7377c478bd9Sstevel@tonic-gate 	uint8_t *reg_addr = smbus->smbus_regaddr;
7387c478bd9Sstevel@tonic-gate 	uint8_t *config_addr = smbus->smbus_configregaddr;
7397c478bd9Sstevel@tonic-gate 	ddi_acc_handle_t config_handle = smbus->smbus_confighandle;
7407c478bd9Sstevel@tonic-gate 
7417c478bd9Sstevel@tonic-gate 	ddi_put8(hp, &reg_addr[reg], data);
7427c478bd9Sstevel@tonic-gate 
7437c478bd9Sstevel@tonic-gate 	SMBUS_PRINT((PRT_PUT, "smbus_put:  addr = %p data = %x\n",
7447c478bd9Sstevel@tonic-gate 	    &reg_addr[reg], data));
7457c478bd9Sstevel@tonic-gate 
7467c478bd9Sstevel@tonic-gate 	/*
7477c478bd9Sstevel@tonic-gate 	 * if FLUSH flag is passed, read a config regs to make sure
7487c478bd9Sstevel@tonic-gate 	 * data written is flushed.
7497c478bd9Sstevel@tonic-gate 	 */
7507c478bd9Sstevel@tonic-gate 	if (flags & SMBUS_FLUSH) {
7517c478bd9Sstevel@tonic-gate 		(void) ddi_get8(config_handle, &config_addr[0]);
7527c478bd9Sstevel@tonic-gate 	}
7537c478bd9Sstevel@tonic-gate }
7547c478bd9Sstevel@tonic-gate 
7557c478bd9Sstevel@tonic-gate static uint8_t
smbus_get(smbus_t * smbus,uint8_t reg)7567c478bd9Sstevel@tonic-gate smbus_get(smbus_t *smbus, uint8_t reg)
7577c478bd9Sstevel@tonic-gate {
7587c478bd9Sstevel@tonic-gate 
7597c478bd9Sstevel@tonic-gate 	ddi_acc_handle_t hp = smbus->smbus_rhandle;
7607c478bd9Sstevel@tonic-gate 	uint8_t *regaddr = smbus->smbus_regaddr;
7617c478bd9Sstevel@tonic-gate 	uint8_t data;
7627c478bd9Sstevel@tonic-gate 
7637c478bd9Sstevel@tonic-gate 	data = ddi_get8(hp, &regaddr[reg]);
7647c478bd9Sstevel@tonic-gate 
7657c478bd9Sstevel@tonic-gate 	SMBUS_PRINT((PRT_GET, "smbus_get: data = %x\n", data));
7667c478bd9Sstevel@tonic-gate 
7677c478bd9Sstevel@tonic-gate 	return (data);
7687c478bd9Sstevel@tonic-gate }
7697c478bd9Sstevel@tonic-gate 
7707c478bd9Sstevel@tonic-gate 
7717c478bd9Sstevel@tonic-gate /*
7727c478bd9Sstevel@tonic-gate  * The southbridge smbus device appears to have a feature where
7737c478bd9Sstevel@tonic-gate  * reads from the status register return 0 for a few microseconds
7747c478bd9Sstevel@tonic-gate  * after clearing the status.
7757c478bd9Sstevel@tonic-gate  *
7767c478bd9Sstevel@tonic-gate  * "status_wait_idle" allows for this by retrying until
7777c478bd9Sstevel@tonic-gate  * it gets the right answer or times out.  The loop count
7787c478bd9Sstevel@tonic-gate  * and the delay are empirical. The routine uses up
7797c478bd9Sstevel@tonic-gate  * 400 us if it fails.
7807c478bd9Sstevel@tonic-gate  *
7817c478bd9Sstevel@tonic-gate  * The fact that this routine waits for 10 us before the
7827c478bd9Sstevel@tonic-gate  * first check is deliberate.
7837c478bd9Sstevel@tonic-gate  */
7847c478bd9Sstevel@tonic-gate static int
smbus_wait_idle(smbus_t * smbus)7857c478bd9Sstevel@tonic-gate smbus_wait_idle(smbus_t *smbus)
7867c478bd9Sstevel@tonic-gate {
7877c478bd9Sstevel@tonic-gate 	int retries = 40;
7887c478bd9Sstevel@tonic-gate 	int status;
7897c478bd9Sstevel@tonic-gate 
7907c478bd9Sstevel@tonic-gate 	smbus_put(smbus, SMB_STS, 0xff, SMBUS_FLUSH);
7917c478bd9Sstevel@tonic-gate 	do {
7927c478bd9Sstevel@tonic-gate 		drv_usecwait(10);
7937c478bd9Sstevel@tonic-gate 		status = smbus_get(smbus, SMB_STS);
7947c478bd9Sstevel@tonic-gate 	} while (status != IDLE && --retries > 0);
7957c478bd9Sstevel@tonic-gate 	return (status);
7967c478bd9Sstevel@tonic-gate }
7977c478bd9Sstevel@tonic-gate /*
7987c478bd9Sstevel@tonic-gate  * smbus_transfer is the function that is registered with
7997c478bd9Sstevel@tonic-gate  * I2C services to be called for each i2c transaction.
8007c478bd9Sstevel@tonic-gate  */
8017c478bd9Sstevel@tonic-gate int
smbus_transfer(dev_info_t * dip,i2c_transfer_t * tp)8027c478bd9Sstevel@tonic-gate smbus_transfer(dev_info_t *dip, i2c_transfer_t *tp)
8037c478bd9Sstevel@tonic-gate {
8047c478bd9Sstevel@tonic-gate 	smbus_t *smbus;
8057c478bd9Sstevel@tonic-gate 	uint8_t status;
8067c478bd9Sstevel@tonic-gate 	clock_t ctime;
8077c478bd9Sstevel@tonic-gate 
8087c478bd9Sstevel@tonic-gate 	smbus = ddi_get_soft_state(smbus_state,
8097c478bd9Sstevel@tonic-gate 	    ddi_get_instance(ddi_get_parent(dip)));
8107c478bd9Sstevel@tonic-gate 
8117c478bd9Sstevel@tonic-gate 	if (smbus_acquire(smbus, dip, tp) == SMBUS_FAILURE) {
8127c478bd9Sstevel@tonic-gate 		tp->i2c_result = I2C_FAILURE;
8137c478bd9Sstevel@tonic-gate 
8147c478bd9Sstevel@tonic-gate 		return (I2C_FAILURE);
8157c478bd9Sstevel@tonic-gate 	}
8167c478bd9Sstevel@tonic-gate 
8177c478bd9Sstevel@tonic-gate 	tp->i2c_r_resid = tp->i2c_rlen;
8187c478bd9Sstevel@tonic-gate 	tp->i2c_w_resid = tp->i2c_wlen;
8197c478bd9Sstevel@tonic-gate 	tp->i2c_result = I2C_SUCCESS;
8207c478bd9Sstevel@tonic-gate 	smbus->smbus_retries = 0;
8217c478bd9Sstevel@tonic-gate 	smbus->smbus_bytes_to_read = 0;
8227c478bd9Sstevel@tonic-gate 
8237c478bd9Sstevel@tonic-gate 	mutex_enter(&smbus->smbus_imutex);
8247c478bd9Sstevel@tonic-gate 
8257c478bd9Sstevel@tonic-gate 	SMBUS_PRINT((PRT_TRANS, "smbus_transfer: rlen=%d wlen=%d flags=%d",
8267c478bd9Sstevel@tonic-gate 	    tp->i2c_r_resid, tp->i2c_w_resid, tp->i2c_flags));
8277c478bd9Sstevel@tonic-gate 
8287c478bd9Sstevel@tonic-gate 	/*
8297c478bd9Sstevel@tonic-gate 	 * First clear the status bits, then read them back to determine
8307c478bd9Sstevel@tonic-gate 	 * the current state.
8317c478bd9Sstevel@tonic-gate 	 */
8327c478bd9Sstevel@tonic-gate 	status = smbus_wait_idle(smbus);
8337c478bd9Sstevel@tonic-gate 
8347c478bd9Sstevel@tonic-gate 	if (status != IDLE) {
8357c478bd9Sstevel@tonic-gate 		/*
8367c478bd9Sstevel@tonic-gate 		 * Try to issue bus reset
8377c478bd9Sstevel@tonic-gate 		 * First reset the state machine.
8387c478bd9Sstevel@tonic-gate 		 */
8397c478bd9Sstevel@tonic-gate 		smbus_put(smbus, SMB_TYP, KILL, SMBUS_FLUSH);
8407c478bd9Sstevel@tonic-gate 		status = smbus_wait_idle(smbus);
8417c478bd9Sstevel@tonic-gate 
8427c478bd9Sstevel@tonic-gate 		if (status != IDLE) {
8437c478bd9Sstevel@tonic-gate 
8447c478bd9Sstevel@tonic-gate 			smbus_put(smbus, SMB_TYP, T_OUT, SMBUS_FLUSH);
8457c478bd9Sstevel@tonic-gate 			status = smbus_wait_idle(smbus);
8467c478bd9Sstevel@tonic-gate 			if (status != IDLE) {
8477c478bd9Sstevel@tonic-gate 				cmn_err(CE_WARN,
8487c478bd9Sstevel@tonic-gate 				    "%s smbus not idle.  Unable to reset %x",
8497c478bd9Sstevel@tonic-gate 				    smbus->smbus_name, status);
8507c478bd9Sstevel@tonic-gate 				smbus->smbus_cur_tran->i2c_result = I2C_FAILURE;
8517c478bd9Sstevel@tonic-gate 				mutex_exit(&smbus->smbus_imutex);
8527c478bd9Sstevel@tonic-gate 				smbus_release(smbus);
8537c478bd9Sstevel@tonic-gate 
8547c478bd9Sstevel@tonic-gate 				return (I2C_FAILURE);
8557c478bd9Sstevel@tonic-gate 			} else {
8567c478bd9Sstevel@tonic-gate 				cmn_err(CE_WARN, "%s T_OUT reset required",
8577c478bd9Sstevel@tonic-gate 				    smbus->smbus_name);
8587c478bd9Sstevel@tonic-gate 			}
8597c478bd9Sstevel@tonic-gate 		}
8607c478bd9Sstevel@tonic-gate 	}
8617c478bd9Sstevel@tonic-gate 
8627c478bd9Sstevel@tonic-gate 	if (smbus_switch(smbus) != SMBUS_COMPLETE) {
8637c478bd9Sstevel@tonic-gate 		if (smbus->smbus_polling) {
8647c478bd9Sstevel@tonic-gate 			smbus->smbus_poll_complete = 0;
8657c478bd9Sstevel@tonic-gate 			smbus->smbus_poll_retries = 0;
8667c478bd9Sstevel@tonic-gate 			do {
8677c478bd9Sstevel@tonic-gate 				drv_usecwait(SMBUS_POLL_INTERVAL);
8687c478bd9Sstevel@tonic-gate 				(void) smbus_intr_cmn(smbus, SMBUS_POLL);
8697c478bd9Sstevel@tonic-gate 			} while (!smbus->smbus_poll_complete);
8707c478bd9Sstevel@tonic-gate 		} else {
8717c478bd9Sstevel@tonic-gate 			/*
8727c478bd9Sstevel@tonic-gate 			 * Start a timeout as there is a bug in southbridge
8737c478bd9Sstevel@tonic-gate 			 * smbus where sometimes a transaction never starts,
8747c478bd9Sstevel@tonic-gate 			 * and needs to be reinitiated.
8757c478bd9Sstevel@tonic-gate 			 */
8767c478bd9Sstevel@tonic-gate 
8777c478bd9Sstevel@tonic-gate 			smbus->smbus_timeout = timeout(smbus_intr_timeout,
8787c478bd9Sstevel@tonic-gate 			    smbus, drv_usectohz(intr_timeout));
8797c478bd9Sstevel@tonic-gate 			SMBUS_PRINT((PRT_TRANS,
8807c478bd9Sstevel@tonic-gate 			    "starting timeout in smbus_transfer %p",
8817c478bd9Sstevel@tonic-gate 			    smbus->smbus_timeout));
8827c478bd9Sstevel@tonic-gate 
8837c478bd9Sstevel@tonic-gate 			ctime = ddi_get_lbolt();
8847c478bd9Sstevel@tonic-gate 			ctime += drv_usectohz(SMBUS_TRANS_TIMEOUT);
8857c478bd9Sstevel@tonic-gate 
8867c478bd9Sstevel@tonic-gate 			smbus_interrupts_on(smbus);
8877c478bd9Sstevel@tonic-gate 
8887c478bd9Sstevel@tonic-gate 
8897c478bd9Sstevel@tonic-gate 			cv_wait(&smbus->smbus_icv, &smbus->smbus_imutex);
8907c478bd9Sstevel@tonic-gate 		}
8917c478bd9Sstevel@tonic-gate 	}
8927c478bd9Sstevel@tonic-gate 
8937c478bd9Sstevel@tonic-gate 
8947c478bd9Sstevel@tonic-gate 	mutex_exit(&smbus->smbus_imutex);
8957c478bd9Sstevel@tonic-gate 	smbus_release(smbus);
8967c478bd9Sstevel@tonic-gate 
8977c478bd9Sstevel@tonic-gate 	return (tp->i2c_result);
8987c478bd9Sstevel@tonic-gate }
8997c478bd9Sstevel@tonic-gate 
9007c478bd9Sstevel@tonic-gate /*
9017c478bd9Sstevel@tonic-gate  * This is called by smbus_intr_cmn() to figure out whether to call
9027c478bd9Sstevel@tonic-gate  * smbus_wr or smbus_rd depending on the command and current state.
9037c478bd9Sstevel@tonic-gate  */
9047c478bd9Sstevel@tonic-gate static int
smbus_switch(smbus_t * smbus)9057c478bd9Sstevel@tonic-gate smbus_switch(smbus_t *smbus)
9067c478bd9Sstevel@tonic-gate {
9077c478bd9Sstevel@tonic-gate 	int ret;
9087c478bd9Sstevel@tonic-gate 	i2c_transfer_t *tp = smbus->smbus_cur_tran;
9097c478bd9Sstevel@tonic-gate 
9107c478bd9Sstevel@tonic-gate 	if (tp == NULL) {
9117c478bd9Sstevel@tonic-gate 		cmn_err(CE_WARN,
9127c478bd9Sstevel@tonic-gate 		    "%s smbus_cur_tran is NULL. Transaction failed",
9137c478bd9Sstevel@tonic-gate 		    smbus->smbus_name);
9147c478bd9Sstevel@tonic-gate 
9157c478bd9Sstevel@tonic-gate 		return (SMBUS_FAILURE);
9167c478bd9Sstevel@tonic-gate 	}
9177c478bd9Sstevel@tonic-gate 
9187c478bd9Sstevel@tonic-gate 	smbus->smbus_saved_w_resid = tp->i2c_w_resid;
9197c478bd9Sstevel@tonic-gate 
9207c478bd9Sstevel@tonic-gate 	switch (tp->i2c_flags) {
9217c478bd9Sstevel@tonic-gate 	case I2C_WR:
9227c478bd9Sstevel@tonic-gate 		ret = smbus_wr(smbus);
9237c478bd9Sstevel@tonic-gate 		break;
9247c478bd9Sstevel@tonic-gate 	case I2C_RD:
9257c478bd9Sstevel@tonic-gate 		ret = smbus_rd(smbus);
9267c478bd9Sstevel@tonic-gate 		break;
9277c478bd9Sstevel@tonic-gate 	case I2C_WR_RD:
9287c478bd9Sstevel@tonic-gate 		/*
9297c478bd9Sstevel@tonic-gate 		 * We could do a bit more decoding here,
9307c478bd9Sstevel@tonic-gate 		 * to allow the transactions that would
9317c478bd9Sstevel@tonic-gate 		 * work as a single smbus command to
9327c478bd9Sstevel@tonic-gate 		 * be done as such.  It's not really
9337c478bd9Sstevel@tonic-gate 		 * worth the trouble.
9347c478bd9Sstevel@tonic-gate 		 */
9357c478bd9Sstevel@tonic-gate 		if (tp->i2c_w_resid > 0) {
9367c478bd9Sstevel@tonic-gate 			ret = smbus_wr(smbus);
9377c478bd9Sstevel@tonic-gate 		} else {
9387c478bd9Sstevel@tonic-gate 			ret = smbus_rd(smbus);
9397c478bd9Sstevel@tonic-gate 		}
9407c478bd9Sstevel@tonic-gate 		break;
9417c478bd9Sstevel@tonic-gate 	default:
9427c478bd9Sstevel@tonic-gate 		tp->i2c_result = I2C_FAILURE;
9437c478bd9Sstevel@tonic-gate 		ret = SMBUS_COMPLETE;
9447c478bd9Sstevel@tonic-gate 		break;
9457c478bd9Sstevel@tonic-gate 	}
9467c478bd9Sstevel@tonic-gate 
9477c478bd9Sstevel@tonic-gate 	return (ret);
9487c478bd9Sstevel@tonic-gate }
9497c478bd9Sstevel@tonic-gate 
9507c478bd9Sstevel@tonic-gate /*
9517c478bd9Sstevel@tonic-gate  *
9527c478bd9Sstevel@tonic-gate  */
9537c478bd9Sstevel@tonic-gate static void
smbus_intr_timeout(void * arg)9547c478bd9Sstevel@tonic-gate smbus_intr_timeout(void *arg)
9557c478bd9Sstevel@tonic-gate {
9567c478bd9Sstevel@tonic-gate 	smbus_t *smbus = (smbus_t *)arg;
9577c478bd9Sstevel@tonic-gate 
9587c478bd9Sstevel@tonic-gate 	mutex_enter(&smbus->smbus_imutex);
9597c478bd9Sstevel@tonic-gate 	/*
9607c478bd9Sstevel@tonic-gate 	 * If timeout is already cleared, it means interrupt arrived
9617c478bd9Sstevel@tonic-gate 	 * while timeout fired.  In this case, just return from here.
9627c478bd9Sstevel@tonic-gate 	 */
9637c478bd9Sstevel@tonic-gate 	if (smbus->smbus_timeout == 0) {
9647c478bd9Sstevel@tonic-gate 
9657c478bd9Sstevel@tonic-gate 		mutex_exit(&smbus->smbus_imutex);
9667c478bd9Sstevel@tonic-gate 
9677c478bd9Sstevel@tonic-gate 		return;
9687c478bd9Sstevel@tonic-gate 	}
9697c478bd9Sstevel@tonic-gate 
9707c478bd9Sstevel@tonic-gate 	(void) smbus_intr_cmn(smbus, SMBUS_TIMEOUT);
9717c478bd9Sstevel@tonic-gate 	mutex_exit(&smbus->smbus_imutex);
9727c478bd9Sstevel@tonic-gate }
9737c478bd9Sstevel@tonic-gate 
9747c478bd9Sstevel@tonic-gate /*
9757c478bd9Sstevel@tonic-gate  * smbus_intr() is the interrupt handler for smbus.
9767c478bd9Sstevel@tonic-gate  */
9777c478bd9Sstevel@tonic-gate static uint_t
smbus_intr(caddr_t arg)9787c478bd9Sstevel@tonic-gate smbus_intr(caddr_t arg)
9797c478bd9Sstevel@tonic-gate {
9807c478bd9Sstevel@tonic-gate 	smbus_t *smbus = (smbus_t *)arg;
9817c478bd9Sstevel@tonic-gate 	uint32_t intr_status;
9827c478bd9Sstevel@tonic-gate 	uint_t result;
9837c478bd9Sstevel@tonic-gate 
9847c478bd9Sstevel@tonic-gate 	/*
9857c478bd9Sstevel@tonic-gate 	 * Check to see if intr is really from smbus
9867c478bd9Sstevel@tonic-gate 	 */
9877c478bd9Sstevel@tonic-gate 	intr_status = ddi_get32(smbus->smbus_confighandle,
9887c478bd9Sstevel@tonic-gate 	    (uint32_t *)&smbus->smbus_configregaddr[SMBUS_SRC_STATUS]);
9897c478bd9Sstevel@tonic-gate 
9907c478bd9Sstevel@tonic-gate 
9917c478bd9Sstevel@tonic-gate 	if ((intr_status & SMBUS_SMB_INTR_STATUS) == 0) {
9927c478bd9Sstevel@tonic-gate 		SMBUS_PRINT((PRT_INTR, "smbus_intr: intr not from smbus\n"));
9937c478bd9Sstevel@tonic-gate 
9947c478bd9Sstevel@tonic-gate 		return (DDI_INTR_UNCLAIMED);
9957c478bd9Sstevel@tonic-gate 	}
9967c478bd9Sstevel@tonic-gate 
9977c478bd9Sstevel@tonic-gate 	mutex_enter(&smbus->smbus_imutex);
9987c478bd9Sstevel@tonic-gate 
9997c478bd9Sstevel@tonic-gate 	/*
10007c478bd9Sstevel@tonic-gate 	 * If timeout is already cleared, it means it arrived before the intr.
10017c478bd9Sstevel@tonic-gate 	 * In that case, just return from here.
10027c478bd9Sstevel@tonic-gate 	 */
10037c478bd9Sstevel@tonic-gate 	if (smbus->smbus_timeout == 0) {
10047c478bd9Sstevel@tonic-gate 
10057c478bd9Sstevel@tonic-gate 		mutex_exit(&smbus->smbus_imutex);
10067c478bd9Sstevel@tonic-gate 
10077c478bd9Sstevel@tonic-gate 		return (DDI_INTR_CLAIMED);
10087c478bd9Sstevel@tonic-gate 	}
10097c478bd9Sstevel@tonic-gate 
10107c478bd9Sstevel@tonic-gate 	result = smbus_intr_cmn(smbus, SMBUS_INTR);
10117c478bd9Sstevel@tonic-gate 	mutex_exit(&smbus->smbus_imutex);
10127c478bd9Sstevel@tonic-gate 	return (result);
10137c478bd9Sstevel@tonic-gate }
10147c478bd9Sstevel@tonic-gate 
10157c478bd9Sstevel@tonic-gate /*
10167c478bd9Sstevel@tonic-gate  * smbus_intr() is the interrupt handler for smbus.
10177c478bd9Sstevel@tonic-gate  */
10187c478bd9Sstevel@tonic-gate static uint_t
smbus_intr_cmn(smbus_t * smbus,char * src)10197c478bd9Sstevel@tonic-gate smbus_intr_cmn(smbus_t *smbus, char *src)
10207c478bd9Sstevel@tonic-gate {
10217c478bd9Sstevel@tonic-gate 	i2c_transfer_t *tp;
10227c478bd9Sstevel@tonic-gate 	char error_str[128];
10237c478bd9Sstevel@tonic-gate 	uint8_t status;
10247c478bd9Sstevel@tonic-gate 	int ret = SMBUS_SUCCESS;
10257c478bd9Sstevel@tonic-gate 	timeout_id_t timer_id;
10267c478bd9Sstevel@tonic-gate 
10277c478bd9Sstevel@tonic-gate 	ASSERT(mutex_owned(&smbus->smbus_imutex));
10287c478bd9Sstevel@tonic-gate 	error_str[0] = '\0';
10297c478bd9Sstevel@tonic-gate 
10307c478bd9Sstevel@tonic-gate 	smbus_interrupts_off(smbus);
10317c478bd9Sstevel@tonic-gate 
10327c478bd9Sstevel@tonic-gate 	tp = smbus->smbus_cur_tran;
10337c478bd9Sstevel@tonic-gate 	/*
10347c478bd9Sstevel@tonic-gate 	 * This only happens when top half is interrupted or
10357c478bd9Sstevel@tonic-gate 	 * times out, then the interrupt arrives.  Interrupt
10367c478bd9Sstevel@tonic-gate 	 * was already disabled by top half, so just exit.
10377c478bd9Sstevel@tonic-gate 	 */
10387c478bd9Sstevel@tonic-gate 	if (tp == NULL) {
10397c478bd9Sstevel@tonic-gate 		return (DDI_INTR_CLAIMED);
10407c478bd9Sstevel@tonic-gate 	}
10417c478bd9Sstevel@tonic-gate 
10427c478bd9Sstevel@tonic-gate 	/*
10437c478bd9Sstevel@tonic-gate 	 * This wait is required before reading the status, otherwise
10447c478bd9Sstevel@tonic-gate 	 * a parity error can occur which causes a panic.  A bug with
10457c478bd9Sstevel@tonic-gate 	 * southbridge SMBUS.
10467c478bd9Sstevel@tonic-gate 	 */
10477c478bd9Sstevel@tonic-gate 	drv_usecwait(15);
10487c478bd9Sstevel@tonic-gate 	status = smbus_get(smbus, SMB_STS);
10497c478bd9Sstevel@tonic-gate 	if (smbus->smbus_polling) {
10507c478bd9Sstevel@tonic-gate 		/*
10517c478bd9Sstevel@tonic-gate 		 * If we are polling, then we expect not to
10527c478bd9Sstevel@tonic-gate 		 * get the right answer for a while,
10537c478bd9Sstevel@tonic-gate 		 * so we don't go on to that error stuff
10547c478bd9Sstevel@tonic-gate 		 * until we've polled the status for a
10557c478bd9Sstevel@tonic-gate 		 * few times. We check for errors here to save time,
10567c478bd9Sstevel@tonic-gate 		 * otherwise we would have to wait for the full
10577c478bd9Sstevel@tonic-gate 		 * poll timeout before dealing with them.
10587c478bd9Sstevel@tonic-gate 		 */
10597c478bd9Sstevel@tonic-gate 		if (status != (CMD_CMPL|IDLE) &&
10607c478bd9Sstevel@tonic-gate 		    (status & (FAILED|BUS_ERR|DRV_ERR)) == 0 &&
10617c478bd9Sstevel@tonic-gate 		    smbus->smbus_poll_retries++ < SMBUS_POLL_MAX_RETRIES) {
10627c478bd9Sstevel@tonic-gate 				return (DDI_INTR_CLAIMED);
10637c478bd9Sstevel@tonic-gate 		}
10647c478bd9Sstevel@tonic-gate 		/*
10657c478bd9Sstevel@tonic-gate 		 * else either ...
10667c478bd9Sstevel@tonic-gate 		 * [] the command has completed, or;
10677c478bd9Sstevel@tonic-gate 		 * [] There has been an error, or;
10687c478bd9Sstevel@tonic-gate 		 * [] we timed out waiting for something useful
10697c478bd9Sstevel@tonic-gate 		 * to happen, so we go on to  to the error handling bit that
10707c478bd9Sstevel@tonic-gate 		 * follows, * which will reset the controller then restart the
10717c478bd9Sstevel@tonic-gate 		 * whole transaction.
10727c478bd9Sstevel@tonic-gate 		 *
10737c478bd9Sstevel@tonic-gate 		 * In all cases, clear "poll_retries" for the next command or
10747c478bd9Sstevel@tonic-gate 		 * retry
10757c478bd9Sstevel@tonic-gate 		 */
10767c478bd9Sstevel@tonic-gate 		smbus->smbus_poll_retries = 0;
10777c478bd9Sstevel@tonic-gate 	}
10787c478bd9Sstevel@tonic-gate 
10797c478bd9Sstevel@tonic-gate 	/*
10807c478bd9Sstevel@tonic-gate 	 * A bug in southbridge SMBUS sometimes requires a reset.  Status
10817c478bd9Sstevel@tonic-gate 	 * should NOT be IDLE without any other bit set.  If it is, the
10827c478bd9Sstevel@tonic-gate 	 * transaction should be restarted.
10837c478bd9Sstevel@tonic-gate 	 */
10847c478bd9Sstevel@tonic-gate 
10857c478bd9Sstevel@tonic-gate 	if (status == IDLE) {
10867c478bd9Sstevel@tonic-gate 		(void) sprintf(error_str, "%s bus is idle, ", error_str);
10877c478bd9Sstevel@tonic-gate 	}
10887c478bd9Sstevel@tonic-gate 
10897c478bd9Sstevel@tonic-gate 	if ((status & CMD_CMPL) == 0) {
10907c478bd9Sstevel@tonic-gate 		(void) sprintf(error_str, "%s command failed to complete, ",
10917c478bd9Sstevel@tonic-gate 		    error_str);
10927c478bd9Sstevel@tonic-gate 	}
10937c478bd9Sstevel@tonic-gate 	if (status & BUS_ERR) {
10947c478bd9Sstevel@tonic-gate 		(void) sprintf(error_str, "%s bus error, ", error_str);
10957c478bd9Sstevel@tonic-gate 	}
10967c478bd9Sstevel@tonic-gate 	if (status & FAILED) {
10977c478bd9Sstevel@tonic-gate 		(void) sprintf(error_str, "%s failed transaction, ", error_str);
10987c478bd9Sstevel@tonic-gate 	}
10997c478bd9Sstevel@tonic-gate 	if (status & DRV_ERR) {
11007c478bd9Sstevel@tonic-gate 		(void) sprintf(error_str, "%s timeout or bus reset", error_str);
11017c478bd9Sstevel@tonic-gate 	}
11027c478bd9Sstevel@tonic-gate 
11037c478bd9Sstevel@tonic-gate 	if (error_str[0] != '\0') {
11047c478bd9Sstevel@tonic-gate 		(void) sprintf(error_str, "%s %s ", error_str, src);
11057c478bd9Sstevel@tonic-gate 	}
11067c478bd9Sstevel@tonic-gate 
11077c478bd9Sstevel@tonic-gate 	/*
11087c478bd9Sstevel@tonic-gate 	 * Clear status to clear the interrupt.
11097c478bd9Sstevel@tonic-gate 	 */
11107c478bd9Sstevel@tonic-gate 	smbus_put(smbus, SMB_STS, 0xff, SMBUS_FLUSH);
11117c478bd9Sstevel@tonic-gate 	if (error_str[0] != '\0') {
11127c478bd9Sstevel@tonic-gate 		smbus_put(smbus, SMB_TYP, KILL, SMBUS_FLUSH);
11137c478bd9Sstevel@tonic-gate 		if (smbus->smbus_retries++ < SMBUS_MAX_RETRIES) {
11147c478bd9Sstevel@tonic-gate 			/*
11157c478bd9Sstevel@tonic-gate 			 * XXXX There was a panic here when the
11167c478bd9Sstevel@tonic-gate 			 * intr timeout was greater than the timeout
11177c478bd9Sstevel@tonic-gate 			 * for the entire transfer.
11187c478bd9Sstevel@tonic-gate 			 *
11197c478bd9Sstevel@tonic-gate 			 * Restore the value of w_resid before the
11207c478bd9Sstevel@tonic-gate 			 * last transaction.  r_resid doesn't need to
11217c478bd9Sstevel@tonic-gate 			 * be restored because it is only decremented
11227c478bd9Sstevel@tonic-gate 			 * after a successful read.  Need to do this
11237c478bd9Sstevel@tonic-gate 			 * here since smbus_switch() keys off of a
11247c478bd9Sstevel@tonic-gate 			 * resid to know whether to call smbus_rd() or
11257c478bd9Sstevel@tonic-gate 			 * smbus_wr().
11267c478bd9Sstevel@tonic-gate 			 */
11277c478bd9Sstevel@tonic-gate 			tp->i2c_w_resid = smbus->smbus_saved_w_resid;
11287c478bd9Sstevel@tonic-gate 			smbus->smbus_bytes_to_read = 0;
11297c478bd9Sstevel@tonic-gate 
11307c478bd9Sstevel@tonic-gate 			SMBUS_PRINT((PRT_INTR_ERR,
11317c478bd9Sstevel@tonic-gate 			    "retrying: %s %s w_resid=%d\n", error_str,
11327c478bd9Sstevel@tonic-gate 			    src, tp->i2c_w_resid));
11337c478bd9Sstevel@tonic-gate 		} else {
11347c478bd9Sstevel@tonic-gate 			cmn_err(CE_WARN, "%s max retries exceeded: %s",
11357c478bd9Sstevel@tonic-gate 			    smbus->smbus_name, error_str);
11367c478bd9Sstevel@tonic-gate 			/*
11377c478bd9Sstevel@tonic-gate 			 * bailing, but first will reset the bus.
11387c478bd9Sstevel@tonic-gate 			 */
11397c478bd9Sstevel@tonic-gate 			smbus_put(smbus, SMB_TYP, KILL, SMBUS_FLUSH);
11407c478bd9Sstevel@tonic-gate 			smbus_put(smbus, SMB_STS, 0xff, SMBUS_FLUSH);
11417c478bd9Sstevel@tonic-gate 			smbus->smbus_cur_tran->i2c_result = I2C_FAILURE;
11427c478bd9Sstevel@tonic-gate 
11437c478bd9Sstevel@tonic-gate 			ret = SMBUS_FAILURE;
11447c478bd9Sstevel@tonic-gate 		}
11457c478bd9Sstevel@tonic-gate 	} else {
11467c478bd9Sstevel@tonic-gate 		smbus->smbus_retries = 0;
11477c478bd9Sstevel@tonic-gate 	}
11487c478bd9Sstevel@tonic-gate 
11497c478bd9Sstevel@tonic-gate 	if (tp != NULL) {
11507c478bd9Sstevel@tonic-gate 		SMBUS_PRINT((PRT_INTR, "flags=%d  wresid=%d r_resid=%d %s\n",
11517c478bd9Sstevel@tonic-gate 		    tp->i2c_flags, tp->i2c_w_resid, tp->i2c_r_resid, src));
11527c478bd9Sstevel@tonic-gate 	}
11537c478bd9Sstevel@tonic-gate 
11547c478bd9Sstevel@tonic-gate 	if (ret != SMBUS_FAILURE) {
11557c478bd9Sstevel@tonic-gate 		ret = smbus_switch(smbus);
11567c478bd9Sstevel@tonic-gate 	}
11577c478bd9Sstevel@tonic-gate 
11587c478bd9Sstevel@tonic-gate 	if (smbus->smbus_polling) {
11597c478bd9Sstevel@tonic-gate 		if (ret == SMBUS_COMPLETE || ret == SMBUS_FAILURE) {
11607c478bd9Sstevel@tonic-gate 			smbus->smbus_poll_complete = 1;
11617c478bd9Sstevel@tonic-gate 		}
11627c478bd9Sstevel@tonic-gate 	} else {
11637c478bd9Sstevel@tonic-gate 		/*
11647c478bd9Sstevel@tonic-gate 		 * Disable previous timeout.  In case it was about to fire this
11657c478bd9Sstevel@tonic-gate 		 * will let it exit without doing anything.
11667c478bd9Sstevel@tonic-gate 		 */
11677c478bd9Sstevel@tonic-gate 		timer_id = smbus->smbus_timeout;
11687c478bd9Sstevel@tonic-gate 		smbus->smbus_timeout = 0;
11697c478bd9Sstevel@tonic-gate 		mutex_exit(&smbus->smbus_imutex);
11707c478bd9Sstevel@tonic-gate 		(void) untimeout(timer_id);
11717c478bd9Sstevel@tonic-gate 		mutex_enter(&smbus->smbus_imutex);
11727c478bd9Sstevel@tonic-gate 		if (ret == SMBUS_COMPLETE || ret == SMBUS_FAILURE) {
11737c478bd9Sstevel@tonic-gate 			cv_signal(&smbus->smbus_icv);
11747c478bd9Sstevel@tonic-gate 		} else {
11757c478bd9Sstevel@tonic-gate 			smbus_interrupts_on(smbus);
11767c478bd9Sstevel@tonic-gate 			smbus->smbus_timeout = timeout(smbus_intr_timeout,
11777c478bd9Sstevel@tonic-gate 			    smbus, drv_usectohz(intr_timeout));
11787c478bd9Sstevel@tonic-gate 			SMBUS_PRINT((PRT_INTR, "smbus_intr starting timeout %p "
1179*19397407SSherry Moore 			    "%s", smbus->smbus_timeout, src));
11807c478bd9Sstevel@tonic-gate 		}
11817c478bd9Sstevel@tonic-gate 	}
11827c478bd9Sstevel@tonic-gate 
11837c478bd9Sstevel@tonic-gate 	return (DDI_INTR_CLAIMED);
11847c478bd9Sstevel@tonic-gate }
11857c478bd9Sstevel@tonic-gate 
11867c478bd9Sstevel@tonic-gate /*
11877c478bd9Sstevel@tonic-gate  * smbus_wr handles writes to the smbus.  Unlike true I2C busses
11887c478bd9Sstevel@tonic-gate  * such as provided by pcf8584, smbus attaches a start and stop bit for each
11897c478bd9Sstevel@tonic-gate  * transaction, so this limits writes to the maximum number of bytes
11907c478bd9Sstevel@tonic-gate  * in a single transaction, which is 33.
11917c478bd9Sstevel@tonic-gate  *
11927c478bd9Sstevel@tonic-gate  * If more than 33 bytes are contained in the transfer, a non-zero
11937c478bd9Sstevel@tonic-gate  * residual has to be returned, and the calling driver has to restart
11947c478bd9Sstevel@tonic-gate  * another transaction to complete writing out any remaining data.  The
11957c478bd9Sstevel@tonic-gate  * reason for this is that most devices require a register/offset as the
11967c478bd9Sstevel@tonic-gate  * first byte to be written for each SMBUS transaction.
11977c478bd9Sstevel@tonic-gate  */
11987c478bd9Sstevel@tonic-gate static int
smbus_wr(smbus_t * smbus)11997c478bd9Sstevel@tonic-gate smbus_wr(smbus_t *smbus)
12007c478bd9Sstevel@tonic-gate {
12017c478bd9Sstevel@tonic-gate 	i2c_transfer_t *tp = smbus->smbus_cur_tran;
12027c478bd9Sstevel@tonic-gate 	uint8_t addr = smbus_dip_to_addr(smbus->smbus_cur_dip);
12037c478bd9Sstevel@tonic-gate 	int bytes_written = 0;
12047c478bd9Sstevel@tonic-gate 	uint8_t a;
12057c478bd9Sstevel@tonic-gate 	uint8_t b;
12067c478bd9Sstevel@tonic-gate 
12077c478bd9Sstevel@tonic-gate 	if (tp->i2c_w_resid != tp->i2c_wlen) {
12087c478bd9Sstevel@tonic-gate 		return (SMBUS_COMPLETE);
12097c478bd9Sstevel@tonic-gate 	}
12107c478bd9Sstevel@tonic-gate 
12117c478bd9Sstevel@tonic-gate 	SMBUS_PRINT((PRT_WR, "smbus_wr:  addr = %x resid = %d\n",
12127c478bd9Sstevel@tonic-gate 	    addr, tp->i2c_w_resid));
12137c478bd9Sstevel@tonic-gate 
12147c478bd9Sstevel@tonic-gate 	smbus_put(smbus, SMB_STS, 0xff, 0);
12157c478bd9Sstevel@tonic-gate 
12167c478bd9Sstevel@tonic-gate 	/*
12177c478bd9Sstevel@tonic-gate 	 * Address must be re-written for each command and it has to
12187c478bd9Sstevel@tonic-gate 	 * be written before SMB_TYP.
12197c478bd9Sstevel@tonic-gate 	 */
12207c478bd9Sstevel@tonic-gate 	smbus_put(smbus, DEV_ADDR, addr, 0);
12217c478bd9Sstevel@tonic-gate 
12227c478bd9Sstevel@tonic-gate 	switch (tp->i2c_w_resid) {
12237c478bd9Sstevel@tonic-gate 
12247c478bd9Sstevel@tonic-gate 	case 1:
12257c478bd9Sstevel@tonic-gate 		a = tp->i2c_wbuf[tp->i2c_wlen - tp->i2c_w_resid--];
12267c478bd9Sstevel@tonic-gate 		smbus_put(smbus, SMB_CMD, a, 0);
12277c478bd9Sstevel@tonic-gate 		smbus_put(smbus, SMB_TYP, SEND_BYTE, 0);
12287c478bd9Sstevel@tonic-gate 		SMBUS_PRINT((PRT_WR, "smbus_wr: send one byte:"
12297c478bd9Sstevel@tonic-gate 		    " %d\n", a));
12307c478bd9Sstevel@tonic-gate 		break;
12317c478bd9Sstevel@tonic-gate 	case 2:
12327c478bd9Sstevel@tonic-gate 		a = tp->i2c_wbuf[tp->i2c_wlen - tp->i2c_w_resid--];
12337c478bd9Sstevel@tonic-gate 		smbus_put(smbus, SMB_CMD, a, 0);
12347c478bd9Sstevel@tonic-gate 
12357c478bd9Sstevel@tonic-gate 		b = tp->i2c_wbuf[tp->i2c_wlen - tp->i2c_w_resid--];
12367c478bd9Sstevel@tonic-gate 		smbus_put(smbus, DEV_DATA0, b, 0);
12377c478bd9Sstevel@tonic-gate 		smbus_put(smbus, SMB_TYP, WR_BYTE, 0);
12387c478bd9Sstevel@tonic-gate 		SMBUS_PRINT((PRT_WR, "smbus_wr: send two bytes:"
12397c478bd9Sstevel@tonic-gate 		    " %d %d\n", a, b));
12407c478bd9Sstevel@tonic-gate 		break;
12417c478bd9Sstevel@tonic-gate 
12427c478bd9Sstevel@tonic-gate 	default:
12437c478bd9Sstevel@tonic-gate 		/*
12447c478bd9Sstevel@tonic-gate 		 * Write out as many bytes as possible in a single command.
12457c478bd9Sstevel@tonic-gate 		 * Note that BLK_DATA just creats a byte stream.  ie, the
12467c478bd9Sstevel@tonic-gate 		 * smbus protocol is not used or interpreted by this driver.
12477c478bd9Sstevel@tonic-gate 		 */
12487c478bd9Sstevel@tonic-gate 		smbus_put(smbus, SMB_TYP, WR_BLK, 0);
12497c478bd9Sstevel@tonic-gate 		a = tp->i2c_wbuf[tp->i2c_wlen - tp->i2c_w_resid--];
12507c478bd9Sstevel@tonic-gate 
12517c478bd9Sstevel@tonic-gate 		smbus_put(smbus, SMB_CMD, a, 0);
12527c478bd9Sstevel@tonic-gate 
12537c478bd9Sstevel@tonic-gate 		SMBUS_PRINT((PRT_WR, "smbus_wr: send multiple bytes: "));
12547c478bd9Sstevel@tonic-gate 		SMBUS_PRINT((PRT_WR, "%x ", a));
12557c478bd9Sstevel@tonic-gate 
12567c478bd9Sstevel@tonic-gate 		while (tp->i2c_w_resid != 0) {
12577c478bd9Sstevel@tonic-gate 			a = tp->i2c_wbuf[tp->i2c_wlen - tp->i2c_w_resid--];
12587c478bd9Sstevel@tonic-gate 			smbus_put(smbus, BLK_DATA, a, 0);
12597c478bd9Sstevel@tonic-gate 			SMBUS_PRINT((PRT_WR, "%x ", a));
12607c478bd9Sstevel@tonic-gate 			/*
12617c478bd9Sstevel@tonic-gate 			 * Note that MAX_BLK_SEND defines how many bytes may
12627c478bd9Sstevel@tonic-gate 			 * be sent to the BLK_DATA register. The leading byte
12637c478bd9Sstevel@tonic-gate 			 * already sent to the SMB_CMD register doesn't count
12647c478bd9Sstevel@tonic-gate 			 * But ALL the BLK_DATA bytes count so pre-increment
12657c478bd9Sstevel@tonic-gate 			 * bytes_written before testing.
12667c478bd9Sstevel@tonic-gate 			 */
12677c478bd9Sstevel@tonic-gate 			if (++bytes_written == MAX_BLK_SEND) {
12687c478bd9Sstevel@tonic-gate 				break;
12697c478bd9Sstevel@tonic-gate 			}
12707c478bd9Sstevel@tonic-gate 		}
12717c478bd9Sstevel@tonic-gate 		SMBUS_PRINT((PRT_WR, "\n"));
12727c478bd9Sstevel@tonic-gate 		smbus_put(smbus, DEV_DATA0, bytes_written, 0);
12737c478bd9Sstevel@tonic-gate 		break;
12747c478bd9Sstevel@tonic-gate 	}
12757c478bd9Sstevel@tonic-gate 
12767c478bd9Sstevel@tonic-gate 	/*
12777c478bd9Sstevel@tonic-gate 	 * writing anything to port reg starts transfer
12787c478bd9Sstevel@tonic-gate 	 */
12797c478bd9Sstevel@tonic-gate 	smbus_put(smbus, STR_PORT, 0, SMBUS_FLUSH);
12807c478bd9Sstevel@tonic-gate 
12817c478bd9Sstevel@tonic-gate 	return (SMBUS_PENDING);
12827c478bd9Sstevel@tonic-gate }
12837c478bd9Sstevel@tonic-gate 
12847c478bd9Sstevel@tonic-gate /*
12857c478bd9Sstevel@tonic-gate  * smbus_rd handles reads to the smbus.  Unlike a true I2C bus
12867c478bd9Sstevel@tonic-gate  * such as provided by pcf8584, smbus attaches a start and stop bit
12877c478bd9Sstevel@tonic-gate  * for each transaction, which limits reads to the maximum number of
12887c478bd9Sstevel@tonic-gate  * bytes in a single SMBUS transaction.  (Block reads don't
12897c478bd9Sstevel@tonic-gate  * seem to work on smbus, and the southbridge documentation is poor).
12907c478bd9Sstevel@tonic-gate  *
12917c478bd9Sstevel@tonic-gate  * It doesn't appear that reads spanning multiple I2C transactions
12927c478bd9Sstevel@tonic-gate  * (ie each with a start-stop) affects the transfer when reading
12937c478bd9Sstevel@tonic-gate  * multiple bytes from devices with internal counters.  The counter
12947c478bd9Sstevel@tonic-gate  * is correctly maintained.
12957c478bd9Sstevel@tonic-gate  *
12967c478bd9Sstevel@tonic-gate  * RD_WORD and RD_BYTE write out the byte in the SMB_CMD register
12977c478bd9Sstevel@tonic-gate  * before reading, so RCV_BYTE is used instead.
12987c478bd9Sstevel@tonic-gate  *
12997c478bd9Sstevel@tonic-gate  * Multi-byte reads iniatiate a SMBUS transaction for each byte to be
13007c478bd9Sstevel@tonic-gate  * received.  Because register/offset information doesn't need to
13017c478bd9Sstevel@tonic-gate  * be resent for each I2C transaction (as opposed to when writing data),
13027c478bd9Sstevel@tonic-gate  * the driver can continue reading data in separate SMBUS transactions
13037c478bd9Sstevel@tonic-gate  * until the requested buffer is filled.
13047c478bd9Sstevel@tonic-gate  */
13057c478bd9Sstevel@tonic-gate static int
smbus_rd(smbus_t * smbus)13067c478bd9Sstevel@tonic-gate smbus_rd(smbus_t *smbus)
13077c478bd9Sstevel@tonic-gate {
13087c478bd9Sstevel@tonic-gate 	i2c_transfer_t *tp = smbus->smbus_cur_tran;
13097c478bd9Sstevel@tonic-gate 	uint8_t addr = smbus_dip_to_addr(smbus->smbus_cur_dip);
13107c478bd9Sstevel@tonic-gate 
13117c478bd9Sstevel@tonic-gate 	if (smbus->smbus_bytes_to_read == 1) {
13127c478bd9Sstevel@tonic-gate 		tp->i2c_rbuf[tp->i2c_rlen - tp->i2c_r_resid] =
13137c478bd9Sstevel@tonic-gate 		    smbus_get(smbus, DEV_DATA0);
13147c478bd9Sstevel@tonic-gate 		SMBUS_PRINT((PRT_RD, "smbus_rd: data in = %d\n",
13157c478bd9Sstevel@tonic-gate 		    tp->i2c_rbuf[tp->i2c_rlen - tp->i2c_r_resid]));
13167c478bd9Sstevel@tonic-gate 		tp->i2c_r_resid--;
13177c478bd9Sstevel@tonic-gate 		smbus->smbus_bytes_to_read = 0;
13187c478bd9Sstevel@tonic-gate 
13197c478bd9Sstevel@tonic-gate 		if (tp->i2c_r_resid == 0) {
13207c478bd9Sstevel@tonic-gate 			return (SMBUS_COMPLETE);
13217c478bd9Sstevel@tonic-gate 		}
13227c478bd9Sstevel@tonic-gate 	}
13237c478bd9Sstevel@tonic-gate 
13247c478bd9Sstevel@tonic-gate 	/*
13257c478bd9Sstevel@tonic-gate 	 * Address must be re-written for each command.  It must
13267c478bd9Sstevel@tonic-gate 	 * be written before SMB_TYP.
13277c478bd9Sstevel@tonic-gate 	 */
13287c478bd9Sstevel@tonic-gate 	smbus_put(smbus, DEV_ADDR, addr | I2C_READ, 0);
13297c478bd9Sstevel@tonic-gate 
13307c478bd9Sstevel@tonic-gate 	if (tp->i2c_r_resid == 0) {
13317c478bd9Sstevel@tonic-gate 		smbus->smbus_bytes_to_read = 0;
13327c478bd9Sstevel@tonic-gate 
13337c478bd9Sstevel@tonic-gate 		return (SMBUS_COMPLETE);
13347c478bd9Sstevel@tonic-gate 	}
13357c478bd9Sstevel@tonic-gate 
13367c478bd9Sstevel@tonic-gate 	smbus->smbus_bytes_to_read = 1;
13377c478bd9Sstevel@tonic-gate 	smbus_put(smbus, SMB_TYP, RCV_BYTE, 0);
13387c478bd9Sstevel@tonic-gate 
13397c478bd9Sstevel@tonic-gate 	smbus_put(smbus, SMB_STS, 0xff, 0);
13407c478bd9Sstevel@tonic-gate 
13417c478bd9Sstevel@tonic-gate 	SMBUS_PRINT((PRT_RD, "smbus_rd: starting a read addr = %x resid = %d "
13427c478bd9Sstevel@tonic-gate 	    "bytes_to_read=%d\n", addr, tp->i2c_r_resid,
13437c478bd9Sstevel@tonic-gate 	    smbus->smbus_bytes_to_read));
13447c478bd9Sstevel@tonic-gate 
13457c478bd9Sstevel@tonic-gate 	smbus_put(smbus, STR_PORT, 0, SMBUS_FLUSH);
13467c478bd9Sstevel@tonic-gate 
13477c478bd9Sstevel@tonic-gate 	return (SMBUS_PENDING);
13487c478bd9Sstevel@tonic-gate }
1349