xref: /freebsd/sys/dev/spibus/spigen.c (revision 3c43a826c6b31865c23ee06bfd9be63d70bf278e)
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>
53d67fe28bSAdrian Chadd 
54d67fe28bSAdrian Chadd #include "spibus_if.h"
55d67fe28bSAdrian Chadd 
56d67fe28bSAdrian Chadd struct spigen_softc {
57d67fe28bSAdrian Chadd 	device_t sc_dev;
58d67fe28bSAdrian Chadd 	struct cdev *sc_cdev;
59d67fe28bSAdrian Chadd 	struct mtx sc_mtx;
60d67fe28bSAdrian Chadd 	uint32_t sc_clock_speed;
61d67fe28bSAdrian Chadd 	uint32_t sc_command_length_max; /* cannot change while mmapped */
62d67fe28bSAdrian Chadd 	uint32_t sc_data_length_max;    /* cannot change while mmapped */
63d67fe28bSAdrian Chadd 	vm_object_t sc_mmap_buffer;     /* command, then data */
64d67fe28bSAdrian Chadd 	vm_offset_t sc_mmap_kvaddr;
65d67fe28bSAdrian Chadd 	size_t sc_mmap_buffer_size;
66d67fe28bSAdrian Chadd 	int sc_mmap_busy;
67d67fe28bSAdrian Chadd 	int sc_debug;
68d67fe28bSAdrian Chadd };
69d67fe28bSAdrian Chadd 
70dac458e0SEmmanuel Vadot #ifdef FDT
71dac458e0SEmmanuel Vadot static void
72dac458e0SEmmanuel Vadot spigen_identify(driver_t *driver, device_t parent)
73dac458e0SEmmanuel Vadot {
74dac458e0SEmmanuel Vadot 	if (device_find_child(parent, "spigen", -1) != NULL)
75dac458e0SEmmanuel Vadot 		return;
76dac458e0SEmmanuel Vadot 	if (BUS_ADD_CHILD(parent, 0, "spigen", -1) == NULL)
77dac458e0SEmmanuel Vadot 		device_printf(parent, "add child failed\n");
78dac458e0SEmmanuel Vadot }
79dac458e0SEmmanuel Vadot #endif
80dac458e0SEmmanuel Vadot 
81d67fe28bSAdrian Chadd static int
82d67fe28bSAdrian Chadd spigen_probe(device_t dev)
83d67fe28bSAdrian Chadd {
84d67fe28bSAdrian Chadd 	device_set_desc(dev, "SPI Generic IO");
85d67fe28bSAdrian Chadd 	return (0);
86d67fe28bSAdrian Chadd }
87d67fe28bSAdrian Chadd 
88d67fe28bSAdrian Chadd static int spigen_open(struct cdev *, int, int, struct thread *);
89d67fe28bSAdrian Chadd static int spigen_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *);
90d67fe28bSAdrian Chadd static int spigen_close(struct cdev *, int, int, struct thread *);
91d67fe28bSAdrian Chadd static d_mmap_single_t spigen_mmap_single;
92d67fe28bSAdrian Chadd 
93d67fe28bSAdrian Chadd static struct cdevsw spigen_cdevsw = {
94d67fe28bSAdrian Chadd 	.d_version =     D_VERSION,
95d67fe28bSAdrian Chadd 	.d_name =        "spigen",
96d67fe28bSAdrian Chadd 	.d_open =        spigen_open,
97d67fe28bSAdrian Chadd 	.d_ioctl =       spigen_ioctl,
98d67fe28bSAdrian Chadd 	.d_mmap_single = spigen_mmap_single,
99d67fe28bSAdrian Chadd 	.d_close =       spigen_close
100d67fe28bSAdrian Chadd };
101d67fe28bSAdrian Chadd 
102d67fe28bSAdrian Chadd static int
103d67fe28bSAdrian Chadd spigen_command_length_max_proc(SYSCTL_HANDLER_ARGS)
104d67fe28bSAdrian Chadd {
105d67fe28bSAdrian Chadd 	struct spigen_softc *sc = (struct spigen_softc *)arg1;
106d67fe28bSAdrian Chadd 	uint32_t command_length_max;
107d67fe28bSAdrian Chadd 	int error;
108d67fe28bSAdrian Chadd 
109d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
110d67fe28bSAdrian Chadd 	command_length_max = sc->sc_command_length_max;
111d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
112d67fe28bSAdrian Chadd 	error = sysctl_handle_int(oidp, &command_length_max,
113d67fe28bSAdrian Chadd 	    sizeof(command_length_max), req);
114d67fe28bSAdrian Chadd 	if (error == 0 && req->newptr != NULL) {
115d67fe28bSAdrian Chadd 		mtx_lock(&sc->sc_mtx);
116d67fe28bSAdrian Chadd 		if (sc->sc_mmap_buffer != NULL)
117d67fe28bSAdrian Chadd 			error = EBUSY;
118d67fe28bSAdrian Chadd 		else
119d67fe28bSAdrian Chadd 			sc->sc_command_length_max = command_length_max;
120d67fe28bSAdrian Chadd 		mtx_unlock(&sc->sc_mtx);
121d67fe28bSAdrian Chadd 	}
122d67fe28bSAdrian Chadd 	return (error);
123d67fe28bSAdrian Chadd }
124d67fe28bSAdrian Chadd 
125d67fe28bSAdrian Chadd static int
126d67fe28bSAdrian Chadd spigen_data_length_max_proc(SYSCTL_HANDLER_ARGS)
127d67fe28bSAdrian Chadd {
128d67fe28bSAdrian Chadd 	struct spigen_softc *sc = (struct spigen_softc *)arg1;
129d67fe28bSAdrian Chadd 	uint32_t data_length_max;
130d67fe28bSAdrian Chadd 	int error;
131d67fe28bSAdrian Chadd 
132d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
133d67fe28bSAdrian Chadd 	data_length_max = sc->sc_data_length_max;
134d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
135d67fe28bSAdrian Chadd 	error = sysctl_handle_int(oidp, &data_length_max,
136d67fe28bSAdrian Chadd 	    sizeof(data_length_max), req);
137d67fe28bSAdrian Chadd 	if (error == 0 && req->newptr != NULL) {
138d67fe28bSAdrian Chadd 		mtx_lock(&sc->sc_mtx);
139d67fe28bSAdrian Chadd 		if (sc->sc_mmap_buffer != NULL)
140d67fe28bSAdrian Chadd 			error = EBUSY;
141d67fe28bSAdrian Chadd 		else
142d67fe28bSAdrian Chadd 			sc->sc_data_length_max = data_length_max;
143d67fe28bSAdrian Chadd 		mtx_unlock(&sc->sc_mtx);
144d67fe28bSAdrian Chadd 	}
145d67fe28bSAdrian Chadd 	return (error);
146d67fe28bSAdrian Chadd }
147d67fe28bSAdrian Chadd 
148d67fe28bSAdrian Chadd static void
149d67fe28bSAdrian Chadd spigen_sysctl_init(struct spigen_softc *sc)
150d67fe28bSAdrian Chadd {
151d67fe28bSAdrian Chadd 	struct sysctl_ctx_list *ctx;
152d67fe28bSAdrian Chadd 	struct sysctl_oid *tree_node;
153d67fe28bSAdrian Chadd 	struct sysctl_oid_list *tree;
154d67fe28bSAdrian Chadd 
155d67fe28bSAdrian Chadd 	/*
156d67fe28bSAdrian Chadd 	 * Add system sysctl tree/handlers.
157d67fe28bSAdrian Chadd 	 */
158d67fe28bSAdrian Chadd 	ctx = device_get_sysctl_ctx(sc->sc_dev);
159d67fe28bSAdrian Chadd 	tree_node = device_get_sysctl_tree(sc->sc_dev);
160d67fe28bSAdrian Chadd 	tree = SYSCTL_CHILDREN(tree_node);
161d67fe28bSAdrian Chadd 	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "command_length_max",
162d67fe28bSAdrian Chadd 	    CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
163d67fe28bSAdrian Chadd 	    spigen_command_length_max_proc, "IU", "SPI command header portion (octets)");
164d67fe28bSAdrian Chadd 	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "data_length_max",
165d67fe28bSAdrian Chadd 	    CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
166d67fe28bSAdrian Chadd 	    spigen_data_length_max_proc, "IU", "SPI data trailer portion (octets)");
167d67fe28bSAdrian Chadd 	SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "data", CTLFLAG_RW,
168d67fe28bSAdrian Chadd 	    &sc->sc_debug, 0, "debug flags");
169d67fe28bSAdrian Chadd 
170d67fe28bSAdrian Chadd }
171d67fe28bSAdrian Chadd 
172d67fe28bSAdrian Chadd static int
173d67fe28bSAdrian Chadd spigen_attach(device_t dev)
174d67fe28bSAdrian Chadd {
175d67fe28bSAdrian Chadd 	struct spigen_softc *sc;
176d67fe28bSAdrian Chadd 	const int unit = device_get_unit(dev);
177d67fe28bSAdrian Chadd 
178d67fe28bSAdrian Chadd 	sc = device_get_softc(dev);
179d67fe28bSAdrian Chadd 	sc->sc_dev = dev;
180d67fe28bSAdrian Chadd 	sc->sc_cdev = make_dev(&spigen_cdevsw, unit,
181d67fe28bSAdrian Chadd 	    UID_ROOT, GID_OPERATOR, 0660, "spigen%d", unit);
182d67fe28bSAdrian Chadd 	sc->sc_cdev->si_drv1 = dev;
183d67fe28bSAdrian Chadd 	sc->sc_command_length_max = PAGE_SIZE;
184d67fe28bSAdrian Chadd 	sc->sc_data_length_max = PAGE_SIZE;
185d67fe28bSAdrian Chadd 	mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
186d67fe28bSAdrian Chadd 	spigen_sysctl_init(sc);
187d67fe28bSAdrian Chadd 
188d67fe28bSAdrian Chadd 	return (0);
189d67fe28bSAdrian Chadd }
190d67fe28bSAdrian Chadd 
191d67fe28bSAdrian Chadd static int
192d67fe28bSAdrian Chadd spigen_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
193d67fe28bSAdrian Chadd {
194d67fe28bSAdrian Chadd 
195d67fe28bSAdrian Chadd 	return (0);
196d67fe28bSAdrian Chadd }
197d67fe28bSAdrian Chadd 
198d67fe28bSAdrian Chadd static int
199d67fe28bSAdrian Chadd spigen_transfer(struct cdev *cdev, struct spigen_transfer *st)
200d67fe28bSAdrian Chadd {
201d67fe28bSAdrian Chadd 	struct spi_command transfer = SPI_COMMAND_INITIALIZER;
202d67fe28bSAdrian Chadd 	device_t dev = cdev->si_drv1;
203d67fe28bSAdrian Chadd 	struct spigen_softc *sc = device_get_softc(dev);
204d67fe28bSAdrian Chadd 	int error = 0;
205d67fe28bSAdrian Chadd 
206d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
207*3c43a826SOleksandr Tymoshenko 	if (st->st_command.iov_len == 0)
208d67fe28bSAdrian Chadd 		error = EINVAL;
209d67fe28bSAdrian Chadd 	else if (st->st_command.iov_len > sc->sc_command_length_max ||
210d67fe28bSAdrian Chadd 	    st->st_data.iov_len > sc->sc_data_length_max)
211d67fe28bSAdrian Chadd 		error = ENOMEM;
212d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
213d67fe28bSAdrian Chadd 	if (error)
214d67fe28bSAdrian Chadd 		return (error);
215d67fe28bSAdrian Chadd 
216d67fe28bSAdrian Chadd #if 0
217d67fe28bSAdrian Chadd 	device_printf(dev, "cmd %p %u data %p %u\n", st->st_command.iov_base,
218d67fe28bSAdrian Chadd 	    st->st_command.iov_len, st->st_data.iov_base, st->st_data.iov_len);
219d67fe28bSAdrian Chadd #endif
220d67fe28bSAdrian Chadd 	transfer.tx_cmd = transfer.rx_cmd = malloc(st->st_command.iov_len,
221d67fe28bSAdrian Chadd 	    M_DEVBUF, M_WAITOK);
222d67fe28bSAdrian Chadd 	if (transfer.tx_cmd == NULL)
223d67fe28bSAdrian Chadd 		return (ENOMEM);
224*3c43a826SOleksandr Tymoshenko 	if (st->st_data.iov_len > 0) {
225d67fe28bSAdrian Chadd 		transfer.tx_data = transfer.rx_data = malloc(st->st_data.iov_len,
226d67fe28bSAdrian Chadd 		    M_DEVBUF, M_WAITOK);
227d67fe28bSAdrian Chadd 		if (transfer.tx_data == NULL) {
228d67fe28bSAdrian Chadd 			free(transfer.tx_cmd, M_DEVBUF);
229d67fe28bSAdrian Chadd 			return (ENOMEM);
230d67fe28bSAdrian Chadd 		}
231*3c43a826SOleksandr Tymoshenko 	}
232*3c43a826SOleksandr Tymoshenko 	else
233*3c43a826SOleksandr Tymoshenko 		transfer.tx_data = transfer.rx_data = NULL;
234d67fe28bSAdrian Chadd 
235d67fe28bSAdrian Chadd 	error = copyin(st->st_command.iov_base, transfer.tx_cmd,
236d67fe28bSAdrian Chadd 	    transfer.tx_cmd_sz = transfer.rx_cmd_sz = st->st_command.iov_len);
237*3c43a826SOleksandr Tymoshenko 	if ((error == 0) && (st->st_data.iov_len > 0))
238d67fe28bSAdrian Chadd 		error = copyin(st->st_data.iov_base, transfer.tx_data,
239d67fe28bSAdrian Chadd 		    transfer.tx_data_sz = transfer.rx_data_sz =
240d67fe28bSAdrian Chadd 		                          st->st_data.iov_len);
241d67fe28bSAdrian Chadd 	if (error == 0)
242d67fe28bSAdrian Chadd 		error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
243d67fe28bSAdrian Chadd 	if (error == 0) {
244d67fe28bSAdrian Chadd 		error = copyout(transfer.rx_cmd, st->st_command.iov_base,
245d67fe28bSAdrian Chadd 		    transfer.rx_cmd_sz);
246*3c43a826SOleksandr Tymoshenko 		if ((error == 0) && (st->st_data.iov_len > 0))
247d67fe28bSAdrian Chadd 			error = copyout(transfer.rx_data, st->st_data.iov_base,
248d67fe28bSAdrian Chadd 			    transfer.rx_data_sz);
249d67fe28bSAdrian Chadd 	}
250d67fe28bSAdrian Chadd 
251d67fe28bSAdrian Chadd 	free(transfer.tx_cmd, M_DEVBUF);
252d67fe28bSAdrian Chadd 	free(transfer.tx_data, M_DEVBUF);
253d67fe28bSAdrian Chadd 	return (error);
254d67fe28bSAdrian Chadd }
255d67fe28bSAdrian Chadd 
256d67fe28bSAdrian Chadd static int
257d67fe28bSAdrian Chadd spigen_transfer_mmapped(struct cdev *cdev, struct spigen_transfer_mmapped *stm)
258d67fe28bSAdrian Chadd {
259d67fe28bSAdrian Chadd 	struct spi_command transfer = SPI_COMMAND_INITIALIZER;
260d67fe28bSAdrian Chadd 	device_t dev = cdev->si_drv1;
261d67fe28bSAdrian Chadd 	struct spigen_softc *sc = device_get_softc(dev);
262d67fe28bSAdrian Chadd 	int error = 0;
263d67fe28bSAdrian Chadd 
264d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
265d67fe28bSAdrian Chadd 	if (sc->sc_mmap_busy)
266d67fe28bSAdrian Chadd 		error = EBUSY;
267d67fe28bSAdrian Chadd 	else if (stm->stm_command_length > sc->sc_command_length_max ||
268d67fe28bSAdrian Chadd 	    stm->stm_data_length > sc->sc_data_length_max)
269d67fe28bSAdrian Chadd 		error = E2BIG;
270d67fe28bSAdrian Chadd 	else if (sc->sc_mmap_buffer == NULL)
271d67fe28bSAdrian Chadd 		error = EINVAL;
272d67fe28bSAdrian Chadd 	else if (sc->sc_mmap_buffer_size <
273d67fe28bSAdrian Chadd 	    stm->stm_command_length + stm->stm_data_length)
274d67fe28bSAdrian Chadd 		error = ENOMEM;
275d67fe28bSAdrian Chadd 	if (error == 0)
276d67fe28bSAdrian Chadd 		sc->sc_mmap_busy = 1;
277d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
278d67fe28bSAdrian Chadd 	if (error)
279d67fe28bSAdrian Chadd 		return (error);
280d67fe28bSAdrian Chadd 
281d67fe28bSAdrian Chadd 	transfer.tx_cmd = transfer.rx_cmd = (void *)sc->sc_mmap_kvaddr;
282d67fe28bSAdrian Chadd 	transfer.tx_cmd_sz = transfer.rx_cmd_sz = stm->stm_command_length;
283d67fe28bSAdrian Chadd 	transfer.tx_data = transfer.rx_data =
284d67fe28bSAdrian Chadd 	    (void *)(sc->sc_mmap_kvaddr + stm->stm_command_length);
285d67fe28bSAdrian Chadd 	transfer.tx_data_sz = transfer.rx_data_sz = stm->stm_data_length;
286d67fe28bSAdrian Chadd 	error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
287d67fe28bSAdrian Chadd 
288d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
289d67fe28bSAdrian Chadd 	KASSERT(sc->sc_mmap_busy, ("mmap no longer marked busy"));
290d67fe28bSAdrian Chadd 	sc->sc_mmap_busy = 0;
291d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
292d67fe28bSAdrian Chadd 	return (error);
293d67fe28bSAdrian Chadd }
294d67fe28bSAdrian Chadd 
295d67fe28bSAdrian Chadd static int
296d67fe28bSAdrian Chadd spigen_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
297d67fe28bSAdrian Chadd     struct thread *td)
298d67fe28bSAdrian Chadd {
299d67fe28bSAdrian Chadd 	device_t dev = cdev->si_drv1;
300d67fe28bSAdrian Chadd 	struct spigen_softc *sc = device_get_softc(dev);
301d67fe28bSAdrian Chadd 	int error;
302d67fe28bSAdrian Chadd 
303d67fe28bSAdrian Chadd 	switch (cmd) {
304d67fe28bSAdrian Chadd 	case SPIGENIOC_TRANSFER:
305d67fe28bSAdrian Chadd 		error = spigen_transfer(cdev, (struct spigen_transfer *)data);
306d67fe28bSAdrian Chadd 		break;
307d67fe28bSAdrian Chadd 	case SPIGENIOC_TRANSFER_MMAPPED:
308d67fe28bSAdrian Chadd 		error = spigen_transfer_mmapped(cdev, (struct spigen_transfer_mmapped *)data);
309d67fe28bSAdrian Chadd 		break;
310d67fe28bSAdrian Chadd 	case SPIGENIOC_GET_CLOCK_SPEED:
311d67fe28bSAdrian Chadd 		mtx_lock(&sc->sc_mtx);
312d67fe28bSAdrian Chadd 		*(uint32_t *)data = sc->sc_clock_speed;
313d67fe28bSAdrian Chadd 		/* XXX TODO: implement spibus ivar call */
314d67fe28bSAdrian Chadd 		mtx_unlock(&sc->sc_mtx);
315d67fe28bSAdrian Chadd 		error = 0;
316d67fe28bSAdrian Chadd 		break;
317d67fe28bSAdrian Chadd 	case SPIGENIOC_SET_CLOCK_SPEED:
318d67fe28bSAdrian Chadd 		mtx_lock(&sc->sc_mtx);
319d67fe28bSAdrian Chadd 		sc->sc_clock_speed = *(uint32_t *)data;
320d67fe28bSAdrian Chadd 		mtx_unlock(&sc->sc_mtx);
321d67fe28bSAdrian Chadd 		error = 0;
322d67fe28bSAdrian Chadd 		break;
323d67fe28bSAdrian Chadd 	default:
324d67fe28bSAdrian Chadd 		error = EOPNOTSUPP;
325d67fe28bSAdrian Chadd 	}
326d67fe28bSAdrian Chadd 	return (error);
327d67fe28bSAdrian Chadd }
328d67fe28bSAdrian Chadd 
329d67fe28bSAdrian Chadd static int
330d67fe28bSAdrian Chadd spigen_mmap_single(struct cdev *cdev, vm_ooffset_t *offset,
331d67fe28bSAdrian Chadd     vm_size_t size, struct vm_object **object, int nprot)
332d67fe28bSAdrian Chadd {
333d67fe28bSAdrian Chadd 	device_t dev = cdev->si_drv1;
334d67fe28bSAdrian Chadd 	struct spigen_softc *sc = device_get_softc(dev);
335d67fe28bSAdrian Chadd 	vm_page_t *m;
336d67fe28bSAdrian Chadd 	size_t n, pages;
337d67fe28bSAdrian Chadd 
338d67fe28bSAdrian Chadd 	if (size == 0 ||
339d67fe28bSAdrian Chadd 	    (nprot & (PROT_EXEC | PROT_READ | PROT_WRITE))
340d67fe28bSAdrian Chadd 	    != (PROT_READ | PROT_WRITE))
341d67fe28bSAdrian Chadd 		return (EINVAL);
342d67fe28bSAdrian Chadd 	size = roundup2(size, PAGE_SIZE);
343d67fe28bSAdrian Chadd 	pages = size / PAGE_SIZE;
344d67fe28bSAdrian Chadd 
345d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
346d67fe28bSAdrian Chadd 	if (sc->sc_mmap_buffer != NULL) {
347d67fe28bSAdrian Chadd 		mtx_unlock(&sc->sc_mtx);
348d67fe28bSAdrian Chadd 		return (EBUSY);
349d67fe28bSAdrian Chadd 	} else if (size > sc->sc_command_length_max + sc->sc_data_length_max) {
350d67fe28bSAdrian Chadd 		mtx_unlock(&sc->sc_mtx);
351d67fe28bSAdrian Chadd 		return (E2BIG);
352d67fe28bSAdrian Chadd 	}
353d67fe28bSAdrian Chadd 	sc->sc_mmap_buffer_size = size;
354d67fe28bSAdrian Chadd 	*offset = 0;
355d67fe28bSAdrian Chadd 	sc->sc_mmap_buffer = *object = vm_pager_allocate(OBJT_PHYS, 0, size,
356d67fe28bSAdrian Chadd 	    nprot, *offset, curthread->td_ucred);
357d67fe28bSAdrian Chadd 	m = malloc(sizeof(*m) * pages, M_TEMP, M_WAITOK);
358d67fe28bSAdrian Chadd 	VM_OBJECT_WLOCK(*object);
359d67fe28bSAdrian Chadd 	vm_object_reference_locked(*object); // kernel and userland both
360d67fe28bSAdrian Chadd 	for (n = 0; n < pages; n++) {
361d67fe28bSAdrian Chadd 		m[n] = vm_page_grab(*object, n,
362d67fe28bSAdrian Chadd 		    VM_ALLOC_NOBUSY | VM_ALLOC_ZERO | VM_ALLOC_WIRED);
363d67fe28bSAdrian Chadd 		m[n]->valid = VM_PAGE_BITS_ALL;
364d67fe28bSAdrian Chadd 	}
365d67fe28bSAdrian Chadd 	VM_OBJECT_WUNLOCK(*object);
366d67fe28bSAdrian Chadd 	sc->sc_mmap_kvaddr = kva_alloc(size);
367d67fe28bSAdrian Chadd 	pmap_qenter(sc->sc_mmap_kvaddr, m, pages);
368d67fe28bSAdrian Chadd 	free(m, M_TEMP);
369d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
370d67fe28bSAdrian Chadd 
371d67fe28bSAdrian Chadd 	if (*object == NULL)
372d67fe28bSAdrian Chadd 		 return (EINVAL);
373d67fe28bSAdrian Chadd 	return (0);
374d67fe28bSAdrian Chadd }
375d67fe28bSAdrian Chadd 
376d67fe28bSAdrian Chadd static int
377d67fe28bSAdrian Chadd spigen_close(struct cdev *cdev, int fflag, int devtype, struct thread *td)
378d67fe28bSAdrian Chadd {
379d67fe28bSAdrian Chadd 	device_t dev = cdev->si_drv1;
380d67fe28bSAdrian Chadd 	struct spigen_softc *sc = device_get_softc(dev);
381d67fe28bSAdrian Chadd 
382d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
383d67fe28bSAdrian Chadd 	if (sc->sc_mmap_buffer != NULL) {
384d67fe28bSAdrian Chadd 		pmap_qremove(sc->sc_mmap_kvaddr,
385d67fe28bSAdrian Chadd 		    sc->sc_mmap_buffer_size / PAGE_SIZE);
386d67fe28bSAdrian Chadd 		kva_free(sc->sc_mmap_kvaddr, sc->sc_mmap_buffer_size);
387d67fe28bSAdrian Chadd 		sc->sc_mmap_kvaddr = 0;
388d67fe28bSAdrian Chadd 		vm_object_deallocate(sc->sc_mmap_buffer);
389d67fe28bSAdrian Chadd 		sc->sc_mmap_buffer = NULL;
390d67fe28bSAdrian Chadd 		sc->sc_mmap_buffer_size = 0;
391d67fe28bSAdrian Chadd 	}
392d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
393d67fe28bSAdrian Chadd 	return (0);
394d67fe28bSAdrian Chadd }
395d67fe28bSAdrian Chadd 
396d67fe28bSAdrian Chadd static int
397d67fe28bSAdrian Chadd spigen_detach(device_t dev)
398d67fe28bSAdrian Chadd {
399d67fe28bSAdrian Chadd 
400d67fe28bSAdrian Chadd 	return (EIO);
401d67fe28bSAdrian Chadd }
402d67fe28bSAdrian Chadd 
403d67fe28bSAdrian Chadd static devclass_t spigen_devclass;
404d67fe28bSAdrian Chadd 
405d67fe28bSAdrian Chadd static device_method_t spigen_methods[] = {
406d67fe28bSAdrian Chadd 	/* Device interface */
407dac458e0SEmmanuel Vadot #ifdef FDT
408dac458e0SEmmanuel Vadot 	DEVMETHOD(device_identify,	spigen_identify),
409dac458e0SEmmanuel Vadot #endif
410d67fe28bSAdrian Chadd 	DEVMETHOD(device_probe,		spigen_probe),
411d67fe28bSAdrian Chadd 	DEVMETHOD(device_attach,	spigen_attach),
412d67fe28bSAdrian Chadd 	DEVMETHOD(device_detach,	spigen_detach),
413d67fe28bSAdrian Chadd 
414d67fe28bSAdrian Chadd 	{ 0, 0 }
415d67fe28bSAdrian Chadd };
416d67fe28bSAdrian Chadd 
417d67fe28bSAdrian Chadd static driver_t spigen_driver = {
418d67fe28bSAdrian Chadd 	"spigen",
419d67fe28bSAdrian Chadd 	spigen_methods,
420d67fe28bSAdrian Chadd 	sizeof(struct spigen_softc),
421d67fe28bSAdrian Chadd };
422d67fe28bSAdrian Chadd 
423d67fe28bSAdrian Chadd DRIVER_MODULE(spigen, spibus, spigen_driver, spigen_devclass, 0, 0);
424