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