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 "opt_platform.h"
31 #include <sys/param.h>
32 #include <sys/systm.h>
35 #include <sys/kernel.h>
37 #include <sys/malloc.h>
39 #include <sys/mutex.h>
40 #include <sys/module.h>
42 #include <sys/rwlock.h>
43 #include <sys/spigenio.h>
44 #include <sys/types.h>
47 #include <vm/vm_extern.h>
48 #include <vm/vm_object.h>
49 #include <vm/vm_page.h>
50 #include <vm/vm_pager.h>
52 #include <dev/spibus/spi.h>
53 #include <dev/spibus/spibusvar.h>
56 #include <dev/ofw/ofw_bus_subr.h>
58 static struct ofw_compat_data compat_data[] = {
59 {"freebsd,spigen", true},
65 #include "spibus_if.h"
70 #ifdef SPIGEN_LEGACY_CDEVNAME
71 struct cdev *sc_adev; /* alias device */
83 spigen_probe(device_t dev)
88 * By default we only bid to attach if specifically added by our parent
89 * (usually via hint.spigen.#.at=busname). On FDT systems we bid as the
90 * default driver based on being configured in the FDT data.
92 rv = BUS_PROBE_NOWILDCARD;
95 if (ofw_bus_status_okay(dev) &&
96 ofw_bus_search_compatible(dev, compat_data)->ocd_data)
97 rv = BUS_PROBE_DEFAULT;
100 device_set_desc(dev, "SPI Generic IO");
105 static int spigen_open(struct cdev *, int, int, struct thread *);
106 static int spigen_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *);
107 static int spigen_close(struct cdev *, int, int, struct thread *);
108 static d_mmap_single_t spigen_mmap_single;
110 static struct cdevsw spigen_cdevsw = {
111 .d_version = D_VERSION,
113 .d_open = spigen_open,
114 .d_ioctl = spigen_ioctl,
115 .d_mmap_single = spigen_mmap_single,
116 .d_close = spigen_close
120 spigen_attach(device_t dev)
122 struct spigen_softc *sc;
123 const int unit = device_get_unit(dev);
125 struct make_dev_args mda;
127 spibus_get_cs(dev, &cs);
128 cs &= ~SPIBUS_CS_HIGH; /* trim 'cs high' bit */
130 sc = device_get_softc(dev);
133 mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
135 make_dev_args_init(&mda);
136 mda.mda_flags = MAKEDEV_WAITOK;
137 mda.mda_devsw = &spigen_cdevsw;
139 mda.mda_uid = UID_ROOT;
140 mda.mda_gid = GID_OPERATOR;
143 mda.mda_si_drv1 = dev;
145 res = make_dev_s(&mda, &(sc->sc_cdev), "spigen%d.%d",
146 device_get_unit(device_get_parent(dev)), cs);
151 #ifdef SPIGEN_LEGACY_CDEVNAME
152 res = make_dev_alias_p(0, &sc->sc_adev, sc->sc_cdev, "spigen%d", unit);
155 destroy_dev(sc->sc_cdev);
166 spigen_open(struct cdev *cdev, int oflags, int devtype, struct thread *td)
169 struct spigen_softc *sc;
172 sc = device_get_softc(dev);
174 mtx_lock(&sc->sc_mtx);
175 device_busy(sc->sc_dev);
176 mtx_unlock(&sc->sc_mtx);
182 spigen_transfer(struct cdev *cdev, struct spigen_transfer *st)
184 struct spi_command transfer = SPI_COMMAND_INITIALIZER;
185 device_t dev = cdev->si_drv1;
189 device_printf(dev, "cmd %p %u data %p %u\n", st->st_command.iov_base,
190 st->st_command.iov_len, st->st_data.iov_base, st->st_data.iov_len);
193 if (st->st_command.iov_len == 0)
196 transfer.tx_cmd = transfer.rx_cmd = malloc(st->st_command.iov_len,
198 if (st->st_data.iov_len > 0) {
199 transfer.tx_data = transfer.rx_data = malloc(st->st_data.iov_len,
203 transfer.tx_data = transfer.rx_data = NULL;
205 error = copyin(st->st_command.iov_base, transfer.tx_cmd,
206 transfer.tx_cmd_sz = transfer.rx_cmd_sz = st->st_command.iov_len);
207 if ((error == 0) && (st->st_data.iov_len > 0))
208 error = copyin(st->st_data.iov_base, transfer.tx_data,
209 transfer.tx_data_sz = transfer.rx_data_sz =
210 st->st_data.iov_len);
212 error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
214 error = copyout(transfer.rx_cmd, st->st_command.iov_base,
216 if ((error == 0) && (st->st_data.iov_len > 0))
217 error = copyout(transfer.rx_data, st->st_data.iov_base,
218 transfer.rx_data_sz);
221 free(transfer.tx_cmd, M_DEVBUF);
222 free(transfer.tx_data, M_DEVBUF);
227 spigen_transfer_mmapped(struct cdev *cdev, struct spigen_transfer_mmapped *stm)
229 struct spi_command transfer = SPI_COMMAND_INITIALIZER;
230 device_t dev = cdev->si_drv1;
231 struct spigen_mmap *mmap;
234 if ((error = devfs_get_cdevpriv((void **)&mmap)) != 0)
237 if (mmap->bufsize < stm->stm_command_length + stm->stm_data_length)
240 transfer.tx_cmd = transfer.rx_cmd = (void *)((uintptr_t)mmap->kvaddr);
241 transfer.tx_cmd_sz = transfer.rx_cmd_sz = stm->stm_command_length;
242 transfer.tx_data = transfer.rx_data =
243 (void *)((uintptr_t)mmap->kvaddr + stm->stm_command_length);
244 transfer.tx_data_sz = transfer.rx_data_sz = stm->stm_data_length;
245 error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
251 spigen_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
254 device_t dev = cdev->si_drv1;
258 case SPIGENIOC_TRANSFER:
259 error = spigen_transfer(cdev, (struct spigen_transfer *)data);
261 case SPIGENIOC_TRANSFER_MMAPPED:
262 error = spigen_transfer_mmapped(cdev, (struct spigen_transfer_mmapped *)data);
264 case SPIGENIOC_GET_CLOCK_SPEED:
265 error = spibus_get_clock(dev, (uint32_t *)data);
267 case SPIGENIOC_SET_CLOCK_SPEED:
268 error = spibus_set_clock(dev, *(uint32_t *)data);
270 case SPIGENIOC_GET_SPI_MODE:
271 error = spibus_get_mode(dev, (uint32_t *)data);
273 case SPIGENIOC_SET_SPI_MODE:
274 error = spibus_set_mode(dev, *(uint32_t *)data);
284 spigen_mmap_cleanup(void *arg)
286 struct spigen_mmap *mmap = arg;
288 if (mmap->kvaddr != 0)
289 pmap_qremove(mmap->kvaddr, mmap->bufsize / PAGE_SIZE);
290 if (mmap->bufobj != NULL)
291 vm_object_deallocate(mmap->bufobj);
292 free(mmap, M_DEVBUF);
296 spigen_mmap_single(struct cdev *cdev, vm_ooffset_t *offset,
297 vm_size_t size, struct vm_object **object, int nprot)
299 struct spigen_mmap *mmap;
305 (nprot & (PROT_EXEC | PROT_READ | PROT_WRITE))
306 != (PROT_READ | PROT_WRITE))
308 size = roundup2(size, PAGE_SIZE);
309 pages = size / PAGE_SIZE;
311 if (devfs_get_cdevpriv((void **)&mmap) == 0)
314 mmap = malloc(sizeof(*mmap), M_DEVBUF, M_ZERO | M_WAITOK);
315 if ((mmap->kvaddr = kva_alloc(size)) == 0) {
316 spigen_mmap_cleanup(mmap);
319 mmap->bufsize = size;
320 mmap->bufobj = vm_pager_allocate(OBJT_PHYS, 0, size, nprot, 0,
321 curthread->td_ucred);
323 m = malloc(sizeof(*m) * pages, M_TEMP, M_WAITOK);
324 VM_OBJECT_WLOCK(mmap->bufobj);
325 vm_object_reference_locked(mmap->bufobj); // kernel and userland both
326 for (n = 0; n < pages; n++) {
327 m[n] = vm_page_grab(mmap->bufobj, n,
328 VM_ALLOC_NOBUSY | VM_ALLOC_ZERO | VM_ALLOC_WIRED);
329 m[n]->valid = VM_PAGE_BITS_ALL;
331 VM_OBJECT_WUNLOCK(mmap->bufobj);
332 pmap_qenter(mmap->kvaddr, m, pages);
335 if ((error = devfs_set_cdevpriv(mmap, spigen_mmap_cleanup)) != 0) {
336 /* Two threads were racing through this code; we lost. */
337 spigen_mmap_cleanup(mmap);
341 *object = mmap->bufobj;
347 spigen_close(struct cdev *cdev, int fflag, int devtype, struct thread *td)
349 device_t dev = cdev->si_drv1;
350 struct spigen_softc *sc = device_get_softc(dev);
352 mtx_lock(&sc->sc_mtx);
353 device_unbusy(sc->sc_dev);
354 mtx_unlock(&sc->sc_mtx);
359 spigen_detach(device_t dev)
361 struct spigen_softc *sc;
363 sc = device_get_softc(dev);
365 #ifdef SPIGEN_LEGACY_CDEVNAME
367 destroy_dev(sc->sc_adev);
371 destroy_dev(sc->sc_cdev);
373 mtx_destroy(&sc->sc_mtx);
378 static devclass_t spigen_devclass;
380 static device_method_t spigen_methods[] = {
381 /* Device interface */
382 DEVMETHOD(device_probe, spigen_probe),
383 DEVMETHOD(device_attach, spigen_attach),
384 DEVMETHOD(device_detach, spigen_detach),
389 static driver_t spigen_driver = {
392 sizeof(struct spigen_softc),
395 DRIVER_MODULE(spigen, spibus, spigen_driver, spigen_devclass, 0, 0);
396 MODULE_DEPEND(spigen, spibus, 1, 1, 1);
398 SIMPLEBUS_PNP_INFO(compat_data);