1300713Sadrian/*- 2300713Sadrian * Copyright (c) 2015 Brian Fundakowski Feldman. All rights reserved. 3300713Sadrian * 4300713Sadrian * Redistribution and use in source and binary forms, with or without 5300713Sadrian * modification, are permitted provided that the following conditions 6300713Sadrian * are met: 7300713Sadrian * 1. Redistributions of source code must retain the above copyright 8300713Sadrian * notice, this list of conditions and the following disclaimer. 9300713Sadrian * 2. Redistributions in binary form must reproduce the above copyright 10300713Sadrian * notice, this list of conditions and the following disclaimer in the 11300713Sadrian * documentation and/or other materials provided with the distribution. 12300713Sadrian * 13300713Sadrian * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 14300713Sadrian * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15300713Sadrian * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 16300713Sadrian * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 17300713Sadrian * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18300713Sadrian * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19300713Sadrian * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20300713Sadrian * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21300713Sadrian * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22300713Sadrian * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23300713Sadrian */ 24300713Sadrian 25300713Sadrian#include <sys/cdefs.h> 26300713Sadrian__FBSDID("$FreeBSD: stable/11/sys/dev/spibus/spigen.c 346547 2019-04-22 13:45:08Z ian $"); 27300713Sadrian 28329272Sgonzo#include "opt_platform.h" 29346517Sian#include "opt_spi.h" 30329272Sgonzo 31300713Sadrian#include <sys/param.h> 32300713Sadrian#include <sys/systm.h> 33300713Sadrian#include <sys/bus.h> 34300713Sadrian#include <sys/conf.h> 35300713Sadrian#include <sys/kernel.h> 36300713Sadrian#include <sys/lock.h> 37300713Sadrian#include <sys/malloc.h> 38300713Sadrian#include <sys/mman.h> 39300713Sadrian#include <sys/mutex.h> 40300713Sadrian#include <sys/module.h> 41300713Sadrian#include <sys/proc.h> 42300713Sadrian#include <sys/rwlock.h> 43300713Sadrian#include <sys/spigenio.h> 44300713Sadrian#include <sys/types.h> 45300713Sadrian 46300713Sadrian#include <vm/vm.h> 47300713Sadrian#include <vm/vm_extern.h> 48300713Sadrian#include <vm/vm_object.h> 49300713Sadrian#include <vm/vm_page.h> 50300713Sadrian#include <vm/vm_pager.h> 51300713Sadrian 52300713Sadrian#include <dev/spibus/spi.h> 53332942Sian#include <dev/spibus/spibusvar.h> 54300713Sadrian 55332942Sian#ifdef FDT 56332942Sian#include <dev/ofw/ofw_bus_subr.h> 57346547Sian 58346547Sianstatic struct ofw_compat_data compat_data[] = { 59346547Sian {"freebsd,spigen", true}, 60346547Sian {NULL, false} 61346547Sian}; 62346547Sian 63332942Sian#endif 64332942Sian 65300713Sadrian#include "spibus_if.h" 66300713Sadrian 67300713Sadrianstruct spigen_softc { 68300713Sadrian device_t sc_dev; 69300713Sadrian struct cdev *sc_cdev; 70346517Sian#ifdef SPIGEN_LEGACY_CDEVNAME 71346517Sian struct cdev *sc_adev; /* alias device */ 72346517Sian#endif 73300713Sadrian struct mtx sc_mtx; 74300713Sadrian}; 75300713Sadrian 76346547Sianstruct spigen_mmap { 77346547Sian vm_object_t bufobj; 78346547Sian vm_offset_t kvaddr; 79346547Sian size_t bufsize; 80346547Sian}; 81346547Sian 82300713Sadrianstatic int 83300713Sadrianspigen_probe(device_t dev) 84300713Sadrian{ 85332942Sian int rv; 86311342Sgonzo 87332942Sian /* 88332942Sian * By default we only bid to attach if specifically added by our parent 89332942Sian * (usually via hint.spigen.#.at=busname). On FDT systems we bid as the 90332942Sian * default driver based on being configured in the FDT data. 91332942Sian */ 92332942Sian rv = BUS_PROBE_NOWILDCARD; 93332942Sian 94332942Sian#ifdef FDT 95332942Sian if (ofw_bus_status_okay(dev) && 96346547Sian ofw_bus_search_compatible(dev, compat_data)->ocd_data) 97332942Sian rv = BUS_PROBE_DEFAULT; 98332942Sian#endif 99332942Sian 100300713Sadrian device_set_desc(dev, "SPI Generic IO"); 101311342Sgonzo 102332942Sian return (rv); 103300713Sadrian} 104300713Sadrian 105300713Sadrianstatic int spigen_open(struct cdev *, int, int, struct thread *); 106300713Sadrianstatic int spigen_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *); 107300713Sadrianstatic int spigen_close(struct cdev *, int, int, struct thread *); 108300713Sadrianstatic d_mmap_single_t spigen_mmap_single; 109300713Sadrian 110300713Sadrianstatic struct cdevsw spigen_cdevsw = { 111300713Sadrian .d_version = D_VERSION, 112300713Sadrian .d_name = "spigen", 113300713Sadrian .d_open = spigen_open, 114300713Sadrian .d_ioctl = spigen_ioctl, 115300713Sadrian .d_mmap_single = spigen_mmap_single, 116300713Sadrian .d_close = spigen_close 117300713Sadrian}; 118300713Sadrian 119300713Sadrianstatic int 120300713Sadrianspigen_attach(device_t dev) 121300713Sadrian{ 122300713Sadrian struct spigen_softc *sc; 123300713Sadrian const int unit = device_get_unit(dev); 124346517Sian int cs, res; 125346517Sian struct make_dev_args mda; 126300713Sadrian 127346517Sian spibus_get_cs(dev, &cs); 128346517Sian cs &= ~SPIBUS_CS_HIGH; /* trim 'cs high' bit */ 129346517Sian 130300713Sadrian sc = device_get_softc(dev); 131300713Sadrian sc->sc_dev = dev; 132346517Sian 133300713Sadrian mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); 134346517Sian 135346517Sian make_dev_args_init(&mda); 136346517Sian mda.mda_flags = MAKEDEV_WAITOK; 137346517Sian mda.mda_devsw = &spigen_cdevsw; 138346517Sian mda.mda_cr = NULL; 139346517Sian mda.mda_uid = UID_ROOT; 140346517Sian mda.mda_gid = GID_OPERATOR; 141346517Sian mda.mda_mode = 0660; 142346517Sian mda.mda_unit = unit; 143346517Sian mda.mda_si_drv1 = dev; 144346517Sian 145346517Sian res = make_dev_s(&mda, &(sc->sc_cdev), "spigen%d.%d", 146346517Sian device_get_unit(device_get_parent(dev)), cs); 147346517Sian if (res) { 148346517Sian return res; 149346517Sian } 150346517Sian 151346517Sian#ifdef SPIGEN_LEGACY_CDEVNAME 152346517Sian res = make_dev_alias_p(0, &sc->sc_adev, sc->sc_cdev, "spigen%d", unit); 153346517Sian if (res) { 154346517Sian if (sc->sc_cdev) { 155346517Sian destroy_dev(sc->sc_cdev); 156346517Sian sc->sc_cdev = NULL; 157346517Sian } 158346517Sian return res; 159346517Sian } 160346517Sian#endif 161346517Sian 162300713Sadrian return (0); 163300713Sadrian} 164300713Sadrian 165300713Sadrianstatic int 166329278Sgonzospigen_open(struct cdev *cdev, int oflags, int devtype, struct thread *td) 167300713Sadrian{ 168329278Sgonzo device_t dev; 169329278Sgonzo struct spigen_softc *sc; 170300713Sadrian 171329278Sgonzo dev = cdev->si_drv1; 172329278Sgonzo sc = device_get_softc(dev); 173329278Sgonzo 174329278Sgonzo mtx_lock(&sc->sc_mtx); 175346547Sian device_busy(sc->sc_dev); 176329278Sgonzo mtx_unlock(&sc->sc_mtx); 177329278Sgonzo 178346547Sian return (0); 179300713Sadrian} 180300713Sadrian 181300713Sadrianstatic int 182300713Sadrianspigen_transfer(struct cdev *cdev, struct spigen_transfer *st) 183300713Sadrian{ 184300713Sadrian struct spi_command transfer = SPI_COMMAND_INITIALIZER; 185300713Sadrian device_t dev = cdev->si_drv1; 186300713Sadrian int error = 0; 187300713Sadrian 188300713Sadrian#if 0 189300713Sadrian device_printf(dev, "cmd %p %u data %p %u\n", st->st_command.iov_base, 190300713Sadrian st->st_command.iov_len, st->st_data.iov_base, st->st_data.iov_len); 191300713Sadrian#endif 192346547Sian 193346547Sian if (st->st_command.iov_len == 0) 194346547Sian return (EINVAL); 195346547Sian 196300713Sadrian transfer.tx_cmd = transfer.rx_cmd = malloc(st->st_command.iov_len, 197300713Sadrian M_DEVBUF, M_WAITOK); 198311342Sgonzo if (st->st_data.iov_len > 0) { 199311342Sgonzo transfer.tx_data = transfer.rx_data = malloc(st->st_data.iov_len, 200311342Sgonzo M_DEVBUF, M_WAITOK); 201300713Sadrian } 202311342Sgonzo else 203311342Sgonzo transfer.tx_data = transfer.rx_data = NULL; 204300713Sadrian 205300713Sadrian error = copyin(st->st_command.iov_base, transfer.tx_cmd, 206300713Sadrian transfer.tx_cmd_sz = transfer.rx_cmd_sz = st->st_command.iov_len); 207311342Sgonzo if ((error == 0) && (st->st_data.iov_len > 0)) 208300713Sadrian error = copyin(st->st_data.iov_base, transfer.tx_data, 209300713Sadrian transfer.tx_data_sz = transfer.rx_data_sz = 210300713Sadrian st->st_data.iov_len); 211300713Sadrian if (error == 0) 212300713Sadrian error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer); 213300713Sadrian if (error == 0) { 214300713Sadrian error = copyout(transfer.rx_cmd, st->st_command.iov_base, 215300713Sadrian transfer.rx_cmd_sz); 216311342Sgonzo if ((error == 0) && (st->st_data.iov_len > 0)) 217300713Sadrian error = copyout(transfer.rx_data, st->st_data.iov_base, 218300713Sadrian transfer.rx_data_sz); 219300713Sadrian } 220300713Sadrian 221300713Sadrian free(transfer.tx_cmd, M_DEVBUF); 222300713Sadrian free(transfer.tx_data, M_DEVBUF); 223300713Sadrian return (error); 224300713Sadrian} 225300713Sadrian 226300713Sadrianstatic int 227300713Sadrianspigen_transfer_mmapped(struct cdev *cdev, struct spigen_transfer_mmapped *stm) 228300713Sadrian{ 229300713Sadrian struct spi_command transfer = SPI_COMMAND_INITIALIZER; 230300713Sadrian device_t dev = cdev->si_drv1; 231346547Sian struct spigen_mmap *mmap; 232346547Sian int error; 233300713Sadrian 234346547Sian if ((error = devfs_get_cdevpriv((void **)&mmap)) != 0) 235300713Sadrian return (error); 236346547Sian 237346547Sian if (mmap->bufsize < stm->stm_command_length + stm->stm_data_length) 238346547Sian return (E2BIG); 239346547Sian 240346547Sian transfer.tx_cmd = transfer.rx_cmd = (void *)((uintptr_t)mmap->kvaddr); 241300713Sadrian transfer.tx_cmd_sz = transfer.rx_cmd_sz = stm->stm_command_length; 242300713Sadrian transfer.tx_data = transfer.rx_data = 243346547Sian (void *)((uintptr_t)mmap->kvaddr + stm->stm_command_length); 244300713Sadrian transfer.tx_data_sz = transfer.rx_data_sz = stm->stm_data_length; 245300713Sadrian error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer); 246300713Sadrian 247300713Sadrian return (error); 248300713Sadrian} 249300713Sadrian 250300713Sadrianstatic int 251300713Sadrianspigen_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag, 252300713Sadrian struct thread *td) 253300713Sadrian{ 254300713Sadrian device_t dev = cdev->si_drv1; 255300713Sadrian int error; 256300713Sadrian 257300713Sadrian switch (cmd) { 258300713Sadrian case SPIGENIOC_TRANSFER: 259300713Sadrian error = spigen_transfer(cdev, (struct spigen_transfer *)data); 260300713Sadrian break; 261300713Sadrian case SPIGENIOC_TRANSFER_MMAPPED: 262300713Sadrian error = spigen_transfer_mmapped(cdev, (struct spigen_transfer_mmapped *)data); 263300713Sadrian break; 264300713Sadrian case SPIGENIOC_GET_CLOCK_SPEED: 265332942Sian error = spibus_get_clock(dev, (uint32_t *)data); 266300713Sadrian break; 267300713Sadrian case SPIGENIOC_SET_CLOCK_SPEED: 268332942Sian error = spibus_set_clock(dev, *(uint32_t *)data); 269300713Sadrian break; 270332942Sian case SPIGENIOC_GET_SPI_MODE: 271332942Sian error = spibus_get_mode(dev, (uint32_t *)data); 272332942Sian break; 273332942Sian case SPIGENIOC_SET_SPI_MODE: 274332942Sian error = spibus_set_mode(dev, *(uint32_t *)data); 275332942Sian break; 276300713Sadrian default: 277332942Sian error = ENOTTY; 278332942Sian break; 279300713Sadrian } 280300713Sadrian return (error); 281300713Sadrian} 282300713Sadrian 283346547Sianstatic void 284346547Sianspigen_mmap_cleanup(void *arg) 285346547Sian{ 286346547Sian struct spigen_mmap *mmap = arg; 287346547Sian 288346547Sian if (mmap->kvaddr != 0) 289346547Sian pmap_qremove(mmap->kvaddr, mmap->bufsize / PAGE_SIZE); 290346547Sian if (mmap->bufobj != NULL) 291346547Sian vm_object_deallocate(mmap->bufobj); 292346547Sian free(mmap, M_DEVBUF); 293346547Sian} 294346547Sian 295300713Sadrianstatic int 296300713Sadrianspigen_mmap_single(struct cdev *cdev, vm_ooffset_t *offset, 297300713Sadrian vm_size_t size, struct vm_object **object, int nprot) 298300713Sadrian{ 299346547Sian struct spigen_mmap *mmap; 300300713Sadrian vm_page_t *m; 301300713Sadrian size_t n, pages; 302346547Sian int error; 303300713Sadrian 304300713Sadrian if (size == 0 || 305300713Sadrian (nprot & (PROT_EXEC | PROT_READ | PROT_WRITE)) 306300713Sadrian != (PROT_READ | PROT_WRITE)) 307300713Sadrian return (EINVAL); 308300713Sadrian size = roundup2(size, PAGE_SIZE); 309300713Sadrian pages = size / PAGE_SIZE; 310300713Sadrian 311346547Sian if (devfs_get_cdevpriv((void **)&mmap) == 0) 312300713Sadrian return (EBUSY); 313346547Sian 314346547Sian mmap = malloc(sizeof(*mmap), M_DEVBUF, M_ZERO | M_WAITOK); 315346547Sian if ((mmap->kvaddr = kva_alloc(size)) == 0) { 316346547Sian spigen_mmap_cleanup(mmap); 317346547Sian return (ENOMEM); 318300713Sadrian } 319346547Sian mmap->bufsize = size; 320346547Sian mmap->bufobj = vm_pager_allocate(OBJT_PHYS, 0, size, nprot, 0, 321346547Sian curthread->td_ucred); 322346547Sian 323300713Sadrian m = malloc(sizeof(*m) * pages, M_TEMP, M_WAITOK); 324346547Sian VM_OBJECT_WLOCK(mmap->bufobj); 325346547Sian vm_object_reference_locked(mmap->bufobj); // kernel and userland both 326300713Sadrian for (n = 0; n < pages; n++) { 327346547Sian m[n] = vm_page_grab(mmap->bufobj, n, 328300713Sadrian VM_ALLOC_NOBUSY | VM_ALLOC_ZERO | VM_ALLOC_WIRED); 329300713Sadrian m[n]->valid = VM_PAGE_BITS_ALL; 330300713Sadrian } 331346547Sian VM_OBJECT_WUNLOCK(mmap->bufobj); 332346547Sian pmap_qenter(mmap->kvaddr, m, pages); 333300713Sadrian free(m, M_TEMP); 334300713Sadrian 335346547Sian if ((error = devfs_set_cdevpriv(mmap, spigen_mmap_cleanup)) != 0) { 336346547Sian /* Two threads were racing through this code; we lost. */ 337346547Sian spigen_mmap_cleanup(mmap); 338346547Sian return (error); 339346547Sian } 340346547Sian *offset = 0; 341346547Sian *object = mmap->bufobj; 342346547Sian 343300713Sadrian return (0); 344300713Sadrian} 345300713Sadrian 346300713Sadrianstatic int 347300713Sadrianspigen_close(struct cdev *cdev, int fflag, int devtype, struct thread *td) 348300713Sadrian{ 349300713Sadrian device_t dev = cdev->si_drv1; 350300713Sadrian struct spigen_softc *sc = device_get_softc(dev); 351300713Sadrian 352300713Sadrian mtx_lock(&sc->sc_mtx); 353346547Sian device_unbusy(sc->sc_dev); 354300713Sadrian mtx_unlock(&sc->sc_mtx); 355300713Sadrian return (0); 356300713Sadrian} 357300713Sadrian 358300713Sadrianstatic int 359300713Sadrianspigen_detach(device_t dev) 360300713Sadrian{ 361329278Sgonzo struct spigen_softc *sc; 362300713Sadrian 363329278Sgonzo sc = device_get_softc(dev); 364329278Sgonzo 365346517Sian#ifdef SPIGEN_LEGACY_CDEVNAME 366346517Sian if (sc->sc_adev) 367346517Sian destroy_dev(sc->sc_adev); 368346517Sian#endif 369346517Sian 370346517Sian if (sc->sc_cdev) 371346517Sian destroy_dev(sc->sc_cdev); 372329278Sgonzo 373346547Sian mtx_destroy(&sc->sc_mtx); 374346547Sian 375329278Sgonzo return (0); 376300713Sadrian} 377300713Sadrian 378300713Sadrianstatic devclass_t spigen_devclass; 379300713Sadrian 380300713Sadrianstatic device_method_t spigen_methods[] = { 381300713Sadrian /* Device interface */ 382300713Sadrian DEVMETHOD(device_probe, spigen_probe), 383300713Sadrian DEVMETHOD(device_attach, spigen_attach), 384300713Sadrian DEVMETHOD(device_detach, spigen_detach), 385300713Sadrian 386300713Sadrian { 0, 0 } 387300713Sadrian}; 388300713Sadrian 389300713Sadrianstatic driver_t spigen_driver = { 390300713Sadrian "spigen", 391300713Sadrian spigen_methods, 392300713Sadrian sizeof(struct spigen_softc), 393300713Sadrian}; 394300713Sadrian 395300713SadrianDRIVER_MODULE(spigen, spibus, spigen_driver, spigen_devclass, 0, 0); 396332942SianMODULE_DEPEND(spigen, spibus, 1, 1, 1); 397346547Sian#ifdef FDT 398346547SianSIMPLEBUS_PNP_INFO(compat_data); 399346547Sian#endif 400