1 /*- 2 * Copyright (c) 2015 Brian Fundakowski Feldman. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 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. 12 * 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. 23 */ 24 25 #include <sys/cdefs.h> 26 __FBSDID("$FreeBSD$"); 27 28 #include "opt_platform.h" 29 #include "opt_spi.h" 30 31 #include <sys/param.h> 32 #include <sys/systm.h> 33 #include <sys/bus.h> 34 #include <sys/conf.h> 35 #include <sys/kernel.h> 36 #include <sys/lock.h> 37 #include <sys/malloc.h> 38 #include <sys/mman.h> 39 #include <sys/mutex.h> 40 #include <sys/module.h> 41 #include <sys/proc.h> 42 #include <sys/rwlock.h> 43 #include <sys/spigenio.h> 44 #include <sys/types.h> 45 46 #include <vm/vm.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> 51 52 #include <dev/spibus/spi.h> 53 #include <dev/spibus/spibusvar.h> 54 55 #ifdef FDT 56 #include <dev/ofw/ofw_bus_subr.h> 57 58 static struct ofw_compat_data compat_data[] = { 59 {"freebsd,spigen", true}, 60 {NULL, false} 61 }; 62 63 #endif 64 65 #include "spibus_if.h" 66 67 struct spigen_softc { 68 device_t sc_dev; 69 struct cdev *sc_cdev; 70 #ifdef SPIGEN_LEGACY_CDEVNAME 71 struct cdev *sc_adev; /* alias device */ 72 #endif 73 struct mtx sc_mtx; 74 }; 75 76 struct spigen_mmap { 77 vm_object_t bufobj; 78 vm_offset_t kvaddr; 79 size_t bufsize; 80 }; 81 82 static int 83 spigen_probe(device_t dev) 84 { 85 int rv; 86 87 /* 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. 91 */ 92 rv = BUS_PROBE_NOWILDCARD; 93 94 #ifdef FDT 95 if (ofw_bus_status_okay(dev) && 96 ofw_bus_search_compatible(dev, compat_data)->ocd_data) 97 rv = BUS_PROBE_DEFAULT; 98 #endif 99 100 device_set_desc(dev, "SPI Generic IO"); 101 102 return (rv); 103 } 104 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; 109 110 static struct cdevsw spigen_cdevsw = { 111 .d_version = D_VERSION, 112 .d_name = "spigen", 113 .d_open = spigen_open, 114 .d_ioctl = spigen_ioctl, 115 .d_mmap_single = spigen_mmap_single, 116 .d_close = spigen_close 117 }; 118 119 static int 120 spigen_attach(device_t dev) 121 { 122 struct spigen_softc *sc; 123 const int unit = device_get_unit(dev); 124 int cs, res; 125 struct make_dev_args mda; 126 127 spibus_get_cs(dev, &cs); 128 cs &= ~SPIBUS_CS_HIGH; /* trim 'cs high' bit */ 129 130 sc = device_get_softc(dev); 131 sc->sc_dev = dev; 132 133 mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); 134 135 make_dev_args_init(&mda); 136 mda.mda_flags = MAKEDEV_WAITOK; 137 mda.mda_devsw = &spigen_cdevsw; 138 mda.mda_cr = NULL; 139 mda.mda_uid = UID_ROOT; 140 mda.mda_gid = GID_OPERATOR; 141 mda.mda_mode = 0660; 142 mda.mda_unit = unit; 143 mda.mda_si_drv1 = dev; 144 145 res = make_dev_s(&mda, &(sc->sc_cdev), "spigen%d.%d", 146 device_get_unit(device_get_parent(dev)), cs); 147 if (res) { 148 return res; 149 } 150 151 #ifdef SPIGEN_LEGACY_CDEVNAME 152 res = make_dev_alias_p(0, &sc->sc_adev, sc->sc_cdev, "spigen%d", unit); 153 if (res) { 154 if (sc->sc_cdev) { 155 destroy_dev(sc->sc_cdev); 156 sc->sc_cdev = NULL; 157 } 158 return res; 159 } 160 #endif 161 162 return (0); 163 } 164 165 static int 166 spigen_open(struct cdev *cdev, int oflags, int devtype, struct thread *td) 167 { 168 device_t dev; 169 struct spigen_softc *sc; 170 171 dev = cdev->si_drv1; 172 sc = device_get_softc(dev); 173 174 mtx_lock(&sc->sc_mtx); 175 device_busy(sc->sc_dev); 176 mtx_unlock(&sc->sc_mtx); 177 178 return (0); 179 } 180 181 static int 182 spigen_transfer(struct cdev *cdev, struct spigen_transfer *st) 183 { 184 struct spi_command transfer = SPI_COMMAND_INITIALIZER; 185 device_t dev = cdev->si_drv1; 186 int error = 0; 187 188 #if 0 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); 191 #endif 192 193 if (st->st_command.iov_len == 0) 194 return (EINVAL); 195 196 transfer.tx_cmd = transfer.rx_cmd = malloc(st->st_command.iov_len, 197 M_DEVBUF, M_WAITOK); 198 if (st->st_data.iov_len > 0) { 199 transfer.tx_data = transfer.rx_data = malloc(st->st_data.iov_len, 200 M_DEVBUF, M_WAITOK); 201 } 202 else 203 transfer.tx_data = transfer.rx_data = NULL; 204 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); 211 if (error == 0) 212 error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer); 213 if (error == 0) { 214 error = copyout(transfer.rx_cmd, st->st_command.iov_base, 215 transfer.rx_cmd_sz); 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); 219 } 220 221 free(transfer.tx_cmd, M_DEVBUF); 222 free(transfer.tx_data, M_DEVBUF); 223 return (error); 224 } 225 226 static int 227 spigen_transfer_mmapped(struct cdev *cdev, struct spigen_transfer_mmapped *stm) 228 { 229 struct spi_command transfer = SPI_COMMAND_INITIALIZER; 230 device_t dev = cdev->si_drv1; 231 struct spigen_mmap *mmap; 232 int error; 233 234 if ((error = devfs_get_cdevpriv((void **)&mmap)) != 0) 235 return (error); 236 237 if (mmap->bufsize < stm->stm_command_length + stm->stm_data_length) 238 return (E2BIG); 239 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); 246 247 return (error); 248 } 249 250 static int 251 spigen_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag, 252 struct thread *td) 253 { 254 device_t dev = cdev->si_drv1; 255 int error; 256 257 switch (cmd) { 258 case SPIGENIOC_TRANSFER: 259 error = spigen_transfer(cdev, (struct spigen_transfer *)data); 260 break; 261 case SPIGENIOC_TRANSFER_MMAPPED: 262 error = spigen_transfer_mmapped(cdev, (struct spigen_transfer_mmapped *)data); 263 break; 264 case SPIGENIOC_GET_CLOCK_SPEED: 265 error = spibus_get_clock(dev, (uint32_t *)data); 266 break; 267 case SPIGENIOC_SET_CLOCK_SPEED: 268 error = spibus_set_clock(dev, *(uint32_t *)data); 269 break; 270 case SPIGENIOC_GET_SPI_MODE: 271 error = spibus_get_mode(dev, (uint32_t *)data); 272 break; 273 case SPIGENIOC_SET_SPI_MODE: 274 error = spibus_set_mode(dev, *(uint32_t *)data); 275 break; 276 default: 277 error = ENOTTY; 278 break; 279 } 280 return (error); 281 } 282 283 static void 284 spigen_mmap_cleanup(void *arg) 285 { 286 struct spigen_mmap *mmap = arg; 287 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); 293 } 294 295 static int 296 spigen_mmap_single(struct cdev *cdev, vm_ooffset_t *offset, 297 vm_size_t size, struct vm_object **object, int nprot) 298 { 299 struct spigen_mmap *mmap; 300 vm_page_t *m; 301 size_t n, pages; 302 int error; 303 304 if (size == 0 || 305 (nprot & (PROT_EXEC | PROT_READ | PROT_WRITE)) 306 != (PROT_READ | PROT_WRITE)) 307 return (EINVAL); 308 size = roundup2(size, PAGE_SIZE); 309 pages = size / PAGE_SIZE; 310 311 if (devfs_get_cdevpriv((void **)&mmap) == 0) 312 return (EBUSY); 313 314 mmap = malloc(sizeof(*mmap), M_DEVBUF, M_ZERO | M_WAITOK); 315 if ((mmap->kvaddr = kva_alloc(size)) == 0) { 316 spigen_mmap_cleanup(mmap); 317 return (ENOMEM); 318 } 319 mmap->bufsize = size; 320 mmap->bufobj = vm_pager_allocate(OBJT_PHYS, 0, size, nprot, 0, 321 curthread->td_ucred); 322 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; 330 } 331 VM_OBJECT_WUNLOCK(mmap->bufobj); 332 pmap_qenter(mmap->kvaddr, m, pages); 333 free(m, M_TEMP); 334 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); 338 return (error); 339 } 340 *offset = 0; 341 *object = mmap->bufobj; 342 343 return (0); 344 } 345 346 static int 347 spigen_close(struct cdev *cdev, int fflag, int devtype, struct thread *td) 348 { 349 device_t dev = cdev->si_drv1; 350 struct spigen_softc *sc = device_get_softc(dev); 351 352 mtx_lock(&sc->sc_mtx); 353 device_unbusy(sc->sc_dev); 354 mtx_unlock(&sc->sc_mtx); 355 return (0); 356 } 357 358 static int 359 spigen_detach(device_t dev) 360 { 361 struct spigen_softc *sc; 362 363 sc = device_get_softc(dev); 364 365 #ifdef SPIGEN_LEGACY_CDEVNAME 366 if (sc->sc_adev) 367 destroy_dev(sc->sc_adev); 368 #endif 369 370 if (sc->sc_cdev) 371 destroy_dev(sc->sc_cdev); 372 373 mtx_destroy(&sc->sc_mtx); 374 375 return (0); 376 } 377 378 static devclass_t spigen_devclass; 379 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), 385 386 { 0, 0 } 387 }; 388 389 static driver_t spigen_driver = { 390 "spigen", 391 spigen_methods, 392 sizeof(struct spigen_softc), 393 }; 394 395 DRIVER_MODULE(spigen, spibus, spigen_driver, spigen_devclass, 0, 0); 396 MODULE_DEPEND(spigen, spibus, 1, 1, 1); 397 #ifdef FDT 398 SIMPLEBUS_PNP_INFO(compat_data); 399 #endif 400