xref: /netbsd-src/sys/dev/spi/spi.c (revision 47bd93c346b4fd92f550964b932b614353ffa781)
1*47bd93c3Sandvar /* $NetBSD: spi.c,v 1.26 2022/05/17 05:05:20 andvar Exp $ */
25c050c46Sgdamore 
35c050c46Sgdamore /*-
45c050c46Sgdamore  * Copyright (c) 2006 Urbana-Champaign Independent Media Center.
55c050c46Sgdamore  * Copyright (c) 2006 Garrett D'Amore.
65c050c46Sgdamore  * All rights reserved.
75c050c46Sgdamore  *
85c050c46Sgdamore  * Portions of this code were written by Garrett D'Amore for the
95c050c46Sgdamore  * Champaign-Urbana Community Wireless Network Project.
105c050c46Sgdamore  *
115c050c46Sgdamore  * Redistribution and use in source and binary forms, with or
125c050c46Sgdamore  * without modification, are permitted provided that the following
135c050c46Sgdamore  * conditions are met:
145c050c46Sgdamore  * 1. Redistributions of source code must retain the above copyright
155c050c46Sgdamore  *    notice, this list of conditions and the following disclaimer.
165c050c46Sgdamore  * 2. Redistributions in binary form must reproduce the above
175c050c46Sgdamore  *    copyright notice, this list of conditions and the following
185c050c46Sgdamore  *    disclaimer in the documentation and/or other materials provided
195c050c46Sgdamore  *    with the distribution.
205c050c46Sgdamore  * 3. All advertising materials mentioning features or use of this
215c050c46Sgdamore  *    software must display the following acknowledgements:
225c050c46Sgdamore  *      This product includes software developed by the Urbana-Champaign
235c050c46Sgdamore  *      Independent Media Center.
245c050c46Sgdamore  *	This product includes software developed by Garrett D'Amore.
255c050c46Sgdamore  * 4. Urbana-Champaign Independent Media Center's name and Garrett
265c050c46Sgdamore  *    D'Amore's name may not be used to endorse or promote products
275c050c46Sgdamore  *    derived from this software without specific prior written permission.
285c050c46Sgdamore  *
295c050c46Sgdamore  * THIS SOFTWARE IS PROVIDED BY THE URBANA-CHAMPAIGN INDEPENDENT
305c050c46Sgdamore  * MEDIA CENTER AND GARRETT D'AMORE ``AS IS'' AND ANY EXPRESS OR
315c050c46Sgdamore  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
325c050c46Sgdamore  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
335c050c46Sgdamore  * ARE DISCLAIMED.  IN NO EVENT SHALL THE URBANA-CHAMPAIGN INDEPENDENT
345c050c46Sgdamore  * MEDIA CENTER OR GARRETT D'AMORE BE LIABLE FOR ANY DIRECT, INDIRECT,
355c050c46Sgdamore  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
365c050c46Sgdamore  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
375c050c46Sgdamore  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
385c050c46Sgdamore  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
395c050c46Sgdamore  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
405c050c46Sgdamore  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
415c050c46Sgdamore  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
425c050c46Sgdamore  */
435c050c46Sgdamore 
445c050c46Sgdamore #include <sys/cdefs.h>
45*47bd93c3Sandvar __KERNEL_RCSID(0, "$NetBSD: spi.c,v 1.26 2022/05/17 05:05:20 andvar Exp $");
465c050c46Sgdamore 
475c050c46Sgdamore #include "locators.h"
485c050c46Sgdamore 
495c050c46Sgdamore #include <sys/param.h>
505c050c46Sgdamore #include <sys/systm.h>
515c050c46Sgdamore #include <sys/device.h>
52975ce0baSmlelstv #include <sys/conf.h>
535c050c46Sgdamore #include <sys/malloc.h>
54b1d9d10dSrmind #include <sys/mutex.h>
55b1d9d10dSrmind #include <sys/condvar.h>
565c050c46Sgdamore #include <sys/errno.h>
575c050c46Sgdamore 
585c050c46Sgdamore #include <dev/spi/spivar.h>
59975ce0baSmlelstv #include <dev/spi/spi_io.h>
60975ce0baSmlelstv 
61975ce0baSmlelstv #include "ioconf.h"
62975ce0baSmlelstv #include "locators.h"
635c050c46Sgdamore 
645c050c46Sgdamore struct spi_softc {
658128840aSthorpej 	device_t		sc_dev;
665c050c46Sgdamore 	struct spi_controller	sc_controller;
675c050c46Sgdamore 	int			sc_mode;
685c050c46Sgdamore 	int			sc_speed;
69975ce0baSmlelstv 	int			sc_slave;
705c050c46Sgdamore 	int			sc_nslaves;
715c050c46Sgdamore 	struct spi_handle	*sc_slaves;
72975ce0baSmlelstv 	kmutex_t		sc_lock;
73975ce0baSmlelstv 	kcondvar_t		sc_cv;
74b1ce5950Smlelstv 	kmutex_t		sc_dev_lock;
75975ce0baSmlelstv 	int			sc_flags;
76975ce0baSmlelstv #define SPIC_BUSY		1
77975ce0baSmlelstv };
78975ce0baSmlelstv 
79975ce0baSmlelstv static dev_type_open(spi_open);
80975ce0baSmlelstv static dev_type_close(spi_close);
81975ce0baSmlelstv static dev_type_ioctl(spi_ioctl);
82975ce0baSmlelstv 
83975ce0baSmlelstv const struct cdevsw spi_cdevsw = {
84975ce0baSmlelstv 	.d_open = spi_open,
85975ce0baSmlelstv 	.d_close = spi_close,
86975ce0baSmlelstv 	.d_read = noread,
87975ce0baSmlelstv 	.d_write = nowrite,
88975ce0baSmlelstv 	.d_ioctl = spi_ioctl,
89975ce0baSmlelstv 	.d_stop = nostop,
90975ce0baSmlelstv 	.d_tty = notty,
91975ce0baSmlelstv 	.d_poll = nopoll,
92975ce0baSmlelstv 	.d_mmap = nommap,
93975ce0baSmlelstv 	.d_kqfilter = nokqfilter,
94975ce0baSmlelstv 	.d_discard = nodiscard,
95b1ce5950Smlelstv 	.d_flag = D_OTHER | D_MPSAFE
965c050c46Sgdamore };
975c050c46Sgdamore 
985c050c46Sgdamore /*
995c050c46Sgdamore  * SPI slave device.  We have one of these per slave.
1005c050c46Sgdamore  */
1015c050c46Sgdamore struct spi_handle {
1025c050c46Sgdamore 	struct spi_softc	*sh_sc;
1035c050c46Sgdamore 	struct spi_controller	*sh_controller;
1045c050c46Sgdamore 	int			sh_slave;
105975ce0baSmlelstv 	int			sh_mode;
106975ce0baSmlelstv 	int			sh_speed;
107f5ccd400Stnn 	int			sh_flags;
108f5ccd400Stnn #define SPIH_ATTACHED		1
1095c050c46Sgdamore };
1105c050c46Sgdamore 
111975ce0baSmlelstv #define SPI_MAXDATA 4096
112975ce0baSmlelstv 
1135c050c46Sgdamore /*
1145c050c46Sgdamore  * API for bus drivers.
1155c050c46Sgdamore  */
1165c050c46Sgdamore 
1175c050c46Sgdamore int
spibus_print(void * aux,const char * pnp)1185c050c46Sgdamore spibus_print(void *aux, const char *pnp)
1195c050c46Sgdamore {
1205c050c46Sgdamore 
1215c050c46Sgdamore 	if (pnp != NULL)
1225c050c46Sgdamore 		aprint_normal("spi at %s", pnp);
1235c050c46Sgdamore 
1245c050c46Sgdamore 	return (UNCONF);
1255c050c46Sgdamore }
1265c050c46Sgdamore 
1275c050c46Sgdamore 
1285c050c46Sgdamore static int
spi_match(device_t parent,cfdata_t cf,void * aux)1295232253eSxtraeme spi_match(device_t parent, cfdata_t cf, void *aux)
1305c050c46Sgdamore {
1315c050c46Sgdamore 
1325c050c46Sgdamore 	return 1;
1335c050c46Sgdamore }
1345c050c46Sgdamore 
1355c050c46Sgdamore static int
spi_print(void * aux,const char * pnp)1365c050c46Sgdamore spi_print(void *aux, const char *pnp)
1375c050c46Sgdamore {
1385c050c46Sgdamore 	struct spi_attach_args *sa = aux;
1395c050c46Sgdamore 
1405c050c46Sgdamore 	if (sa->sa_handle->sh_slave != -1)
1415c050c46Sgdamore 		aprint_normal(" slave %d", sa->sa_handle->sh_slave);
1425c050c46Sgdamore 
1435c050c46Sgdamore 	return (UNCONF);
1445c050c46Sgdamore }
1455c050c46Sgdamore 
1465c050c46Sgdamore static int
spi_search(device_t parent,cfdata_t cf,const int * ldesc,void * aux)1475232253eSxtraeme spi_search(device_t parent, cfdata_t cf, const int *ldesc, void *aux)
1485c050c46Sgdamore {
1495232253eSxtraeme 	struct spi_softc *sc = device_private(parent);
1505c050c46Sgdamore 	struct spi_attach_args sa;
1515c050c46Sgdamore 	int addr;
1525c050c46Sgdamore 
1535c050c46Sgdamore 	addr = cf->cf_loc[SPICF_SLAVE];
1545c050c46Sgdamore 	if ((addr < 0) || (addr >= sc->sc_controller.sct_nslaves)) {
1555c050c46Sgdamore 		return -1;
1565c050c46Sgdamore 	}
1575c050c46Sgdamore 
158f5ccd400Stnn 	memset(&sa, 0, sizeof sa);
1595c050c46Sgdamore 	sa.sa_handle = &sc->sc_slaves[addr];
160f5ccd400Stnn 	if (ISSET(sa.sa_handle->sh_flags, SPIH_ATTACHED))
161f5ccd400Stnn 		return -1;
1625c050c46Sgdamore 
1632685996bSthorpej 	if (config_probe(parent, cf, &sa)) {
164f5ccd400Stnn 		SET(sa.sa_handle->sh_flags, SPIH_ATTACHED);
165c7fb772bSthorpej 		config_attach(parent, cf, &sa, spi_print, CFARGS_NONE);
166f5ccd400Stnn 	}
1675c050c46Sgdamore 
1685c050c46Sgdamore 	return 0;
1695c050c46Sgdamore }
1705c050c46Sgdamore 
1715c050c46Sgdamore /*
172f5ccd400Stnn  * XXX this is the same as i2c_fill_compat. It could be refactored into a
173f5ccd400Stnn  * common fill_compat function with pointers to compat & ncompat instead
174f5ccd400Stnn  * of attach_args as the first parameter.
175f5ccd400Stnn  */
176f5ccd400Stnn static void
spi_fill_compat(struct spi_attach_args * sa,const char * compat,size_t len,char ** buffer)177f5ccd400Stnn spi_fill_compat(struct spi_attach_args *sa, const char *compat, size_t len,
178f5ccd400Stnn 	char **buffer)
179f5ccd400Stnn {
180f5ccd400Stnn 	int count, i;
181f5ccd400Stnn 	const char *c, *start, **ptr;
182f5ccd400Stnn 
183f5ccd400Stnn 	*buffer = NULL;
184f5ccd400Stnn 	for (i = count = 0, c = compat; i < len; i++, c++)
185f5ccd400Stnn 		if (*c == 0)
186f5ccd400Stnn 			count++;
187f5ccd400Stnn 	count += 2;
188f5ccd400Stnn 	ptr = malloc(sizeof(char*)*count, M_TEMP, M_WAITOK);
189f5ccd400Stnn 	if (!ptr)
190f5ccd400Stnn 		return;
191f5ccd400Stnn 
192f5ccd400Stnn 	for (i = count = 0, start = c = compat; i < len; i++, c++) {
193f5ccd400Stnn 		if (*c == 0) {
194f5ccd400Stnn 			ptr[count++] = start;
195f5ccd400Stnn 			start = c + 1;
196f5ccd400Stnn 		}
197f5ccd400Stnn 	}
198f5ccd400Stnn 	if (start < compat + len) {
199f5ccd400Stnn 		/* last string not 0 terminated */
200f5ccd400Stnn 		size_t l = c - start;
201f5ccd400Stnn 		*buffer = malloc(l + 1, M_TEMP, M_WAITOK);
202f5ccd400Stnn 		memcpy(*buffer, start, l);
203f5ccd400Stnn 		(*buffer)[l] = 0;
204f5ccd400Stnn 		ptr[count++] = *buffer;
205f5ccd400Stnn 	}
206f5ccd400Stnn 	ptr[count] = NULL;
207f5ccd400Stnn 
208f5ccd400Stnn 	sa->sa_compat = ptr;
209f5ccd400Stnn 	sa->sa_ncompat = count;
210f5ccd400Stnn }
211f5ccd400Stnn 
212f5ccd400Stnn static void
spi_direct_attach_child_devices(device_t parent,struct spi_softc * sc,prop_array_t child_devices)213f5ccd400Stnn spi_direct_attach_child_devices(device_t parent, struct spi_softc *sc,
214f5ccd400Stnn     prop_array_t child_devices)
215f5ccd400Stnn {
216f5ccd400Stnn 	unsigned int count;
217f5ccd400Stnn 	prop_dictionary_t child;
218f5ccd400Stnn 	prop_data_t cdata;
219f5ccd400Stnn 	uint32_t slave;
220f5ccd400Stnn 	uint64_t cookie;
221f5ccd400Stnn 	struct spi_attach_args sa;
222f5ccd400Stnn 	int loc[SPICF_NLOCS];
223f5ccd400Stnn 	char *buf;
224f5ccd400Stnn 	int i;
225f5ccd400Stnn 
226f5ccd400Stnn 	memset(loc, 0, sizeof loc);
227f5ccd400Stnn 	count = prop_array_count(child_devices);
228f5ccd400Stnn 	for (i = 0; i < count; i++) {
229f5ccd400Stnn 		child = prop_array_get(child_devices, i);
230f5ccd400Stnn 		if (!child)
231f5ccd400Stnn 			continue;
232f5ccd400Stnn 		if (!prop_dictionary_get_uint32(child, "slave", &slave))
233f5ccd400Stnn 			continue;
234f5ccd400Stnn 		if(slave >= sc->sc_controller.sct_nslaves)
235f5ccd400Stnn 			continue;
236f5ccd400Stnn 		if (!prop_dictionary_get_uint64(child, "cookie", &cookie))
237f5ccd400Stnn 			continue;
238f5ccd400Stnn 		if (!(cdata = prop_dictionary_get(child, "compatible")))
239f5ccd400Stnn 			continue;
240f5ccd400Stnn 		loc[SPICF_SLAVE] = slave;
241f5ccd400Stnn 
242f5ccd400Stnn 		memset(&sa, 0, sizeof sa);
243f5ccd400Stnn 		sa.sa_handle = &sc->sc_slaves[i];
244af6f2f7bShkenken 		sa.sa_prop = child;
245af6f2f7bShkenken 		sa.sa_cookie = cookie;
246f5ccd400Stnn 		if (ISSET(sa.sa_handle->sh_flags, SPIH_ATTACHED))
247f5ccd400Stnn 			continue;
248f5ccd400Stnn 		SET(sa.sa_handle->sh_flags, SPIH_ATTACHED);
249f5ccd400Stnn 
250f5ccd400Stnn 		buf = NULL;
251f5ccd400Stnn 		spi_fill_compat(&sa,
252814a7798Sthorpej 				prop_data_value(cdata),
253f5ccd400Stnn 				prop_data_size(cdata), &buf);
2542685996bSthorpej 		config_found(parent, &sa, spi_print,
255c7fb772bSthorpej 		    CFARGS(.locators = loc));
256f5ccd400Stnn 
257f5ccd400Stnn 		if (sa.sa_compat)
258f5ccd400Stnn 			free(sa.sa_compat, M_TEMP);
259f5ccd400Stnn 		if (buf)
260f5ccd400Stnn 			free(buf, M_TEMP);
261f5ccd400Stnn 	}
262f5ccd400Stnn }
263f5ccd400Stnn 
264f5ccd400Stnn int
spi_compatible_match(const struct spi_attach_args * sa,const cfdata_t cf,const struct device_compatible_entry * compats)265f5ccd400Stnn spi_compatible_match(const struct spi_attach_args *sa, const cfdata_t cf,
266f5ccd400Stnn 		     const struct device_compatible_entry *compats)
267f5ccd400Stnn {
268f5ccd400Stnn 	if (sa->sa_ncompat > 0)
269f5ccd400Stnn 		return device_compatible_match(sa->sa_compat, sa->sa_ncompat,
2701f5ee023Sthorpej 					       compats);
271f5ccd400Stnn 
272f5ccd400Stnn 	return 1;
273f5ccd400Stnn }
274f5ccd400Stnn 
275fc470d40Sthorpej const struct device_compatible_entry *
spi_compatible_lookup(const struct spi_attach_args * sa,const struct device_compatible_entry * compats)276fc470d40Sthorpej spi_compatible_lookup(const struct spi_attach_args *sa,
277fc470d40Sthorpej     const struct device_compatible_entry *compats)
278fc470d40Sthorpej {
279fc470d40Sthorpej 	return device_compatible_lookup(sa->sa_compat, sa->sa_ncompat,
280fc470d40Sthorpej 					compats);
281fc470d40Sthorpej }
282fc470d40Sthorpej 
283f5ccd400Stnn /*
2845c050c46Sgdamore  * API for device drivers.
2855c050c46Sgdamore  *
2865c050c46Sgdamore  * We provide wrapper routines to decouple the ABI for the SPI
2875c050c46Sgdamore  * device drivers from the ABI for the SPI bus drivers.
2885c050c46Sgdamore  */
2895c050c46Sgdamore static void
spi_attach(device_t parent,device_t self,void * aux)2905232253eSxtraeme spi_attach(device_t parent, device_t self, void *aux)
2915c050c46Sgdamore {
2925c050c46Sgdamore 	struct spi_softc *sc = device_private(self);
2935c050c46Sgdamore 	struct spibus_attach_args *sba = aux;
2945c050c46Sgdamore 	int i;
2955c050c46Sgdamore 
2965c050c46Sgdamore 	aprint_naive(": SPI bus\n");
2975c050c46Sgdamore 	aprint_normal(": SPI bus\n");
2985c050c46Sgdamore 
299b1ce5950Smlelstv 	mutex_init(&sc->sc_dev_lock, MUTEX_DEFAULT, IPL_NONE);
30037614169Skardel 	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_VM);
301975ce0baSmlelstv 	cv_init(&sc->sc_cv, "spictl");
302975ce0baSmlelstv 
3038128840aSthorpej 	sc->sc_dev = self;
3045c050c46Sgdamore 	sc->sc_controller = *sba->sba_controller;
305b99a618cSrkujawa 	sc->sc_nslaves = sba->sba_controller->sct_nslaves;
3065c050c46Sgdamore 	/* allocate slave structures */
3075c050c46Sgdamore 	sc->sc_slaves = malloc(sizeof (struct spi_handle) * sc->sc_nslaves,
3085c050c46Sgdamore 	    M_DEVBUF, M_WAITOK | M_ZERO);
3095c050c46Sgdamore 
3105c050c46Sgdamore 	sc->sc_speed = 0;
311c36ea7cfSgdamore 	sc->sc_mode = -1;
312975ce0baSmlelstv 	sc->sc_slave = -1;
3135c050c46Sgdamore 
3145c050c46Sgdamore 	/*
3155c050c46Sgdamore 	 * Initialize slave handles
3165c050c46Sgdamore 	 */
3175c050c46Sgdamore 	for (i = 0; i < sc->sc_nslaves; i++) {
3185c050c46Sgdamore 		sc->sc_slaves[i].sh_slave = i;
3195c050c46Sgdamore 		sc->sc_slaves[i].sh_sc = sc;
3205c050c46Sgdamore 		sc->sc_slaves[i].sh_controller = &sc->sc_controller;
3215c050c46Sgdamore 	}
3225c050c46Sgdamore 
323f5ccd400Stnn 	/* First attach devices known to be present via fdt */
324f5ccd400Stnn 	if (sba->sba_child_devices) {
325f5ccd400Stnn 		spi_direct_attach_child_devices(self, sc, sba->sba_child_devices);
326f5ccd400Stnn 	}
327f5ccd400Stnn 	/* Then do any other devices the user may have manually wired */
3282685996bSthorpej 	config_search(self, NULL,
329c7fb772bSthorpej 	    CFARGS(.search = spi_search));
3305c050c46Sgdamore }
3315c050c46Sgdamore 
332975ce0baSmlelstv static int
spi_open(dev_t dev,int flag,int fmt,lwp_t * l)333975ce0baSmlelstv spi_open(dev_t dev, int flag, int fmt, lwp_t *l)
334975ce0baSmlelstv {
335975ce0baSmlelstv 	struct spi_softc *sc = device_lookup_private(&spi_cd, minor(dev));
336975ce0baSmlelstv 
337975ce0baSmlelstv 	if (sc == NULL)
338975ce0baSmlelstv 		return ENXIO;
339975ce0baSmlelstv 
340975ce0baSmlelstv 	return 0;
341975ce0baSmlelstv }
342975ce0baSmlelstv 
343975ce0baSmlelstv static int
spi_close(dev_t dev,int flag,int fmt,lwp_t * l)344975ce0baSmlelstv spi_close(dev_t dev, int flag, int fmt, lwp_t *l)
345975ce0baSmlelstv {
346975ce0baSmlelstv 
347975ce0baSmlelstv 	return 0;
348975ce0baSmlelstv }
349975ce0baSmlelstv 
350975ce0baSmlelstv static int
spi_ioctl(dev_t dev,u_long cmd,void * data,int flag,lwp_t * l)351975ce0baSmlelstv spi_ioctl(dev_t dev, u_long cmd, void *data, int flag, lwp_t *l)
352975ce0baSmlelstv {
353975ce0baSmlelstv 	struct spi_softc *sc = device_lookup_private(&spi_cd, minor(dev));
354975ce0baSmlelstv 	struct spi_handle *sh;
355975ce0baSmlelstv 	spi_ioctl_configure_t *sic;
356975ce0baSmlelstv 	spi_ioctl_transfer_t *sit;
357975ce0baSmlelstv 	uint8_t *sbuf, *rbuf;
358975ce0baSmlelstv 	int error;
359975ce0baSmlelstv 
360975ce0baSmlelstv 	if (sc == NULL)
361975ce0baSmlelstv 		return ENXIO;
362975ce0baSmlelstv 
363b1ce5950Smlelstv 	mutex_enter(&sc->sc_dev_lock);
364b1ce5950Smlelstv 
365975ce0baSmlelstv 	switch (cmd) {
366975ce0baSmlelstv 	case SPI_IOCTL_CONFIGURE:
367975ce0baSmlelstv 		sic = (spi_ioctl_configure_t *)data;
368975ce0baSmlelstv 		if (sic->sic_addr < 0 || sic->sic_addr >= sc->sc_nslaves) {
369975ce0baSmlelstv 			error = EINVAL;
370975ce0baSmlelstv 			break;
371975ce0baSmlelstv 		}
372975ce0baSmlelstv 		sh = &sc->sc_slaves[sic->sic_addr];
3738128840aSthorpej 		error = spi_configure(sc->sc_dev, sh, sic->sic_mode,
3748128840aSthorpej 		    sic->sic_speed);
375975ce0baSmlelstv 		break;
376975ce0baSmlelstv 	case SPI_IOCTL_TRANSFER:
377975ce0baSmlelstv 		sit = (spi_ioctl_transfer_t *)data;
378975ce0baSmlelstv 		if (sit->sit_addr < 0 || sit->sit_addr >= sc->sc_nslaves) {
379975ce0baSmlelstv 			error = EINVAL;
380975ce0baSmlelstv 			break;
381975ce0baSmlelstv 		}
38226a59cedSmlelstv 		if ((sit->sit_send && sit->sit_sendlen == 0)
383159a9ce8Smlelstv 		    || (sit->sit_recv && sit->sit_recvlen == 0)) {
38426a59cedSmlelstv 			error = EINVAL;
38526a59cedSmlelstv 			break;
38626a59cedSmlelstv 		}
387975ce0baSmlelstv 		sh = &sc->sc_slaves[sit->sit_addr];
388975ce0baSmlelstv 		sbuf = rbuf = NULL;
389975ce0baSmlelstv 		error = 0;
39026a59cedSmlelstv 		if (sit->sit_send && sit->sit_sendlen <= SPI_MAXDATA) {
391975ce0baSmlelstv 			sbuf = malloc(sit->sit_sendlen, M_DEVBUF, M_WAITOK);
392975ce0baSmlelstv 			error = copyin(sit->sit_send, sbuf, sit->sit_sendlen);
393975ce0baSmlelstv 		}
39426a59cedSmlelstv 		if (sit->sit_recv && sit->sit_recvlen <= SPI_MAXDATA) {
395975ce0baSmlelstv 			rbuf = malloc(sit->sit_recvlen, M_DEVBUF, M_WAITOK);
396975ce0baSmlelstv 		}
397975ce0baSmlelstv 		if (error == 0) {
398975ce0baSmlelstv 			if (sbuf && rbuf)
399975ce0baSmlelstv 				error = spi_send_recv(sh,
400975ce0baSmlelstv 					sit->sit_sendlen, sbuf,
401975ce0baSmlelstv 					sit->sit_recvlen, rbuf);
402975ce0baSmlelstv 			else if (sbuf)
403975ce0baSmlelstv 				error = spi_send(sh,
404975ce0baSmlelstv 					sit->sit_sendlen, sbuf);
405975ce0baSmlelstv 			else if (rbuf)
406975ce0baSmlelstv 				error = spi_recv(sh,
407975ce0baSmlelstv 					sit->sit_recvlen, rbuf);
408975ce0baSmlelstv 		}
409975ce0baSmlelstv 		if (rbuf) {
410975ce0baSmlelstv 			if (error == 0)
411975ce0baSmlelstv 				error = copyout(rbuf, sit->sit_recv,
412975ce0baSmlelstv 						sit->sit_recvlen);
413975ce0baSmlelstv 			free(rbuf, M_DEVBUF);
414975ce0baSmlelstv 		}
415975ce0baSmlelstv 		if (sbuf) {
416975ce0baSmlelstv 			free(sbuf, M_DEVBUF);
417975ce0baSmlelstv 		}
418975ce0baSmlelstv 		break;
419975ce0baSmlelstv 	default:
420975ce0baSmlelstv 		error = ENODEV;
421975ce0baSmlelstv 		break;
422975ce0baSmlelstv 	}
423975ce0baSmlelstv 
424b1ce5950Smlelstv 	mutex_exit(&sc->sc_dev_lock);
425b1ce5950Smlelstv 
426975ce0baSmlelstv 	return error;
427975ce0baSmlelstv }
428975ce0baSmlelstv 
4295232253eSxtraeme CFATTACH_DECL_NEW(spi, sizeof(struct spi_softc),
4305c050c46Sgdamore     spi_match, spi_attach, NULL, NULL);
4315c050c46Sgdamore 
4325c050c46Sgdamore /*
4335c050c46Sgdamore  * Configure.  This should be the first thing that the SPI driver
4345c050c46Sgdamore  * should do, to configure which mode (e.g. SPI_MODE_0, which is the
4355c050c46Sgdamore  * same as Philips Microwire mode), and speed.  If the bus driver
4365c050c46Sgdamore  * cannot run fast enough, then it should just configure the fastest
4375c050c46Sgdamore  * mode that it can support.  If the bus driver cannot run slow
4385c050c46Sgdamore  * enough, then the device is incompatible and an error should be
4395c050c46Sgdamore  * returned.
4405c050c46Sgdamore  */
4415c050c46Sgdamore int
spi_configure(device_t dev __unused,struct spi_handle * sh,int mode,int speed)442f64c887eSthorpej spi_configure(device_t dev __unused, struct spi_handle *sh, int mode, int speed)
4435c050c46Sgdamore {
4445c050c46Sgdamore 
445975ce0baSmlelstv 	sh->sh_mode = mode;
446975ce0baSmlelstv 	sh->sh_speed = speed;
447f64c887eSthorpej 
448f64c887eSthorpej 	/* No need to report errors; no failures. */
449f64c887eSthorpej 
450975ce0baSmlelstv 	return 0;
4515c050c46Sgdamore }
452975ce0baSmlelstv 
453975ce0baSmlelstv /*
454975ce0baSmlelstv  * Acquire controller
455975ce0baSmlelstv  */
456975ce0baSmlelstv static void
spi_acquire(struct spi_handle * sh)457975ce0baSmlelstv spi_acquire(struct spi_handle *sh)
458975ce0baSmlelstv {
459975ce0baSmlelstv 	struct spi_softc *sc = sh->sh_sc;
460975ce0baSmlelstv 
461975ce0baSmlelstv 	mutex_enter(&sc->sc_lock);
462975ce0baSmlelstv 	while ((sc->sc_flags & SPIC_BUSY) != 0)
463975ce0baSmlelstv 		cv_wait(&sc->sc_cv, &sc->sc_lock);
464975ce0baSmlelstv 	sc->sc_flags |= SPIC_BUSY;
465975ce0baSmlelstv 	mutex_exit(&sc->sc_lock);
466975ce0baSmlelstv }
467975ce0baSmlelstv 
468975ce0baSmlelstv /*
469975ce0baSmlelstv  * Release controller
470975ce0baSmlelstv  */
471975ce0baSmlelstv static void
spi_release(struct spi_handle * sh)472975ce0baSmlelstv spi_release(struct spi_handle *sh)
473975ce0baSmlelstv {
474975ce0baSmlelstv 	struct spi_softc *sc = sh->sh_sc;
475975ce0baSmlelstv 
476975ce0baSmlelstv 	mutex_enter(&sc->sc_lock);
477975ce0baSmlelstv 	sc->sc_flags &= ~SPIC_BUSY;
478975ce0baSmlelstv 	cv_broadcast(&sc->sc_cv);
479975ce0baSmlelstv 	mutex_exit(&sc->sc_lock);
4805c050c46Sgdamore }
4815c050c46Sgdamore 
4825c050c46Sgdamore void
spi_transfer_init(struct spi_transfer * st)4835c050c46Sgdamore spi_transfer_init(struct spi_transfer *st)
4845c050c46Sgdamore {
4855c050c46Sgdamore 
48637614169Skardel 	mutex_init(&st->st_lock, MUTEX_DEFAULT, IPL_VM);
487975ce0baSmlelstv 	cv_init(&st->st_cv, "spixfr");
488b1d9d10dSrmind 
4895c050c46Sgdamore 	st->st_flags = 0;
4905c050c46Sgdamore 	st->st_errno = 0;
4915c050c46Sgdamore 	st->st_done = NULL;
4925c050c46Sgdamore 	st->st_chunks = NULL;
4935c050c46Sgdamore 	st->st_private = NULL;
4945c050c46Sgdamore 	st->st_slave = -1;
4955c050c46Sgdamore }
4965c050c46Sgdamore 
4975c050c46Sgdamore void
spi_chunk_init(struct spi_chunk * chunk,int cnt,const uint8_t * wptr,uint8_t * rptr)4985c050c46Sgdamore spi_chunk_init(struct spi_chunk *chunk, int cnt, const uint8_t *wptr,
4995c050c46Sgdamore     uint8_t *rptr)
5005c050c46Sgdamore {
5015c050c46Sgdamore 
5025c050c46Sgdamore 	chunk->chunk_write = chunk->chunk_wptr = wptr;
50358e3c9efSmrg 	chunk->chunk_read = chunk->chunk_rptr = rptr;
5045c050c46Sgdamore 	chunk->chunk_rresid = chunk->chunk_wresid = chunk->chunk_count = cnt;
5055c050c46Sgdamore 	chunk->chunk_next = NULL;
5065c050c46Sgdamore }
5075c050c46Sgdamore 
5085c050c46Sgdamore void
spi_transfer_add(struct spi_transfer * st,struct spi_chunk * chunk)5095c050c46Sgdamore spi_transfer_add(struct spi_transfer *st, struct spi_chunk *chunk)
5105c050c46Sgdamore {
5115c050c46Sgdamore 	struct spi_chunk **cpp;
5125c050c46Sgdamore 
5135c050c46Sgdamore 	/* this is an O(n) insert -- perhaps we should use a simpleq? */
5145c050c46Sgdamore 	for (cpp = &st->st_chunks; *cpp; cpp = &(*cpp)->chunk_next);
5155c050c46Sgdamore 	*cpp = chunk;
5165c050c46Sgdamore }
5175c050c46Sgdamore 
5185c050c46Sgdamore int
spi_transfer(struct spi_handle * sh,struct spi_transfer * st)5195c050c46Sgdamore spi_transfer(struct spi_handle *sh, struct spi_transfer *st)
5205c050c46Sgdamore {
521975ce0baSmlelstv 	struct spi_softc	*sc = sh->sh_sc;
5225c050c46Sgdamore 	struct spi_controller	*tag = sh->sh_controller;
5235c050c46Sgdamore 	struct spi_chunk	*chunk;
524975ce0baSmlelstv 	int error;
5255c050c46Sgdamore 
5265c050c46Sgdamore 	/*
5275c050c46Sgdamore 	 * Initialize "resid" counters and pointers, so that callers
5285c050c46Sgdamore 	 * and bus drivers don't have to.
5295c050c46Sgdamore 	 */
5305c050c46Sgdamore 	for (chunk = st->st_chunks; chunk; chunk = chunk->chunk_next) {
5315c050c46Sgdamore 		chunk->chunk_wresid = chunk->chunk_rresid = chunk->chunk_count;
5325c050c46Sgdamore 		chunk->chunk_wptr = chunk->chunk_write;
5335c050c46Sgdamore 		chunk->chunk_rptr = chunk->chunk_read;
5345c050c46Sgdamore 	}
5355c050c46Sgdamore 
5365c050c46Sgdamore 	/*
537975ce0baSmlelstv 	 * Match slave and parameters to handle
5385c050c46Sgdamore 	 */
5395c050c46Sgdamore 	st->st_slave = sh->sh_slave;
5405c050c46Sgdamore 
541975ce0baSmlelstv 	/*
542975ce0baSmlelstv 	 * Reserve controller during transaction
543975ce0baSmlelstv  	 */
544975ce0baSmlelstv 	spi_acquire(sh);
545975ce0baSmlelstv 
546975ce0baSmlelstv 	st->st_spiprivate = (void *)sh;
547975ce0baSmlelstv 
548975ce0baSmlelstv 	/*
549975ce0baSmlelstv 	 * Reconfigure controller
550975ce0baSmlelstv 	 *
551975ce0baSmlelstv 	 * XXX backends don't configure per-slave parameters
552975ce0baSmlelstv 	 * Whenever we switch slaves or change mode or speed, we
553975ce0baSmlelstv 	 * need to tell the backend.
554975ce0baSmlelstv 	 */
555975ce0baSmlelstv 	if (sc->sc_slave != sh->sh_slave
556975ce0baSmlelstv 	    || sc->sc_mode != sh->sh_mode
557975ce0baSmlelstv 	    || sc->sc_speed != sh->sh_speed) {
558975ce0baSmlelstv 		error = (*tag->sct_configure)(tag->sct_cookie,
559975ce0baSmlelstv 				sh->sh_slave, sh->sh_mode, sh->sh_speed);
560975ce0baSmlelstv 		if (error)
561975ce0baSmlelstv 			return error;
562975ce0baSmlelstv 	}
563975ce0baSmlelstv 	sc->sc_mode = sh->sh_mode;
564975ce0baSmlelstv 	sc->sc_speed = sh->sh_speed;
565975ce0baSmlelstv 	sc->sc_slave = sh->sh_slave;
566975ce0baSmlelstv 
567975ce0baSmlelstv 	error = (*tag->sct_transfer)(tag->sct_cookie, st);
568975ce0baSmlelstv 
569975ce0baSmlelstv 	return error;
5705c050c46Sgdamore }
5715c050c46Sgdamore 
5725c050c46Sgdamore void
spi_wait(struct spi_transfer * st)5735c050c46Sgdamore spi_wait(struct spi_transfer *st)
5745c050c46Sgdamore {
575975ce0baSmlelstv 	struct spi_handle *sh = st->st_spiprivate;
5765c050c46Sgdamore 
577b1d9d10dSrmind 	mutex_enter(&st->st_lock);
578c44a6702Sjym 	while (!(st->st_flags & SPI_F_DONE)) {
579b1d9d10dSrmind 		cv_wait(&st->st_cv, &st->st_lock);
5805c050c46Sgdamore 	}
581b1d9d10dSrmind 	mutex_exit(&st->st_lock);
5823e230313Sjakllsch 	cv_destroy(&st->st_cv);
5833e230313Sjakllsch 	mutex_destroy(&st->st_lock);
584975ce0baSmlelstv 
585975ce0baSmlelstv 	/*
586975ce0baSmlelstv 	 * End transaction
587975ce0baSmlelstv 	 */
588975ce0baSmlelstv 	spi_release(sh);
5895c050c46Sgdamore }
5905c050c46Sgdamore 
5915c050c46Sgdamore void
spi_done(struct spi_transfer * st,int err)5925c050c46Sgdamore spi_done(struct spi_transfer *st, int err)
5935c050c46Sgdamore {
5945c050c46Sgdamore 
595b1d9d10dSrmind 	mutex_enter(&st->st_lock);
5965c050c46Sgdamore 	if ((st->st_errno = err) != 0) {
5975c050c46Sgdamore 		st->st_flags |= SPI_F_ERROR;
5985c050c46Sgdamore 	}
5995c050c46Sgdamore 	st->st_flags |= SPI_F_DONE;
6005c050c46Sgdamore 	if (st->st_done != NULL) {
6015c050c46Sgdamore 		(*st->st_done)(st);
6025c050c46Sgdamore 	} else {
603b1d9d10dSrmind 		cv_broadcast(&st->st_cv);
6045c050c46Sgdamore 	}
605b1d9d10dSrmind 	mutex_exit(&st->st_lock);
6065c050c46Sgdamore }
6075c050c46Sgdamore 
6085c050c46Sgdamore /*
6095c050c46Sgdamore  * Some convenience routines.  These routines block until the work
6105c050c46Sgdamore  * is done.
6115c050c46Sgdamore  *
6125c050c46Sgdamore  * spi_recv - receives data from the bus
6135c050c46Sgdamore  *
6145c050c46Sgdamore  * spi_send - sends data to the bus
6155c050c46Sgdamore  *
6165c050c46Sgdamore  * spi_send_recv - sends data to the bus, and then receives.  Note that this is
6175c050c46Sgdamore  * done synchronously, i.e. send a command and get the response.  This is
618*47bd93c3Sandvar  * not full duplex.  If you want full duplex, you can't use these convenience
6195c050c46Sgdamore  * wrappers.
6205c050c46Sgdamore  */
6215c050c46Sgdamore int
spi_recv(struct spi_handle * sh,int cnt,uint8_t * data)6225c050c46Sgdamore spi_recv(struct spi_handle *sh, int cnt, uint8_t *data)
6235c050c46Sgdamore {
6245c050c46Sgdamore 	struct spi_transfer	trans;
6255c050c46Sgdamore 	struct spi_chunk	chunk;
6265c050c46Sgdamore 
6275c050c46Sgdamore 	spi_transfer_init(&trans);
6285c050c46Sgdamore 	spi_chunk_init(&chunk, cnt, NULL, data);
6295c050c46Sgdamore 	spi_transfer_add(&trans, &chunk);
6305c050c46Sgdamore 
6315c050c46Sgdamore 	/* enqueue it and wait for it to complete */
6325c050c46Sgdamore 	spi_transfer(sh, &trans);
6335c050c46Sgdamore 	spi_wait(&trans);
6345c050c46Sgdamore 
6355c050c46Sgdamore 	if (trans.st_flags & SPI_F_ERROR)
6365c050c46Sgdamore 		return trans.st_errno;
6375c050c46Sgdamore 
6385c050c46Sgdamore 	return 0;
6395c050c46Sgdamore }
6405c050c46Sgdamore 
6415c050c46Sgdamore int
spi_send(struct spi_handle * sh,int cnt,const uint8_t * data)6425c050c46Sgdamore spi_send(struct spi_handle *sh, int cnt, const uint8_t *data)
6435c050c46Sgdamore {
6445c050c46Sgdamore 	struct spi_transfer	trans;
6455c050c46Sgdamore 	struct spi_chunk	chunk;
6465c050c46Sgdamore 
6475c050c46Sgdamore 	spi_transfer_init(&trans);
6485c050c46Sgdamore 	spi_chunk_init(&chunk, cnt, data, NULL);
6495c050c46Sgdamore 	spi_transfer_add(&trans, &chunk);
6505c050c46Sgdamore 
6515c050c46Sgdamore 	/* enqueue it and wait for it to complete */
6525c050c46Sgdamore 	spi_transfer(sh, &trans);
6535c050c46Sgdamore 	spi_wait(&trans);
6545c050c46Sgdamore 
6555c050c46Sgdamore 	if (trans.st_flags & SPI_F_ERROR)
6565c050c46Sgdamore 		return trans.st_errno;
6575c050c46Sgdamore 
6585c050c46Sgdamore 	return 0;
6595c050c46Sgdamore }
6605c050c46Sgdamore 
6615c050c46Sgdamore int
spi_send_recv(struct spi_handle * sh,int scnt,const uint8_t * snd,int rcnt,uint8_t * rcv)6625c050c46Sgdamore spi_send_recv(struct spi_handle *sh, int scnt, const uint8_t *snd,
6635c050c46Sgdamore     int rcnt, uint8_t *rcv)
6645c050c46Sgdamore {
6655c050c46Sgdamore 	struct spi_transfer	trans;
6665c050c46Sgdamore 	struct spi_chunk	chunk1, chunk2;
6675c050c46Sgdamore 
6685c050c46Sgdamore 	spi_transfer_init(&trans);
6695c050c46Sgdamore 	spi_chunk_init(&chunk1, scnt, snd, NULL);
6705c050c46Sgdamore 	spi_chunk_init(&chunk2, rcnt, NULL, rcv);
6715c050c46Sgdamore 	spi_transfer_add(&trans, &chunk1);
6725c050c46Sgdamore 	spi_transfer_add(&trans, &chunk2);
6735c050c46Sgdamore 
6745c050c46Sgdamore 	/* enqueue it and wait for it to complete */
6755c050c46Sgdamore 	spi_transfer(sh, &trans);
6765c050c46Sgdamore 	spi_wait(&trans);
6775c050c46Sgdamore 
6785c050c46Sgdamore 	if (trans.st_flags & SPI_F_ERROR)
6795c050c46Sgdamore 		return trans.st_errno;
6805c050c46Sgdamore 
6815c050c46Sgdamore 	return 0;
6825c050c46Sgdamore }
683