2 * Copyright (c) 2015 Brian Fundakowski Feldman. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 #include <sys/cdefs.h>
26 __FBSDID("$FreeBSD$");
28 #include <sys/param.h>
29 #include <sys/systm.h>
32 #include <sys/kernel.h>
34 #include <sys/malloc.h>
36 #include <sys/mutex.h>
37 #include <sys/module.h>
39 #include <sys/rwlock.h>
40 #include <sys/spigenio.h>
41 #include <sys/sysctl.h>
42 #include <sys/types.h>
45 #include <vm/vm_extern.h>
46 #include <vm/vm_object.h>
47 #include <vm/vm_page.h>
48 #include <vm/vm_pager.h>
50 #include <dev/spibus/spi.h>
52 #include "spibus_if.h"
58 uint32_t sc_clock_speed;
59 uint32_t sc_command_length_max; /* cannot change while mmapped */
60 uint32_t sc_data_length_max; /* cannot change while mmapped */
61 vm_object_t sc_mmap_buffer; /* command, then data */
62 vm_offset_t sc_mmap_kvaddr;
63 size_t sc_mmap_buffer_size;
69 spigen_probe(device_t dev)
71 device_set_desc(dev, "SPI Generic IO");
75 static int spigen_open(struct cdev *, int, int, struct thread *);
76 static int spigen_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *);
77 static int spigen_close(struct cdev *, int, int, struct thread *);
78 static d_mmap_single_t spigen_mmap_single;
80 static struct cdevsw spigen_cdevsw = {
81 .d_version = D_VERSION,
83 .d_open = spigen_open,
84 .d_ioctl = spigen_ioctl,
85 .d_mmap_single = spigen_mmap_single,
86 .d_close = spigen_close
90 spigen_command_length_max_proc(SYSCTL_HANDLER_ARGS)
92 struct spigen_softc *sc = (struct spigen_softc *)arg1;
93 uint32_t command_length_max;
96 mtx_lock(&sc->sc_mtx);
97 command_length_max = sc->sc_command_length_max;
98 mtx_unlock(&sc->sc_mtx);
99 error = sysctl_handle_int(oidp, &command_length_max,
100 sizeof(command_length_max), req);
101 if (error == 0 && req->newptr != NULL) {
102 mtx_lock(&sc->sc_mtx);
103 if (sc->sc_mmap_buffer != NULL)
106 sc->sc_command_length_max = command_length_max;
107 mtx_unlock(&sc->sc_mtx);
113 spigen_data_length_max_proc(SYSCTL_HANDLER_ARGS)
115 struct spigen_softc *sc = (struct spigen_softc *)arg1;
116 uint32_t data_length_max;
119 mtx_lock(&sc->sc_mtx);
120 data_length_max = sc->sc_data_length_max;
121 mtx_unlock(&sc->sc_mtx);
122 error = sysctl_handle_int(oidp, &data_length_max,
123 sizeof(data_length_max), req);
124 if (error == 0 && req->newptr != NULL) {
125 mtx_lock(&sc->sc_mtx);
126 if (sc->sc_mmap_buffer != NULL)
129 sc->sc_data_length_max = data_length_max;
130 mtx_unlock(&sc->sc_mtx);
136 spigen_sysctl_init(struct spigen_softc *sc)
138 struct sysctl_ctx_list *ctx;
139 struct sysctl_oid *tree_node;
140 struct sysctl_oid_list *tree;
143 * Add system sysctl tree/handlers.
145 ctx = device_get_sysctl_ctx(sc->sc_dev);
146 tree_node = device_get_sysctl_tree(sc->sc_dev);
147 tree = SYSCTL_CHILDREN(tree_node);
148 SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "command_length_max",
149 CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
150 spigen_command_length_max_proc, "IU", "SPI command header portion (octets)");
151 SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "data_length_max",
152 CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
153 spigen_data_length_max_proc, "IU", "SPI data trailer portion (octets)");
154 SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "data", CTLFLAG_RW,
155 &sc->sc_debug, 0, "debug flags");
160 spigen_attach(device_t dev)
162 struct spigen_softc *sc;
163 const int unit = device_get_unit(dev);
165 sc = device_get_softc(dev);
167 sc->sc_cdev = make_dev(&spigen_cdevsw, unit,
168 UID_ROOT, GID_OPERATOR, 0660, "spigen%d", unit);
169 sc->sc_cdev->si_drv1 = dev;
170 sc->sc_command_length_max = PAGE_SIZE;
171 sc->sc_data_length_max = PAGE_SIZE;
172 mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
173 spigen_sysctl_init(sc);
179 spigen_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
186 spigen_transfer(struct cdev *cdev, struct spigen_transfer *st)
188 struct spi_command transfer = SPI_COMMAND_INITIALIZER;
189 device_t dev = cdev->si_drv1;
190 struct spigen_softc *sc = device_get_softc(dev);
193 mtx_lock(&sc->sc_mtx);
194 if (st->st_command.iov_len == 0 || st->st_data.iov_len == 0)
196 else if (st->st_command.iov_len > sc->sc_command_length_max ||
197 st->st_data.iov_len > sc->sc_data_length_max)
199 mtx_unlock(&sc->sc_mtx);
204 device_printf(dev, "cmd %p %u data %p %u\n", st->st_command.iov_base,
205 st->st_command.iov_len, st->st_data.iov_base, st->st_data.iov_len);
207 transfer.tx_cmd = transfer.rx_cmd = malloc(st->st_command.iov_len,
209 if (transfer.tx_cmd == NULL)
211 transfer.tx_data = transfer.rx_data = malloc(st->st_data.iov_len,
213 if (transfer.tx_data == NULL) {
214 free(transfer.tx_cmd, M_DEVBUF);
218 error = copyin(st->st_command.iov_base, transfer.tx_cmd,
219 transfer.tx_cmd_sz = transfer.rx_cmd_sz = st->st_command.iov_len);
221 error = copyin(st->st_data.iov_base, transfer.tx_data,
222 transfer.tx_data_sz = transfer.rx_data_sz =
223 st->st_data.iov_len);
225 error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
227 error = copyout(transfer.rx_cmd, st->st_command.iov_base,
230 error = copyout(transfer.rx_data, st->st_data.iov_base,
231 transfer.rx_data_sz);
234 free(transfer.tx_cmd, M_DEVBUF);
235 free(transfer.tx_data, M_DEVBUF);
240 spigen_transfer_mmapped(struct cdev *cdev, struct spigen_transfer_mmapped *stm)
242 struct spi_command transfer = SPI_COMMAND_INITIALIZER;
243 device_t dev = cdev->si_drv1;
244 struct spigen_softc *sc = device_get_softc(dev);
247 mtx_lock(&sc->sc_mtx);
248 if (sc->sc_mmap_busy)
250 else if (stm->stm_command_length > sc->sc_command_length_max ||
251 stm->stm_data_length > sc->sc_data_length_max)
253 else if (sc->sc_mmap_buffer == NULL)
255 else if (sc->sc_mmap_buffer_size <
256 stm->stm_command_length + stm->stm_data_length)
259 sc->sc_mmap_busy = 1;
260 mtx_unlock(&sc->sc_mtx);
264 transfer.tx_cmd = transfer.rx_cmd = (void *)sc->sc_mmap_kvaddr;
265 transfer.tx_cmd_sz = transfer.rx_cmd_sz = stm->stm_command_length;
266 transfer.tx_data = transfer.rx_data =
267 (void *)(sc->sc_mmap_kvaddr + stm->stm_command_length);
268 transfer.tx_data_sz = transfer.rx_data_sz = stm->stm_data_length;
269 error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
271 mtx_lock(&sc->sc_mtx);
272 KASSERT(sc->sc_mmap_busy, ("mmap no longer marked busy"));
273 sc->sc_mmap_busy = 0;
274 mtx_unlock(&sc->sc_mtx);
279 spigen_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
282 device_t dev = cdev->si_drv1;
283 struct spigen_softc *sc = device_get_softc(dev);
287 case SPIGENIOC_TRANSFER:
288 error = spigen_transfer(cdev, (struct spigen_transfer *)data);
290 case SPIGENIOC_TRANSFER_MMAPPED:
291 error = spigen_transfer_mmapped(cdev, (struct spigen_transfer_mmapped *)data);
293 case SPIGENIOC_GET_CLOCK_SPEED:
294 mtx_lock(&sc->sc_mtx);
295 *(uint32_t *)data = sc->sc_clock_speed;
296 /* XXX TODO: implement spibus ivar call */
297 mtx_unlock(&sc->sc_mtx);
300 case SPIGENIOC_SET_CLOCK_SPEED:
301 mtx_lock(&sc->sc_mtx);
302 sc->sc_clock_speed = *(uint32_t *)data;
303 mtx_unlock(&sc->sc_mtx);
313 spigen_mmap_single(struct cdev *cdev, vm_ooffset_t *offset,
314 vm_size_t size, struct vm_object **object, int nprot)
316 device_t dev = cdev->si_drv1;
317 struct spigen_softc *sc = device_get_softc(dev);
322 (nprot & (PROT_EXEC | PROT_READ | PROT_WRITE))
323 != (PROT_READ | PROT_WRITE))
325 size = roundup2(size, PAGE_SIZE);
326 pages = size / PAGE_SIZE;
328 mtx_lock(&sc->sc_mtx);
329 if (sc->sc_mmap_buffer != NULL) {
330 mtx_unlock(&sc->sc_mtx);
332 } else if (size > sc->sc_command_length_max + sc->sc_data_length_max) {
333 mtx_unlock(&sc->sc_mtx);
336 sc->sc_mmap_buffer_size = size;
338 sc->sc_mmap_buffer = *object = vm_pager_allocate(OBJT_PHYS, 0, size,
339 nprot, *offset, curthread->td_ucred);
340 m = malloc(sizeof(*m) * pages, M_TEMP, M_WAITOK);
341 VM_OBJECT_WLOCK(*object);
342 vm_object_reference_locked(*object); // kernel and userland both
343 for (n = 0; n < pages; n++) {
344 m[n] = vm_page_grab(*object, n,
345 VM_ALLOC_NOBUSY | VM_ALLOC_ZERO | VM_ALLOC_WIRED);
346 m[n]->valid = VM_PAGE_BITS_ALL;
348 VM_OBJECT_WUNLOCK(*object);
349 sc->sc_mmap_kvaddr = kva_alloc(size);
350 pmap_qenter(sc->sc_mmap_kvaddr, m, pages);
352 mtx_unlock(&sc->sc_mtx);
360 spigen_close(struct cdev *cdev, int fflag, int devtype, struct thread *td)
362 device_t dev = cdev->si_drv1;
363 struct spigen_softc *sc = device_get_softc(dev);
365 mtx_lock(&sc->sc_mtx);
366 if (sc->sc_mmap_buffer != NULL) {
367 pmap_qremove(sc->sc_mmap_kvaddr,
368 sc->sc_mmap_buffer_size / PAGE_SIZE);
369 kva_free(sc->sc_mmap_kvaddr, sc->sc_mmap_buffer_size);
370 sc->sc_mmap_kvaddr = 0;
371 vm_object_deallocate(sc->sc_mmap_buffer);
372 sc->sc_mmap_buffer = NULL;
373 sc->sc_mmap_buffer_size = 0;
375 mtx_unlock(&sc->sc_mtx);
380 spigen_detach(device_t dev)
386 static devclass_t spigen_devclass;
388 static device_method_t spigen_methods[] = {
389 /* Device interface */
390 DEVMETHOD(device_probe, spigen_probe),
391 DEVMETHOD(device_attach, spigen_attach),
392 DEVMETHOD(device_detach, spigen_detach),
397 static driver_t spigen_driver = {
400 sizeof(struct spigen_softc),
403 DRIVER_MODULE(spigen, spibus, spigen_driver, spigen_devclass, 0, 0);