xref: /freebsd/sys/dev/spibus/spigen.c (revision 197d784bf3167062ddaa273cf24aeea745fbaed6)
1d67fe28bSAdrian Chadd /*-
2d67fe28bSAdrian Chadd  * Copyright (c) 2015 Brian Fundakowski Feldman.  All rights reserved.
3d67fe28bSAdrian Chadd  *
4d67fe28bSAdrian Chadd  * Redistribution and use in source and binary forms, with or without
5d67fe28bSAdrian Chadd  * modification, are permitted provided that the following conditions
6d67fe28bSAdrian Chadd  * are met:
7d67fe28bSAdrian Chadd  * 1. Redistributions of source code must retain the above copyright
8d67fe28bSAdrian Chadd  *    notice, this list of conditions and the following disclaimer.
9d67fe28bSAdrian Chadd  * 2. Redistributions in binary form must reproduce the above copyright
10d67fe28bSAdrian Chadd  *    notice, this list of conditions and the following disclaimer in the
11d67fe28bSAdrian Chadd  *    documentation and/or other materials provided with the distribution.
12d67fe28bSAdrian Chadd  *
13d67fe28bSAdrian Chadd  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14d67fe28bSAdrian Chadd  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15d67fe28bSAdrian Chadd  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16d67fe28bSAdrian Chadd  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17d67fe28bSAdrian Chadd  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18d67fe28bSAdrian Chadd  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19d67fe28bSAdrian Chadd  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20d67fe28bSAdrian Chadd  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21d67fe28bSAdrian Chadd  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22d67fe28bSAdrian Chadd  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23d67fe28bSAdrian Chadd  */
24d67fe28bSAdrian Chadd 
25d67fe28bSAdrian Chadd #include <sys/cdefs.h>
26d67fe28bSAdrian Chadd __FBSDID("$FreeBSD$");
27d67fe28bSAdrian Chadd 
28dac458e0SEmmanuel Vadot #include "opt_platform.h"
29dac458e0SEmmanuel Vadot 
30d67fe28bSAdrian Chadd #include <sys/param.h>
31d67fe28bSAdrian Chadd #include <sys/systm.h>
32d67fe28bSAdrian Chadd #include <sys/bus.h>
33d67fe28bSAdrian Chadd #include <sys/conf.h>
34d67fe28bSAdrian Chadd #include <sys/kernel.h>
35d67fe28bSAdrian Chadd #include <sys/lock.h>
36d67fe28bSAdrian Chadd #include <sys/malloc.h>
37d67fe28bSAdrian Chadd #include <sys/mman.h>
38d67fe28bSAdrian Chadd #include <sys/mutex.h>
39d67fe28bSAdrian Chadd #include <sys/module.h>
40d67fe28bSAdrian Chadd #include <sys/proc.h>
41d67fe28bSAdrian Chadd #include <sys/rwlock.h>
42d67fe28bSAdrian Chadd #include <sys/spigenio.h>
43d67fe28bSAdrian Chadd #include <sys/sysctl.h>
44d67fe28bSAdrian Chadd #include <sys/types.h>
45d67fe28bSAdrian Chadd 
46d67fe28bSAdrian Chadd #include <vm/vm.h>
47d67fe28bSAdrian Chadd #include <vm/vm_extern.h>
48d67fe28bSAdrian Chadd #include <vm/vm_object.h>
49d67fe28bSAdrian Chadd #include <vm/vm_page.h>
50d67fe28bSAdrian Chadd #include <vm/vm_pager.h>
51d67fe28bSAdrian Chadd 
52d67fe28bSAdrian Chadd #include <dev/spibus/spi.h>
53a0e911e0SIan Lepore #include <dev/spibus/spibusvar.h>
54d67fe28bSAdrian Chadd 
553b46d868SIan Lepore #ifdef FDT
563b46d868SIan Lepore #include <dev/ofw/ofw_bus_subr.h>
573b46d868SIan Lepore #endif
583b46d868SIan Lepore 
59d67fe28bSAdrian Chadd #include "spibus_if.h"
60d67fe28bSAdrian Chadd 
6150868fa6SOleksandr Tymoshenko #define	SPIGEN_OPEN		(1 << 0)
6250868fa6SOleksandr Tymoshenko #define	SPIGEN_MMAP_BUSY	(1 << 1)
6350868fa6SOleksandr Tymoshenko 
64d67fe28bSAdrian Chadd struct spigen_softc {
65d67fe28bSAdrian Chadd 	device_t sc_dev;
66d67fe28bSAdrian Chadd 	struct cdev *sc_cdev;
67d67fe28bSAdrian Chadd 	struct mtx sc_mtx;
68d67fe28bSAdrian Chadd 	uint32_t sc_command_length_max; /* cannot change while mmapped */
69d67fe28bSAdrian Chadd 	uint32_t sc_data_length_max;    /* cannot change while mmapped */
70d67fe28bSAdrian Chadd 	vm_object_t sc_mmap_buffer;     /* command, then data */
71d67fe28bSAdrian Chadd 	vm_offset_t sc_mmap_kvaddr;
72d67fe28bSAdrian Chadd 	size_t sc_mmap_buffer_size;
73d67fe28bSAdrian Chadd 	int sc_debug;
7450868fa6SOleksandr Tymoshenko 	int sc_flags;
75d67fe28bSAdrian Chadd };
76d67fe28bSAdrian Chadd 
77d67fe28bSAdrian Chadd static int
78d67fe28bSAdrian Chadd spigen_probe(device_t dev)
79d67fe28bSAdrian Chadd {
803b46d868SIan Lepore 	int rv;
8132d74127SOleksandr Tymoshenko 
823b46d868SIan Lepore #ifdef FDT
833b46d868SIan Lepore 	if (!ofw_bus_status_okay(dev))
843b46d868SIan Lepore 		return (ENXIO);
853b46d868SIan Lepore 
863b46d868SIan Lepore 	if (!ofw_bus_is_compatible(dev, "freebsd,spigen"))
873b46d868SIan Lepore 		return (ENXIO);
883b46d868SIan Lepore 	rv = BUS_PROBE_DEFAULT;
893b46d868SIan Lepore #else
903b46d868SIan Lepore 	rv = BUS_PROBE_NOWILDCARD;
913b46d868SIan Lepore #endif
92d67fe28bSAdrian Chadd 	device_set_desc(dev, "SPI Generic IO");
9332d74127SOleksandr Tymoshenko 
943b46d868SIan Lepore 	return (rv);
95d67fe28bSAdrian Chadd }
96d67fe28bSAdrian Chadd 
97d67fe28bSAdrian Chadd static int spigen_open(struct cdev *, int, int, struct thread *);
98d67fe28bSAdrian Chadd static int spigen_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *);
99d67fe28bSAdrian Chadd static int spigen_close(struct cdev *, int, int, struct thread *);
100d67fe28bSAdrian Chadd static d_mmap_single_t spigen_mmap_single;
101d67fe28bSAdrian Chadd 
102d67fe28bSAdrian Chadd static struct cdevsw spigen_cdevsw = {
103d67fe28bSAdrian Chadd 	.d_version =     D_VERSION,
104d67fe28bSAdrian Chadd 	.d_name =        "spigen",
105d67fe28bSAdrian Chadd 	.d_open =        spigen_open,
106d67fe28bSAdrian Chadd 	.d_ioctl =       spigen_ioctl,
107d67fe28bSAdrian Chadd 	.d_mmap_single = spigen_mmap_single,
108d67fe28bSAdrian Chadd 	.d_close =       spigen_close
109d67fe28bSAdrian Chadd };
110d67fe28bSAdrian Chadd 
111d67fe28bSAdrian Chadd static int
112d67fe28bSAdrian Chadd spigen_command_length_max_proc(SYSCTL_HANDLER_ARGS)
113d67fe28bSAdrian Chadd {
114d67fe28bSAdrian Chadd 	struct spigen_softc *sc = (struct spigen_softc *)arg1;
115d67fe28bSAdrian Chadd 	uint32_t command_length_max;
116d67fe28bSAdrian Chadd 	int error;
117d67fe28bSAdrian Chadd 
118d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
119d67fe28bSAdrian Chadd 	command_length_max = sc->sc_command_length_max;
120d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
121d67fe28bSAdrian Chadd 	error = sysctl_handle_int(oidp, &command_length_max,
122d67fe28bSAdrian Chadd 	    sizeof(command_length_max), req);
123d67fe28bSAdrian Chadd 	if (error == 0 && req->newptr != NULL) {
124d67fe28bSAdrian Chadd 		mtx_lock(&sc->sc_mtx);
125d67fe28bSAdrian Chadd 		if (sc->sc_mmap_buffer != NULL)
126d67fe28bSAdrian Chadd 			error = EBUSY;
127d67fe28bSAdrian Chadd 		else
128d67fe28bSAdrian Chadd 			sc->sc_command_length_max = command_length_max;
129d67fe28bSAdrian Chadd 		mtx_unlock(&sc->sc_mtx);
130d67fe28bSAdrian Chadd 	}
131d67fe28bSAdrian Chadd 	return (error);
132d67fe28bSAdrian Chadd }
133d67fe28bSAdrian Chadd 
134d67fe28bSAdrian Chadd static int
135d67fe28bSAdrian Chadd spigen_data_length_max_proc(SYSCTL_HANDLER_ARGS)
136d67fe28bSAdrian Chadd {
137d67fe28bSAdrian Chadd 	struct spigen_softc *sc = (struct spigen_softc *)arg1;
138d67fe28bSAdrian Chadd 	uint32_t data_length_max;
139d67fe28bSAdrian Chadd 	int error;
140d67fe28bSAdrian Chadd 
141d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
142d67fe28bSAdrian Chadd 	data_length_max = sc->sc_data_length_max;
143d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
144d67fe28bSAdrian Chadd 	error = sysctl_handle_int(oidp, &data_length_max,
145d67fe28bSAdrian Chadd 	    sizeof(data_length_max), req);
146d67fe28bSAdrian Chadd 	if (error == 0 && req->newptr != NULL) {
147d67fe28bSAdrian Chadd 		mtx_lock(&sc->sc_mtx);
148d67fe28bSAdrian Chadd 		if (sc->sc_mmap_buffer != NULL)
149d67fe28bSAdrian Chadd 			error = EBUSY;
150d67fe28bSAdrian Chadd 		else
151d67fe28bSAdrian Chadd 			sc->sc_data_length_max = data_length_max;
152d67fe28bSAdrian Chadd 		mtx_unlock(&sc->sc_mtx);
153d67fe28bSAdrian Chadd 	}
154d67fe28bSAdrian Chadd 	return (error);
155d67fe28bSAdrian Chadd }
156d67fe28bSAdrian Chadd 
157d67fe28bSAdrian Chadd static void
158d67fe28bSAdrian Chadd spigen_sysctl_init(struct spigen_softc *sc)
159d67fe28bSAdrian Chadd {
160d67fe28bSAdrian Chadd 	struct sysctl_ctx_list *ctx;
161d67fe28bSAdrian Chadd 	struct sysctl_oid *tree_node;
162d67fe28bSAdrian Chadd 	struct sysctl_oid_list *tree;
163d67fe28bSAdrian Chadd 
164d67fe28bSAdrian Chadd 	/*
165d67fe28bSAdrian Chadd 	 * Add system sysctl tree/handlers.
166d67fe28bSAdrian Chadd 	 */
167d67fe28bSAdrian Chadd 	ctx = device_get_sysctl_ctx(sc->sc_dev);
168d67fe28bSAdrian Chadd 	tree_node = device_get_sysctl_tree(sc->sc_dev);
169d67fe28bSAdrian Chadd 	tree = SYSCTL_CHILDREN(tree_node);
170d67fe28bSAdrian Chadd 	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "command_length_max",
171d67fe28bSAdrian Chadd 	    CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
172d67fe28bSAdrian Chadd 	    spigen_command_length_max_proc, "IU", "SPI command header portion (octets)");
173d67fe28bSAdrian Chadd 	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "data_length_max",
174d67fe28bSAdrian Chadd 	    CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
175d67fe28bSAdrian Chadd 	    spigen_data_length_max_proc, "IU", "SPI data trailer portion (octets)");
176d67fe28bSAdrian Chadd 	SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "data", CTLFLAG_RW,
177d67fe28bSAdrian Chadd 	    &sc->sc_debug, 0, "debug flags");
178d67fe28bSAdrian Chadd 
179d67fe28bSAdrian Chadd }
180d67fe28bSAdrian Chadd 
181d67fe28bSAdrian Chadd static int
182d67fe28bSAdrian Chadd spigen_attach(device_t dev)
183d67fe28bSAdrian Chadd {
184d67fe28bSAdrian Chadd 	struct spigen_softc *sc;
185d67fe28bSAdrian Chadd 	const int unit = device_get_unit(dev);
186d67fe28bSAdrian Chadd 
187d67fe28bSAdrian Chadd 	sc = device_get_softc(dev);
188d67fe28bSAdrian Chadd 	sc->sc_dev = dev;
189d67fe28bSAdrian Chadd 	sc->sc_cdev = make_dev(&spigen_cdevsw, unit,
190d67fe28bSAdrian Chadd 	    UID_ROOT, GID_OPERATOR, 0660, "spigen%d", unit);
191d67fe28bSAdrian Chadd 	sc->sc_cdev->si_drv1 = dev;
192d67fe28bSAdrian Chadd 	sc->sc_command_length_max = PAGE_SIZE;
193d67fe28bSAdrian Chadd 	sc->sc_data_length_max = PAGE_SIZE;
194d67fe28bSAdrian Chadd 	mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
195d67fe28bSAdrian Chadd 	spigen_sysctl_init(sc);
196d67fe28bSAdrian Chadd 
197d67fe28bSAdrian Chadd 	return (0);
198d67fe28bSAdrian Chadd }
199d67fe28bSAdrian Chadd 
200d67fe28bSAdrian Chadd static int
20150868fa6SOleksandr Tymoshenko spigen_open(struct cdev *cdev, int oflags, int devtype, struct thread *td)
202d67fe28bSAdrian Chadd {
20350868fa6SOleksandr Tymoshenko 	int error;
20450868fa6SOleksandr Tymoshenko 	device_t dev;
20550868fa6SOleksandr Tymoshenko 	struct spigen_softc *sc;
206d67fe28bSAdrian Chadd 
20750868fa6SOleksandr Tymoshenko 	error = 0;
20850868fa6SOleksandr Tymoshenko 	dev = cdev->si_drv1;
20950868fa6SOleksandr Tymoshenko 	sc = device_get_softc(dev);
21050868fa6SOleksandr Tymoshenko 
21150868fa6SOleksandr Tymoshenko 	mtx_lock(&sc->sc_mtx);
21250868fa6SOleksandr Tymoshenko 	if (sc->sc_flags & SPIGEN_OPEN)
21350868fa6SOleksandr Tymoshenko 		error = EBUSY;
21450868fa6SOleksandr Tymoshenko 	else
21550868fa6SOleksandr Tymoshenko 		sc->sc_flags |= SPIGEN_OPEN;
21650868fa6SOleksandr Tymoshenko 	mtx_unlock(&sc->sc_mtx);
21750868fa6SOleksandr Tymoshenko 
21850868fa6SOleksandr Tymoshenko 	return (error);
219d67fe28bSAdrian Chadd }
220d67fe28bSAdrian Chadd 
221d67fe28bSAdrian Chadd static int
222d67fe28bSAdrian Chadd spigen_transfer(struct cdev *cdev, struct spigen_transfer *st)
223d67fe28bSAdrian Chadd {
224d67fe28bSAdrian Chadd 	struct spi_command transfer = SPI_COMMAND_INITIALIZER;
225d67fe28bSAdrian Chadd 	device_t dev = cdev->si_drv1;
226d67fe28bSAdrian Chadd 	struct spigen_softc *sc = device_get_softc(dev);
227d67fe28bSAdrian Chadd 	int error = 0;
228d67fe28bSAdrian Chadd 
229d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
2303c43a826SOleksandr Tymoshenko 	if (st->st_command.iov_len == 0)
231d67fe28bSAdrian Chadd 		error = EINVAL;
232d67fe28bSAdrian Chadd 	else if (st->st_command.iov_len > sc->sc_command_length_max ||
233d67fe28bSAdrian Chadd 	    st->st_data.iov_len > sc->sc_data_length_max)
234d67fe28bSAdrian Chadd 		error = ENOMEM;
235d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
236d67fe28bSAdrian Chadd 	if (error)
237d67fe28bSAdrian Chadd 		return (error);
238d67fe28bSAdrian Chadd 
239d67fe28bSAdrian Chadd #if 0
240d67fe28bSAdrian Chadd 	device_printf(dev, "cmd %p %u data %p %u\n", st->st_command.iov_base,
241d67fe28bSAdrian Chadd 	    st->st_command.iov_len, st->st_data.iov_base, st->st_data.iov_len);
242d67fe28bSAdrian Chadd #endif
243d67fe28bSAdrian Chadd 	transfer.tx_cmd = transfer.rx_cmd = malloc(st->st_command.iov_len,
244d67fe28bSAdrian Chadd 	    M_DEVBUF, M_WAITOK);
2453c43a826SOleksandr Tymoshenko 	if (st->st_data.iov_len > 0) {
246d67fe28bSAdrian Chadd 		transfer.tx_data = transfer.rx_data = malloc(st->st_data.iov_len,
247d67fe28bSAdrian Chadd 		    M_DEVBUF, M_WAITOK);
2483c43a826SOleksandr Tymoshenko 	}
2493c43a826SOleksandr Tymoshenko 	else
2503c43a826SOleksandr Tymoshenko 		transfer.tx_data = transfer.rx_data = NULL;
251d67fe28bSAdrian Chadd 
252d67fe28bSAdrian Chadd 	error = copyin(st->st_command.iov_base, transfer.tx_cmd,
253d67fe28bSAdrian Chadd 	    transfer.tx_cmd_sz = transfer.rx_cmd_sz = st->st_command.iov_len);
2543c43a826SOleksandr Tymoshenko 	if ((error == 0) && (st->st_data.iov_len > 0))
255d67fe28bSAdrian Chadd 		error = copyin(st->st_data.iov_base, transfer.tx_data,
256d67fe28bSAdrian Chadd 		    transfer.tx_data_sz = transfer.rx_data_sz =
257d67fe28bSAdrian Chadd 		                          st->st_data.iov_len);
258d67fe28bSAdrian Chadd 	if (error == 0)
259d67fe28bSAdrian Chadd 		error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
260d67fe28bSAdrian Chadd 	if (error == 0) {
261d67fe28bSAdrian Chadd 		error = copyout(transfer.rx_cmd, st->st_command.iov_base,
262d67fe28bSAdrian Chadd 		    transfer.rx_cmd_sz);
2633c43a826SOleksandr Tymoshenko 		if ((error == 0) && (st->st_data.iov_len > 0))
264d67fe28bSAdrian Chadd 			error = copyout(transfer.rx_data, st->st_data.iov_base,
265d67fe28bSAdrian Chadd 			    transfer.rx_data_sz);
266d67fe28bSAdrian Chadd 	}
267d67fe28bSAdrian Chadd 
268d67fe28bSAdrian Chadd 	free(transfer.tx_cmd, M_DEVBUF);
269d67fe28bSAdrian Chadd 	free(transfer.tx_data, M_DEVBUF);
270d67fe28bSAdrian Chadd 	return (error);
271d67fe28bSAdrian Chadd }
272d67fe28bSAdrian Chadd 
273d67fe28bSAdrian Chadd static int
274d67fe28bSAdrian Chadd spigen_transfer_mmapped(struct cdev *cdev, struct spigen_transfer_mmapped *stm)
275d67fe28bSAdrian Chadd {
276d67fe28bSAdrian Chadd 	struct spi_command transfer = SPI_COMMAND_INITIALIZER;
277d67fe28bSAdrian Chadd 	device_t dev = cdev->si_drv1;
278d67fe28bSAdrian Chadd 	struct spigen_softc *sc = device_get_softc(dev);
279d67fe28bSAdrian Chadd 	int error = 0;
280d67fe28bSAdrian Chadd 
281d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
28250868fa6SOleksandr Tymoshenko 	if (sc->sc_flags & SPIGEN_MMAP_BUSY)
283d67fe28bSAdrian Chadd 		error = EBUSY;
284d67fe28bSAdrian Chadd 	else if (stm->stm_command_length > sc->sc_command_length_max ||
285d67fe28bSAdrian Chadd 	    stm->stm_data_length > sc->sc_data_length_max)
286d67fe28bSAdrian Chadd 		error = E2BIG;
287d67fe28bSAdrian Chadd 	else if (sc->sc_mmap_buffer == NULL)
288d67fe28bSAdrian Chadd 		error = EINVAL;
289d67fe28bSAdrian Chadd 	else if (sc->sc_mmap_buffer_size <
290d67fe28bSAdrian Chadd 	    stm->stm_command_length + stm->stm_data_length)
291d67fe28bSAdrian Chadd 		error = ENOMEM;
292d67fe28bSAdrian Chadd 	if (error == 0)
29350868fa6SOleksandr Tymoshenko 		sc->sc_flags |= SPIGEN_MMAP_BUSY;
294d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
295d67fe28bSAdrian Chadd 	if (error)
296d67fe28bSAdrian Chadd 		return (error);
297d67fe28bSAdrian Chadd 
298d67fe28bSAdrian Chadd 	transfer.tx_cmd = transfer.rx_cmd = (void *)sc->sc_mmap_kvaddr;
299d67fe28bSAdrian Chadd 	transfer.tx_cmd_sz = transfer.rx_cmd_sz = stm->stm_command_length;
300d67fe28bSAdrian Chadd 	transfer.tx_data = transfer.rx_data =
301d67fe28bSAdrian Chadd 	    (void *)(sc->sc_mmap_kvaddr + stm->stm_command_length);
302d67fe28bSAdrian Chadd 	transfer.tx_data_sz = transfer.rx_data_sz = stm->stm_data_length;
303d67fe28bSAdrian Chadd 	error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
304d67fe28bSAdrian Chadd 
305d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
30650868fa6SOleksandr Tymoshenko 	KASSERT((sc->sc_flags & SPIGEN_MMAP_BUSY), ("mmap no longer marked busy"));
30750868fa6SOleksandr Tymoshenko 	sc->sc_flags &= ~(SPIGEN_MMAP_BUSY);
308d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
309d67fe28bSAdrian Chadd 	return (error);
310d67fe28bSAdrian Chadd }
311d67fe28bSAdrian Chadd 
312d67fe28bSAdrian Chadd static int
313d67fe28bSAdrian Chadd spigen_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
314d67fe28bSAdrian Chadd     struct thread *td)
315d67fe28bSAdrian Chadd {
316d67fe28bSAdrian Chadd 	device_t dev = cdev->si_drv1;
317d67fe28bSAdrian Chadd 	int error;
318d67fe28bSAdrian Chadd 
319d67fe28bSAdrian Chadd 	switch (cmd) {
320d67fe28bSAdrian Chadd 	case SPIGENIOC_TRANSFER:
321d67fe28bSAdrian Chadd 		error = spigen_transfer(cdev, (struct spigen_transfer *)data);
322d67fe28bSAdrian Chadd 		break;
323d67fe28bSAdrian Chadd 	case SPIGENIOC_TRANSFER_MMAPPED:
324d67fe28bSAdrian Chadd 		error = spigen_transfer_mmapped(cdev, (struct spigen_transfer_mmapped *)data);
325d67fe28bSAdrian Chadd 		break;
326d67fe28bSAdrian Chadd 	case SPIGENIOC_GET_CLOCK_SPEED:
327*197d784bSIan Lepore 		error = spibus_get_clock(dev, (uint32_t *)data);
328d67fe28bSAdrian Chadd 		break;
329d67fe28bSAdrian Chadd 	case SPIGENIOC_SET_CLOCK_SPEED:
330a0e911e0SIan Lepore 		error = spibus_set_clock(dev, *(uint32_t *)data);
331a0e911e0SIan Lepore 		break;
332a0e911e0SIan Lepore 	case SPIGENIOC_GET_SPI_MODE:
333*197d784bSIan Lepore 		error = spibus_get_mode(dev, (uint32_t *)data);
334a0e911e0SIan Lepore 		break;
335a0e911e0SIan Lepore 	case SPIGENIOC_SET_SPI_MODE:
336a0e911e0SIan Lepore 		error = spibus_set_mode(dev, *(uint32_t *)data);
337d67fe28bSAdrian Chadd 		break;
338d67fe28bSAdrian Chadd 	default:
339a0e911e0SIan Lepore 		error = ENOTTY;
340a0e911e0SIan Lepore 		break;
341d67fe28bSAdrian Chadd 	}
342d67fe28bSAdrian Chadd 	return (error);
343d67fe28bSAdrian Chadd }
344d67fe28bSAdrian Chadd 
345d67fe28bSAdrian Chadd static int
346d67fe28bSAdrian Chadd spigen_mmap_single(struct cdev *cdev, vm_ooffset_t *offset,
347d67fe28bSAdrian Chadd     vm_size_t size, struct vm_object **object, int nprot)
348d67fe28bSAdrian Chadd {
349d67fe28bSAdrian Chadd 	device_t dev = cdev->si_drv1;
350d67fe28bSAdrian Chadd 	struct spigen_softc *sc = device_get_softc(dev);
351d67fe28bSAdrian Chadd 	vm_page_t *m;
352d67fe28bSAdrian Chadd 	size_t n, pages;
353d67fe28bSAdrian Chadd 
354d67fe28bSAdrian Chadd 	if (size == 0 ||
355d67fe28bSAdrian Chadd 	    (nprot & (PROT_EXEC | PROT_READ | PROT_WRITE))
356d67fe28bSAdrian Chadd 	    != (PROT_READ | PROT_WRITE))
357d67fe28bSAdrian Chadd 		return (EINVAL);
358d67fe28bSAdrian Chadd 	size = roundup2(size, PAGE_SIZE);
359d67fe28bSAdrian Chadd 	pages = size / PAGE_SIZE;
360d67fe28bSAdrian Chadd 
361d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
362d67fe28bSAdrian Chadd 	if (sc->sc_mmap_buffer != NULL) {
363d67fe28bSAdrian Chadd 		mtx_unlock(&sc->sc_mtx);
364d67fe28bSAdrian Chadd 		return (EBUSY);
365d67fe28bSAdrian Chadd 	} else if (size > sc->sc_command_length_max + sc->sc_data_length_max) {
366d67fe28bSAdrian Chadd 		mtx_unlock(&sc->sc_mtx);
367d67fe28bSAdrian Chadd 		return (E2BIG);
368d67fe28bSAdrian Chadd 	}
369d67fe28bSAdrian Chadd 	sc->sc_mmap_buffer_size = size;
370d67fe28bSAdrian Chadd 	*offset = 0;
371d67fe28bSAdrian Chadd 	sc->sc_mmap_buffer = *object = vm_pager_allocate(OBJT_PHYS, 0, size,
372d67fe28bSAdrian Chadd 	    nprot, *offset, curthread->td_ucred);
373d67fe28bSAdrian Chadd 	m = malloc(sizeof(*m) * pages, M_TEMP, M_WAITOK);
374d67fe28bSAdrian Chadd 	VM_OBJECT_WLOCK(*object);
375d67fe28bSAdrian Chadd 	vm_object_reference_locked(*object); // kernel and userland both
376d67fe28bSAdrian Chadd 	for (n = 0; n < pages; n++) {
377d67fe28bSAdrian Chadd 		m[n] = vm_page_grab(*object, n,
378d67fe28bSAdrian Chadd 		    VM_ALLOC_NOBUSY | VM_ALLOC_ZERO | VM_ALLOC_WIRED);
379d67fe28bSAdrian Chadd 		m[n]->valid = VM_PAGE_BITS_ALL;
380d67fe28bSAdrian Chadd 	}
381d67fe28bSAdrian Chadd 	VM_OBJECT_WUNLOCK(*object);
382d67fe28bSAdrian Chadd 	sc->sc_mmap_kvaddr = kva_alloc(size);
383d67fe28bSAdrian Chadd 	pmap_qenter(sc->sc_mmap_kvaddr, m, pages);
384d67fe28bSAdrian Chadd 	free(m, M_TEMP);
385d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
386d67fe28bSAdrian Chadd 
387d67fe28bSAdrian Chadd 	if (*object == NULL)
388d67fe28bSAdrian Chadd 		 return (EINVAL);
389d67fe28bSAdrian Chadd 	return (0);
390d67fe28bSAdrian Chadd }
391d67fe28bSAdrian Chadd 
392d67fe28bSAdrian Chadd static int
393d67fe28bSAdrian Chadd spigen_close(struct cdev *cdev, int fflag, int devtype, struct thread *td)
394d67fe28bSAdrian Chadd {
395d67fe28bSAdrian Chadd 	device_t dev = cdev->si_drv1;
396d67fe28bSAdrian Chadd 	struct spigen_softc *sc = device_get_softc(dev);
397d67fe28bSAdrian Chadd 
398d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
399d67fe28bSAdrian Chadd 	if (sc->sc_mmap_buffer != NULL) {
400d67fe28bSAdrian Chadd 		pmap_qremove(sc->sc_mmap_kvaddr,
401d67fe28bSAdrian Chadd 		    sc->sc_mmap_buffer_size / PAGE_SIZE);
402d67fe28bSAdrian Chadd 		kva_free(sc->sc_mmap_kvaddr, sc->sc_mmap_buffer_size);
403d67fe28bSAdrian Chadd 		sc->sc_mmap_kvaddr = 0;
404d67fe28bSAdrian Chadd 		vm_object_deallocate(sc->sc_mmap_buffer);
405d67fe28bSAdrian Chadd 		sc->sc_mmap_buffer = NULL;
406d67fe28bSAdrian Chadd 		sc->sc_mmap_buffer_size = 0;
407d67fe28bSAdrian Chadd 	}
40850868fa6SOleksandr Tymoshenko 	sc->sc_flags &= ~(SPIGEN_OPEN);
409d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
410d67fe28bSAdrian Chadd 	return (0);
411d67fe28bSAdrian Chadd }
412d67fe28bSAdrian Chadd 
413d67fe28bSAdrian Chadd static int
414d67fe28bSAdrian Chadd spigen_detach(device_t dev)
415d67fe28bSAdrian Chadd {
41650868fa6SOleksandr Tymoshenko 	struct spigen_softc *sc;
417d67fe28bSAdrian Chadd 
41850868fa6SOleksandr Tymoshenko 	sc = device_get_softc(dev);
41950868fa6SOleksandr Tymoshenko 
42050868fa6SOleksandr Tymoshenko 	mtx_lock(&sc->sc_mtx);
42150868fa6SOleksandr Tymoshenko 	if (sc->sc_flags & SPIGEN_OPEN) {
42250868fa6SOleksandr Tymoshenko 		mtx_unlock(&sc->sc_mtx);
42350868fa6SOleksandr Tymoshenko 		return (EBUSY);
42450868fa6SOleksandr Tymoshenko 	}
42550868fa6SOleksandr Tymoshenko 	mtx_unlock(&sc->sc_mtx);
42650868fa6SOleksandr Tymoshenko 
42750868fa6SOleksandr Tymoshenko 	mtx_destroy(&sc->sc_mtx);
42850868fa6SOleksandr Tymoshenko 
42950868fa6SOleksandr Tymoshenko         if (sc->sc_cdev)
43050868fa6SOleksandr Tymoshenko                 destroy_dev(sc->sc_cdev);
43150868fa6SOleksandr Tymoshenko 
43250868fa6SOleksandr Tymoshenko 	return (0);
433d67fe28bSAdrian Chadd }
434d67fe28bSAdrian Chadd 
435d67fe28bSAdrian Chadd static devclass_t spigen_devclass;
436d67fe28bSAdrian Chadd 
437d67fe28bSAdrian Chadd static device_method_t spigen_methods[] = {
438d67fe28bSAdrian Chadd 	/* Device interface */
439d67fe28bSAdrian Chadd 	DEVMETHOD(device_probe,		spigen_probe),
440d67fe28bSAdrian Chadd 	DEVMETHOD(device_attach,	spigen_attach),
441d67fe28bSAdrian Chadd 	DEVMETHOD(device_detach,	spigen_detach),
442d67fe28bSAdrian Chadd 
443d67fe28bSAdrian Chadd 	{ 0, 0 }
444d67fe28bSAdrian Chadd };
445d67fe28bSAdrian Chadd 
446d67fe28bSAdrian Chadd static driver_t spigen_driver = {
447d67fe28bSAdrian Chadd 	"spigen",
448d67fe28bSAdrian Chadd 	spigen_methods,
449d67fe28bSAdrian Chadd 	sizeof(struct spigen_softc),
450d67fe28bSAdrian Chadd };
451d67fe28bSAdrian Chadd 
452d67fe28bSAdrian Chadd DRIVER_MODULE(spigen, spibus, spigen_driver, spigen_devclass, 0, 0);
453cdfebb9cSIan Lepore MODULE_DEPEND(spigen, spibus, 1, 1, 1);
454