xref: /illumos-gate/usr/src/uts/sun4u/io/i2c/misc/i2c_svc.c (revision 88294e09)
17c478bd9Sstevel@tonic-gate /*
2*88294e09SRichard Bean  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
3*88294e09SRichard Bean  * Use is subject to license terms.
47c478bd9Sstevel@tonic-gate  */
57c478bd9Sstevel@tonic-gate 
67c478bd9Sstevel@tonic-gate #include <sys/types.h>
77c478bd9Sstevel@tonic-gate #include <sys/conf.h>
87c478bd9Sstevel@tonic-gate #include <sys/kmem.h>
97c478bd9Sstevel@tonic-gate #include <sys/open.h>
107c478bd9Sstevel@tonic-gate #include <sys/ddi.h>
117c478bd9Sstevel@tonic-gate #include <sys/sunddi.h>
127c478bd9Sstevel@tonic-gate #include <sys/file.h>
137c478bd9Sstevel@tonic-gate #include <sys/modctl.h>
147c478bd9Sstevel@tonic-gate #include <sys/i2c/misc/i2c_svc.h>
157c478bd9Sstevel@tonic-gate #include <sys/i2c/misc/i2c_svc_impl.h>
167c478bd9Sstevel@tonic-gate 
177c478bd9Sstevel@tonic-gate kmutex_t i2c_svc_mutex;
187c478bd9Sstevel@tonic-gate 
197c478bd9Sstevel@tonic-gate static struct modldrv i2c_modldrv = {
207c478bd9Sstevel@tonic-gate 	&mod_miscops,		/* type of module - misc */
21*88294e09SRichard Bean 	"I2C module",
227c478bd9Sstevel@tonic-gate 	NULL,
237c478bd9Sstevel@tonic-gate };
247c478bd9Sstevel@tonic-gate 
257c478bd9Sstevel@tonic-gate static struct modlinkage i2c_modlinkage = {
267c478bd9Sstevel@tonic-gate 	MODREV_1,
277c478bd9Sstevel@tonic-gate 	&i2c_modldrv,
287c478bd9Sstevel@tonic-gate 	0
297c478bd9Sstevel@tonic-gate };
307c478bd9Sstevel@tonic-gate 
317c478bd9Sstevel@tonic-gate i2c_nexus_reg_list_t *nexus_reg_list_head = NULL;
327c478bd9Sstevel@tonic-gate 
337c478bd9Sstevel@tonic-gate int i2csvcdebug = 0;
347c478bd9Sstevel@tonic-gate 
357c478bd9Sstevel@tonic-gate int
_init(void)367c478bd9Sstevel@tonic-gate _init(void)
377c478bd9Sstevel@tonic-gate {
387c478bd9Sstevel@tonic-gate 	int error;
397c478bd9Sstevel@tonic-gate 
407c478bd9Sstevel@tonic-gate 	if ((error = mod_install(&i2c_modlinkage)) == 0) {
417c478bd9Sstevel@tonic-gate 		mutex_init(&i2c_svc_mutex, NULL, MUTEX_DRIVER, NULL);
427c478bd9Sstevel@tonic-gate 	}
437c478bd9Sstevel@tonic-gate 
447c478bd9Sstevel@tonic-gate 	return (error);
457c478bd9Sstevel@tonic-gate }
467c478bd9Sstevel@tonic-gate 
477c478bd9Sstevel@tonic-gate int
_fini(void)487c478bd9Sstevel@tonic-gate _fini(void)
497c478bd9Sstevel@tonic-gate {
507c478bd9Sstevel@tonic-gate 	int error;
517c478bd9Sstevel@tonic-gate 
527c478bd9Sstevel@tonic-gate 	if ((error = mod_remove(&i2c_modlinkage)) == 0) {
537c478bd9Sstevel@tonic-gate 		mutex_destroy(&i2c_svc_mutex);
547c478bd9Sstevel@tonic-gate 	}
557c478bd9Sstevel@tonic-gate 
567c478bd9Sstevel@tonic-gate 	return (error);
577c478bd9Sstevel@tonic-gate }
587c478bd9Sstevel@tonic-gate 
597c478bd9Sstevel@tonic-gate int
_info(struct modinfo * modinfop)607c478bd9Sstevel@tonic-gate _info(struct modinfo *modinfop)
617c478bd9Sstevel@tonic-gate {
627c478bd9Sstevel@tonic-gate 	return (mod_info(&i2c_modlinkage, modinfop));
637c478bd9Sstevel@tonic-gate }
647c478bd9Sstevel@tonic-gate 
657c478bd9Sstevel@tonic-gate /*
667c478bd9Sstevel@tonic-gate  * i2c_client_register is called by I2C client drivers,
677c478bd9Sstevel@tonic-gate  * typically in attach, but before starting any bus transfers.
687c478bd9Sstevel@tonic-gate  *
697c478bd9Sstevel@tonic-gate  * dip	   - the client device's dip.
707c478bd9Sstevel@tonic-gate  * i2c_hdl - pointer to a handle returned on success.
717c478bd9Sstevel@tonic-gate  *
727c478bd9Sstevel@tonic-gate  */
737c478bd9Sstevel@tonic-gate int
i2c_client_register(dev_info_t * dip,i2c_client_hdl_t * i2c_hdl)747c478bd9Sstevel@tonic-gate i2c_client_register(dev_info_t *dip, i2c_client_hdl_t *i2c_hdl)
757c478bd9Sstevel@tonic-gate {
767c478bd9Sstevel@tonic-gate 	dev_info_t *pdip;
777c478bd9Sstevel@tonic-gate 	i2c_client_hdl_t hdl;
787c478bd9Sstevel@tonic-gate 	i2c_nexus_reg_list_t *reg_list;
797c478bd9Sstevel@tonic-gate 
807c478bd9Sstevel@tonic-gate 	pdip = ddi_get_parent(dip);
817c478bd9Sstevel@tonic-gate 
827c478bd9Sstevel@tonic-gate 	mutex_enter(&i2c_svc_mutex);
837c478bd9Sstevel@tonic-gate 
847c478bd9Sstevel@tonic-gate 	reg_list = nexus_reg_list_head;
857c478bd9Sstevel@tonic-gate 	/*
867c478bd9Sstevel@tonic-gate 	 * search parent reg list to find dip's parent.
877c478bd9Sstevel@tonic-gate 	 */
887c478bd9Sstevel@tonic-gate 	for (; reg_list != NULL; reg_list = reg_list->next) {
897c478bd9Sstevel@tonic-gate 		if (reg_list->dip == pdip) {
907c478bd9Sstevel@tonic-gate 			break;
917c478bd9Sstevel@tonic-gate 		}
927c478bd9Sstevel@tonic-gate 	}
937c478bd9Sstevel@tonic-gate 
947c478bd9Sstevel@tonic-gate 	mutex_exit(&i2c_svc_mutex);
957c478bd9Sstevel@tonic-gate 
967c478bd9Sstevel@tonic-gate 	if (reg_list == NULL) {
977c478bd9Sstevel@tonic-gate 
987c478bd9Sstevel@tonic-gate 		return (I2C_FAILURE);
997c478bd9Sstevel@tonic-gate 	}
1007c478bd9Sstevel@tonic-gate 
1017c478bd9Sstevel@tonic-gate 	hdl = kmem_alloc(sizeof (struct i2c_client_hdl_impl), KM_SLEEP);
1027c478bd9Sstevel@tonic-gate 
1037c478bd9Sstevel@tonic-gate 	CHDL(hdl)->chdl_dip = dip;
1047c478bd9Sstevel@tonic-gate 	CHDL(hdl)->chdl_nexus_reg = &reg_list->nexus_reg;
1057c478bd9Sstevel@tonic-gate 	*i2c_hdl = hdl;
1067c478bd9Sstevel@tonic-gate 
1077c478bd9Sstevel@tonic-gate 	return (I2C_SUCCESS);
1087c478bd9Sstevel@tonic-gate }
1097c478bd9Sstevel@tonic-gate 
1107c478bd9Sstevel@tonic-gate /*
1117c478bd9Sstevel@tonic-gate  * i2c_client_unregister() is called by the I2C client driver
1127c478bd9Sstevel@tonic-gate  * when it no longer wishes to transmit on the I2C bus, typically
1137c478bd9Sstevel@tonic-gate  * during its detach routine.
1147c478bd9Sstevel@tonic-gate  *
1157c478bd9Sstevel@tonic-gate  * hdl - handle previously returned by i2c_client_register().
1167c478bd9Sstevel@tonic-gate  *
1177c478bd9Sstevel@tonic-gate  */
1187c478bd9Sstevel@tonic-gate void
i2c_client_unregister(i2c_client_hdl_t hdl)1197c478bd9Sstevel@tonic-gate i2c_client_unregister(i2c_client_hdl_t hdl)
1207c478bd9Sstevel@tonic-gate {
1217c478bd9Sstevel@tonic-gate 	kmem_free(hdl, sizeof (struct i2c_client_hdl_impl));
1227c478bd9Sstevel@tonic-gate }
1237c478bd9Sstevel@tonic-gate 
1247c478bd9Sstevel@tonic-gate /*
1257c478bd9Sstevel@tonic-gate  * i2c_transfer() is called by client drivers to handle
1267c478bd9Sstevel@tonic-gate  * I2C data transfers.  It performs some basic sanity checking of
1277c478bd9Sstevel@tonic-gate  * flags vs. i2c_len and i2c_wlen values, and then calls the
1287c478bd9Sstevel@tonic-gate  * parent's i2c_transfer() function to handle the actual transfer.
1297c478bd9Sstevel@tonic-gate  */
1307c478bd9Sstevel@tonic-gate int
i2c_transfer(i2c_client_hdl_t hdl,i2c_transfer_t * i2c_tran)1317c478bd9Sstevel@tonic-gate i2c_transfer(i2c_client_hdl_t hdl, i2c_transfer_t *i2c_tran)
1327c478bd9Sstevel@tonic-gate {
1337c478bd9Sstevel@tonic-gate 	switch (i2c_tran->i2c_flags) {
1347c478bd9Sstevel@tonic-gate 	case I2C_WR:
1357c478bd9Sstevel@tonic-gate 		if (i2c_tran->i2c_wlen == 0) {
1367c478bd9Sstevel@tonic-gate 
1377c478bd9Sstevel@tonic-gate 			return (EINVAL);
1387c478bd9Sstevel@tonic-gate 		}
1397c478bd9Sstevel@tonic-gate 		break;
1407c478bd9Sstevel@tonic-gate 	case I2C_RD:
1417c478bd9Sstevel@tonic-gate 		if (i2c_tran->i2c_rlen == 0) {
1427c478bd9Sstevel@tonic-gate 
1437c478bd9Sstevel@tonic-gate 			return (EINVAL);
1447c478bd9Sstevel@tonic-gate 		}
1457c478bd9Sstevel@tonic-gate 		break;
1467c478bd9Sstevel@tonic-gate 	case I2C_WR_RD:
1477c478bd9Sstevel@tonic-gate 		if (i2c_tran->i2c_wlen == 0 || i2c_tran->i2c_rlen == 0) {
1487c478bd9Sstevel@tonic-gate 
1497c478bd9Sstevel@tonic-gate 			return (EINVAL);
1507c478bd9Sstevel@tonic-gate 		}
1517c478bd9Sstevel@tonic-gate 		break;
1527c478bd9Sstevel@tonic-gate 	default:
1537c478bd9Sstevel@tonic-gate 
1547c478bd9Sstevel@tonic-gate 		return (EINVAL);
1557c478bd9Sstevel@tonic-gate 	}
1567c478bd9Sstevel@tonic-gate 
1577c478bd9Sstevel@tonic-gate 	if (CHDL(hdl)->chdl_nexus_reg->i2c_nexus_transfer != NULL) {
1587c478bd9Sstevel@tonic-gate 		(*CHDL(hdl)->chdl_nexus_reg->i2c_nexus_transfer)
1597c478bd9Sstevel@tonic-gate 				(CHDL(hdl)->chdl_dip, i2c_tran);
1607c478bd9Sstevel@tonic-gate 
1617c478bd9Sstevel@tonic-gate 		return (i2c_tran->i2c_result);
1627c478bd9Sstevel@tonic-gate 	} else {
1637c478bd9Sstevel@tonic-gate 
1647c478bd9Sstevel@tonic-gate 		return (ENOTSUP);
1657c478bd9Sstevel@tonic-gate 	}
1667c478bd9Sstevel@tonic-gate }
1677c478bd9Sstevel@tonic-gate 
1687c478bd9Sstevel@tonic-gate /*
1697c478bd9Sstevel@tonic-gate  * i2c_transfer_alloc() allocates a i2c_transfer structure along
1707c478bd9Sstevel@tonic-gate  * with read and write buffers of size rlen and wlen respectively.
1717c478bd9Sstevel@tonic-gate  *
1727c478bd9Sstevel@tonic-gate  * i2c_hdl - handle returned previously by i2c_client_register()
1737c478bd9Sstevel@tonic-gate  * i2c - address of pointer to allocated buffer returned on success.
1747c478bd9Sstevel@tonic-gate  * wlen - write size buffer to allocate.  May be 0.
1757c478bd9Sstevel@tonic-gate  * rlen - read size buffer to allocate.  May be 0.
1767c478bd9Sstevel@tonic-gate  * flags - I2C_SLEEP or I2C_NOSLEEP
1777c478bd9Sstevel@tonic-gate  */
1787c478bd9Sstevel@tonic-gate /*ARGSUSED*/
1797c478bd9Sstevel@tonic-gate int
i2c_transfer_alloc(i2c_client_hdl_t hdl,i2c_transfer_t ** i2c,ushort_t wlen,ushort_t rlen,uint_t flags)1807c478bd9Sstevel@tonic-gate i2c_transfer_alloc(i2c_client_hdl_t hdl,
1817c478bd9Sstevel@tonic-gate 			i2c_transfer_t **i2c,
1827c478bd9Sstevel@tonic-gate 			ushort_t wlen,
1837c478bd9Sstevel@tonic-gate 			ushort_t rlen,
1847c478bd9Sstevel@tonic-gate 			uint_t flags)
1857c478bd9Sstevel@tonic-gate {
1867c478bd9Sstevel@tonic-gate 	i2c_transfer_alloc_t *i2cw;
1877c478bd9Sstevel@tonic-gate 	int sleep;
1887c478bd9Sstevel@tonic-gate 	int size;
1897c478bd9Sstevel@tonic-gate 
1907ff1e9f5Szk 	/*
1917ff1e9f5Szk 	 * set i2c to NULL in case the caller just checks i2c
1927ff1e9f5Szk 	 * to determine failures.
1937ff1e9f5Szk 	 */
1947ff1e9f5Szk 	*i2c = NULL;
1957ff1e9f5Szk 
1967c478bd9Sstevel@tonic-gate 	if (flags & I2C_SLEEP) {
1977c478bd9Sstevel@tonic-gate 		sleep = KM_SLEEP;
1987c478bd9Sstevel@tonic-gate 	} else if (flags & I2C_NOSLEEP) {
1997c478bd9Sstevel@tonic-gate 		sleep = KM_NOSLEEP;
2007c478bd9Sstevel@tonic-gate 	} else {
2017c478bd9Sstevel@tonic-gate 		sleep = KM_NOSLEEP;
2027c478bd9Sstevel@tonic-gate 	}
2037c478bd9Sstevel@tonic-gate 
2047c478bd9Sstevel@tonic-gate 	size = sizeof (i2c_transfer_alloc_t) + rlen + wlen;
2057c478bd9Sstevel@tonic-gate 
2067c478bd9Sstevel@tonic-gate 	if ((i2cw = kmem_zalloc(size, sleep)) == NULL) {
2077c478bd9Sstevel@tonic-gate 
2087c478bd9Sstevel@tonic-gate 		return (I2C_FAILURE);
2097c478bd9Sstevel@tonic-gate 	}
2107c478bd9Sstevel@tonic-gate 
2117c478bd9Sstevel@tonic-gate 	i2cw->i2cw_size = size;
2127c478bd9Sstevel@tonic-gate 	i2cw->i2cw_i2ct.i2c_wlen = wlen;
2137c478bd9Sstevel@tonic-gate 	i2cw->i2cw_i2ct.i2c_rlen = rlen;
2147c478bd9Sstevel@tonic-gate 	if (wlen != 0) {
2157c478bd9Sstevel@tonic-gate 		i2cw->i2cw_i2ct.i2c_wbuf = (uchar_t *)i2cw +
2167c478bd9Sstevel@tonic-gate 			sizeof (i2c_transfer_alloc_t);
2177c478bd9Sstevel@tonic-gate 	}
2187c478bd9Sstevel@tonic-gate 	if (rlen != 0) {
2197c478bd9Sstevel@tonic-gate 		i2cw->i2cw_i2ct.i2c_rbuf = (uchar_t *)i2cw +
2207c478bd9Sstevel@tonic-gate 			sizeof (i2c_transfer_alloc_t) + wlen;
2217c478bd9Sstevel@tonic-gate 	}
2227c478bd9Sstevel@tonic-gate 	*i2c = (i2c_transfer_t *)i2cw;
2237c478bd9Sstevel@tonic-gate 
2247c478bd9Sstevel@tonic-gate 	return (I2C_SUCCESS);
2257c478bd9Sstevel@tonic-gate }
2267c478bd9Sstevel@tonic-gate 
2277c478bd9Sstevel@tonic-gate /*
2287c478bd9Sstevel@tonic-gate  * i2c_transfer_free() is called to free a buffer previously
2297c478bd9Sstevel@tonic-gate  * allocated by i2c_transfer_allocate().
2307c478bd9Sstevel@tonic-gate  *
2317c478bd9Sstevel@tonic-gate  * i2c_hdl - handle returned previously by i2c_client_register()
2327c478bd9Sstevel@tonic-gate  * i2c - buffer previously allocated by i2c_transfer_allocate()
2337c478bd9Sstevel@tonic-gate  */
2347c478bd9Sstevel@tonic-gate /*ARGSUSED*/
2357c478bd9Sstevel@tonic-gate void
i2c_transfer_free(i2c_client_hdl_t hdl,i2c_transfer_t * i2c_tran)2367c478bd9Sstevel@tonic-gate i2c_transfer_free(i2c_client_hdl_t hdl, i2c_transfer_t *i2c_tran)
2377c478bd9Sstevel@tonic-gate {
2387c478bd9Sstevel@tonic-gate 	i2c_transfer_alloc_t *i2cw = (i2c_transfer_alloc_t *)i2c_tran;
2397c478bd9Sstevel@tonic-gate 
2407c478bd9Sstevel@tonic-gate 	kmem_free(i2cw, i2cw->i2cw_size);
2417c478bd9Sstevel@tonic-gate }
2427c478bd9Sstevel@tonic-gate 
2437c478bd9Sstevel@tonic-gate /*
2447c478bd9Sstevel@tonic-gate  * i2c_nexus_register() is called by the nexus driver to inform
2457c478bd9Sstevel@tonic-gate  * I2C services that it is ready to accept transactions, and
2467c478bd9Sstevel@tonic-gate  * give the I2C services a vector of functions.
2477c478bd9Sstevel@tonic-gate  *
2487c478bd9Sstevel@tonic-gate  * dip - dip of the bus controller
2497c478bd9Sstevel@tonic-gate  * nexus_reg - pointer to reg structure of vector functions
2507c478bd9Sstevel@tonic-gate  */
2517c478bd9Sstevel@tonic-gate void
i2c_nexus_register(dev_info_t * dip,i2c_nexus_reg_t * nexus_reg)2527c478bd9Sstevel@tonic-gate i2c_nexus_register(dev_info_t *dip, i2c_nexus_reg_t *nexus_reg)
2537c478bd9Sstevel@tonic-gate {
2547c478bd9Sstevel@tonic-gate 	i2c_nexus_reg_list_t *nexus_reglist;
2557c478bd9Sstevel@tonic-gate 
2567c478bd9Sstevel@tonic-gate 	nexus_reglist = kmem_alloc(sizeof (struct i2c_nexus_reg_list),
2577c478bd9Sstevel@tonic-gate 		KM_SLEEP);
2587c478bd9Sstevel@tonic-gate 
2597c478bd9Sstevel@tonic-gate 	mutex_enter(&i2c_svc_mutex);
2607c478bd9Sstevel@tonic-gate 	nexus_reglist->next = nexus_reg_list_head;
2617c478bd9Sstevel@tonic-gate 	nexus_reg_list_head = nexus_reglist;
2627c478bd9Sstevel@tonic-gate 	mutex_exit(&i2c_svc_mutex);
2637c478bd9Sstevel@tonic-gate 
2647c478bd9Sstevel@tonic-gate 	nexus_reglist->nexus_reg = *nexus_reg;
2657c478bd9Sstevel@tonic-gate 	nexus_reglist->dip = dip;
2667c478bd9Sstevel@tonic-gate }
2677c478bd9Sstevel@tonic-gate 
2687c478bd9Sstevel@tonic-gate /*
2697c478bd9Sstevel@tonic-gate  * i2c_nexus_unregister() is called by the nexus driver when
2707c478bd9Sstevel@tonic-gate  * it is no longer able to accept transactions for its I2C
2717c478bd9Sstevel@tonic-gate  * children.
2727c478bd9Sstevel@tonic-gate  *
2737c478bd9Sstevel@tonic-gate  * dip - dev_info pointer passed to i2c_nexus_register().
2747c478bd9Sstevel@tonic-gate  */
2757c478bd9Sstevel@tonic-gate void
i2c_nexus_unregister(dev_info_t * dip)2767c478bd9Sstevel@tonic-gate i2c_nexus_unregister(dev_info_t *dip)
2777c478bd9Sstevel@tonic-gate {
2787c478bd9Sstevel@tonic-gate 	i2c_nexus_reg_list_t **reg_list;
2797c478bd9Sstevel@tonic-gate 	i2c_nexus_reg_list_t *save = NULL;
2807c478bd9Sstevel@tonic-gate 
2817c478bd9Sstevel@tonic-gate 	mutex_enter(&i2c_svc_mutex);
2827c478bd9Sstevel@tonic-gate 
2837c478bd9Sstevel@tonic-gate 	reg_list = &nexus_reg_list_head;
2847c478bd9Sstevel@tonic-gate 
2857c478bd9Sstevel@tonic-gate 	/*
2867c478bd9Sstevel@tonic-gate 	 * reg_list is the address of the pointer to an element on
2877c478bd9Sstevel@tonic-gate 	 * the reg list.  It starts out being the address of the
2887c478bd9Sstevel@tonic-gate 	 * list head, but then is changed to the address of the
2897c478bd9Sstevel@tonic-gate 	 * next pointer in a list element.  Once the element to
2907c478bd9Sstevel@tonic-gate 	 * delete is found, then we change the pointer to the
2917c478bd9Sstevel@tonic-gate 	 * address found in the next pointer of the element to
2927c478bd9Sstevel@tonic-gate 	 * be deleted.
2937c478bd9Sstevel@tonic-gate 	 */
2947c478bd9Sstevel@tonic-gate 	for (; *reg_list != NULL; reg_list = &(*reg_list)->next) {
2957c478bd9Sstevel@tonic-gate 		if ((*reg_list)->dip == dip) {
2967c478bd9Sstevel@tonic-gate 			save = *reg_list;
2977c478bd9Sstevel@tonic-gate 			/* prev next pointer adjusted to point */
2987c478bd9Sstevel@tonic-gate 			*reg_list = (*reg_list)->next;
2997c478bd9Sstevel@tonic-gate 			break;
3007c478bd9Sstevel@tonic-gate 		}
3017c478bd9Sstevel@tonic-gate 	}
3027c478bd9Sstevel@tonic-gate 	mutex_exit(&i2c_svc_mutex);
3037c478bd9Sstevel@tonic-gate 	if (save != NULL) {
3047c478bd9Sstevel@tonic-gate 		kmem_free(save, sizeof (i2c_nexus_reg_list_t));
3057c478bd9Sstevel@tonic-gate 	} else {
3067c478bd9Sstevel@tonic-gate 		cmn_err(CE_WARN, "could not find nexus reg to free");
3077c478bd9Sstevel@tonic-gate 	}
3087c478bd9Sstevel@tonic-gate }
309