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