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" 29*1fcf4de0SIan Lepore #include "opt_spi.h" 30dac458e0SEmmanuel Vadot 31d67fe28bSAdrian Chadd #include <sys/param.h> 32d67fe28bSAdrian Chadd #include <sys/systm.h> 33d67fe28bSAdrian Chadd #include <sys/bus.h> 34d67fe28bSAdrian Chadd #include <sys/conf.h> 35d67fe28bSAdrian Chadd #include <sys/kernel.h> 36d67fe28bSAdrian Chadd #include <sys/lock.h> 37d67fe28bSAdrian Chadd #include <sys/malloc.h> 38d67fe28bSAdrian Chadd #include <sys/mman.h> 39d67fe28bSAdrian Chadd #include <sys/mutex.h> 40d67fe28bSAdrian Chadd #include <sys/module.h> 41d67fe28bSAdrian Chadd #include <sys/proc.h> 42d67fe28bSAdrian Chadd #include <sys/rwlock.h> 43d67fe28bSAdrian Chadd #include <sys/spigenio.h> 44d67fe28bSAdrian Chadd #include <sys/sysctl.h> 45d67fe28bSAdrian Chadd #include <sys/types.h> 46d67fe28bSAdrian Chadd 47d67fe28bSAdrian Chadd #include <vm/vm.h> 48d67fe28bSAdrian Chadd #include <vm/vm_extern.h> 49d67fe28bSAdrian Chadd #include <vm/vm_object.h> 50d67fe28bSAdrian Chadd #include <vm/vm_page.h> 51d67fe28bSAdrian Chadd #include <vm/vm_pager.h> 52d67fe28bSAdrian Chadd 53d67fe28bSAdrian Chadd #include <dev/spibus/spi.h> 54a0e911e0SIan Lepore #include <dev/spibus/spibusvar.h> 55d67fe28bSAdrian Chadd 563b46d868SIan Lepore #ifdef FDT 573b46d868SIan Lepore #include <dev/ofw/ofw_bus_subr.h> 583b46d868SIan Lepore #endif 593b46d868SIan Lepore 60d67fe28bSAdrian Chadd #include "spibus_if.h" 61d67fe28bSAdrian Chadd 6250868fa6SOleksandr Tymoshenko #define SPIGEN_OPEN (1 << 0) 6350868fa6SOleksandr Tymoshenko #define SPIGEN_MMAP_BUSY (1 << 1) 6450868fa6SOleksandr Tymoshenko 65d67fe28bSAdrian Chadd struct spigen_softc { 66d67fe28bSAdrian Chadd device_t sc_dev; 67d67fe28bSAdrian Chadd struct cdev *sc_cdev; 68*1fcf4de0SIan Lepore #ifdef SPIGEN_LEGACY_CDEVNAME 69*1fcf4de0SIan Lepore struct cdev *sc_adev; /* alias device */ 70*1fcf4de0SIan Lepore #endif 71d67fe28bSAdrian Chadd struct mtx sc_mtx; 72d67fe28bSAdrian Chadd uint32_t sc_command_length_max; /* cannot change while mmapped */ 73d67fe28bSAdrian Chadd uint32_t sc_data_length_max; /* cannot change while mmapped */ 74d67fe28bSAdrian Chadd vm_object_t sc_mmap_buffer; /* command, then data */ 75d67fe28bSAdrian Chadd vm_offset_t sc_mmap_kvaddr; 76d67fe28bSAdrian Chadd size_t sc_mmap_buffer_size; 77d67fe28bSAdrian Chadd int sc_debug; 7850868fa6SOleksandr Tymoshenko int sc_flags; 79d67fe28bSAdrian Chadd }; 80d67fe28bSAdrian Chadd 81d67fe28bSAdrian Chadd static int 82d67fe28bSAdrian Chadd spigen_probe(device_t dev) 83d67fe28bSAdrian Chadd { 843b46d868SIan Lepore int rv; 8532d74127SOleksandr Tymoshenko 86bc7b44aeSIan Lepore /* 87bc7b44aeSIan Lepore * By default we only bid to attach if specifically added by our parent 88bc7b44aeSIan Lepore * (usually via hint.spigen.#.at=busname). On FDT systems we bid as the 89bc7b44aeSIan Lepore * default driver based on being configured in the FDT data. 90bc7b44aeSIan Lepore */ 913b46d868SIan Lepore rv = BUS_PROBE_NOWILDCARD; 92bc7b44aeSIan Lepore 93bc7b44aeSIan Lepore #ifdef FDT 94bc7b44aeSIan Lepore if (ofw_bus_status_okay(dev) && 95bc7b44aeSIan Lepore ofw_bus_is_compatible(dev, "freebsd,spigen")) 96bc7b44aeSIan Lepore rv = BUS_PROBE_DEFAULT; 973b46d868SIan Lepore #endif 98bc7b44aeSIan Lepore 99d67fe28bSAdrian Chadd device_set_desc(dev, "SPI Generic IO"); 10032d74127SOleksandr Tymoshenko 1013b46d868SIan Lepore return (rv); 102d67fe28bSAdrian Chadd } 103d67fe28bSAdrian Chadd 104d67fe28bSAdrian Chadd static int spigen_open(struct cdev *, int, int, struct thread *); 105d67fe28bSAdrian Chadd static int spigen_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *); 106d67fe28bSAdrian Chadd static int spigen_close(struct cdev *, int, int, struct thread *); 107d67fe28bSAdrian Chadd static d_mmap_single_t spigen_mmap_single; 108d67fe28bSAdrian Chadd 109d67fe28bSAdrian Chadd static struct cdevsw spigen_cdevsw = { 110d67fe28bSAdrian Chadd .d_version = D_VERSION, 111d67fe28bSAdrian Chadd .d_name = "spigen", 112d67fe28bSAdrian Chadd .d_open = spigen_open, 113d67fe28bSAdrian Chadd .d_ioctl = spigen_ioctl, 114d67fe28bSAdrian Chadd .d_mmap_single = spigen_mmap_single, 115d67fe28bSAdrian Chadd .d_close = spigen_close 116d67fe28bSAdrian Chadd }; 117d67fe28bSAdrian Chadd 118d67fe28bSAdrian Chadd static int 119d67fe28bSAdrian Chadd spigen_command_length_max_proc(SYSCTL_HANDLER_ARGS) 120d67fe28bSAdrian Chadd { 121d67fe28bSAdrian Chadd struct spigen_softc *sc = (struct spigen_softc *)arg1; 122d67fe28bSAdrian Chadd uint32_t command_length_max; 123d67fe28bSAdrian Chadd int error; 124d67fe28bSAdrian Chadd 125d67fe28bSAdrian Chadd mtx_lock(&sc->sc_mtx); 126d67fe28bSAdrian Chadd command_length_max = sc->sc_command_length_max; 127d67fe28bSAdrian Chadd mtx_unlock(&sc->sc_mtx); 128d67fe28bSAdrian Chadd error = sysctl_handle_int(oidp, &command_length_max, 129d67fe28bSAdrian Chadd sizeof(command_length_max), req); 130d67fe28bSAdrian Chadd if (error == 0 && req->newptr != NULL) { 131d67fe28bSAdrian Chadd mtx_lock(&sc->sc_mtx); 132d67fe28bSAdrian Chadd if (sc->sc_mmap_buffer != NULL) 133d67fe28bSAdrian Chadd error = EBUSY; 134d67fe28bSAdrian Chadd else 135d67fe28bSAdrian Chadd sc->sc_command_length_max = command_length_max; 136d67fe28bSAdrian Chadd mtx_unlock(&sc->sc_mtx); 137d67fe28bSAdrian Chadd } 138d67fe28bSAdrian Chadd return (error); 139d67fe28bSAdrian Chadd } 140d67fe28bSAdrian Chadd 141d67fe28bSAdrian Chadd static int 142d67fe28bSAdrian Chadd spigen_data_length_max_proc(SYSCTL_HANDLER_ARGS) 143d67fe28bSAdrian Chadd { 144d67fe28bSAdrian Chadd struct spigen_softc *sc = (struct spigen_softc *)arg1; 145d67fe28bSAdrian Chadd uint32_t data_length_max; 146d67fe28bSAdrian Chadd int error; 147d67fe28bSAdrian Chadd 148d67fe28bSAdrian Chadd mtx_lock(&sc->sc_mtx); 149d67fe28bSAdrian Chadd data_length_max = sc->sc_data_length_max; 150d67fe28bSAdrian Chadd mtx_unlock(&sc->sc_mtx); 151d67fe28bSAdrian Chadd error = sysctl_handle_int(oidp, &data_length_max, 152d67fe28bSAdrian Chadd sizeof(data_length_max), req); 153d67fe28bSAdrian Chadd if (error == 0 && req->newptr != NULL) { 154d67fe28bSAdrian Chadd mtx_lock(&sc->sc_mtx); 155d67fe28bSAdrian Chadd if (sc->sc_mmap_buffer != NULL) 156d67fe28bSAdrian Chadd error = EBUSY; 157d67fe28bSAdrian Chadd else 158d67fe28bSAdrian Chadd sc->sc_data_length_max = data_length_max; 159d67fe28bSAdrian Chadd mtx_unlock(&sc->sc_mtx); 160d67fe28bSAdrian Chadd } 161d67fe28bSAdrian Chadd return (error); 162d67fe28bSAdrian Chadd } 163d67fe28bSAdrian Chadd 164d67fe28bSAdrian Chadd static void 165d67fe28bSAdrian Chadd spigen_sysctl_init(struct spigen_softc *sc) 166d67fe28bSAdrian Chadd { 167d67fe28bSAdrian Chadd struct sysctl_ctx_list *ctx; 168d67fe28bSAdrian Chadd struct sysctl_oid *tree_node; 169d67fe28bSAdrian Chadd struct sysctl_oid_list *tree; 170d67fe28bSAdrian Chadd 171d67fe28bSAdrian Chadd /* 172d67fe28bSAdrian Chadd * Add system sysctl tree/handlers. 173d67fe28bSAdrian Chadd */ 174d67fe28bSAdrian Chadd ctx = device_get_sysctl_ctx(sc->sc_dev); 175d67fe28bSAdrian Chadd tree_node = device_get_sysctl_tree(sc->sc_dev); 176d67fe28bSAdrian Chadd tree = SYSCTL_CHILDREN(tree_node); 177d67fe28bSAdrian Chadd SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "command_length_max", 178d67fe28bSAdrian Chadd CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc), 179d67fe28bSAdrian Chadd spigen_command_length_max_proc, "IU", "SPI command header portion (octets)"); 180d67fe28bSAdrian Chadd SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "data_length_max", 181d67fe28bSAdrian Chadd CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc), 182d67fe28bSAdrian Chadd spigen_data_length_max_proc, "IU", "SPI data trailer portion (octets)"); 183d67fe28bSAdrian Chadd SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "data", CTLFLAG_RW, 184d67fe28bSAdrian Chadd &sc->sc_debug, 0, "debug flags"); 185d67fe28bSAdrian Chadd 186d67fe28bSAdrian Chadd } 187d67fe28bSAdrian Chadd 188d67fe28bSAdrian Chadd static int 189d67fe28bSAdrian Chadd spigen_attach(device_t dev) 190d67fe28bSAdrian Chadd { 191d67fe28bSAdrian Chadd struct spigen_softc *sc; 192d67fe28bSAdrian Chadd const int unit = device_get_unit(dev); 193*1fcf4de0SIan Lepore int cs, res; 194*1fcf4de0SIan Lepore struct make_dev_args mda; 195*1fcf4de0SIan Lepore 196*1fcf4de0SIan Lepore spibus_get_cs(dev, &cs); 197*1fcf4de0SIan Lepore cs &= ~SPIBUS_CS_HIGH; /* trim 'cs high' bit */ 198d67fe28bSAdrian Chadd 199d67fe28bSAdrian Chadd sc = device_get_softc(dev); 200d67fe28bSAdrian Chadd sc->sc_dev = dev; 201d67fe28bSAdrian Chadd sc->sc_command_length_max = PAGE_SIZE; 202d67fe28bSAdrian Chadd sc->sc_data_length_max = PAGE_SIZE; 203*1fcf4de0SIan Lepore 204d67fe28bSAdrian Chadd mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); 205*1fcf4de0SIan Lepore 206*1fcf4de0SIan Lepore make_dev_args_init(&mda); 207*1fcf4de0SIan Lepore mda.mda_flags = MAKEDEV_WAITOK; 208*1fcf4de0SIan Lepore mda.mda_devsw = &spigen_cdevsw; 209*1fcf4de0SIan Lepore mda.mda_cr = NULL; 210*1fcf4de0SIan Lepore mda.mda_uid = UID_ROOT; 211*1fcf4de0SIan Lepore mda.mda_gid = GID_OPERATOR; 212*1fcf4de0SIan Lepore mda.mda_mode = 0660; 213*1fcf4de0SIan Lepore mda.mda_unit = unit; 214*1fcf4de0SIan Lepore mda.mda_si_drv1 = dev; 215*1fcf4de0SIan Lepore 216*1fcf4de0SIan Lepore res = make_dev_s(&mda, &(sc->sc_cdev), "spigen%d.%d", 217*1fcf4de0SIan Lepore device_get_unit(device_get_parent(dev)), cs); 218*1fcf4de0SIan Lepore if (res) { 219*1fcf4de0SIan Lepore return res; 220*1fcf4de0SIan Lepore } 221*1fcf4de0SIan Lepore 222*1fcf4de0SIan Lepore #ifdef SPIGEN_LEGACY_CDEVNAME 223*1fcf4de0SIan Lepore res = make_dev_alias_p(0, &sc->sc_adev, sc->sc_cdev, "spigen%d", unit); 224*1fcf4de0SIan Lepore if (res) { 225*1fcf4de0SIan Lepore if (sc->sc_cdev) { 226*1fcf4de0SIan Lepore destroy_dev(sc->sc_cdev); 227*1fcf4de0SIan Lepore sc->sc_cdev = NULL; 228*1fcf4de0SIan Lepore } 229*1fcf4de0SIan Lepore return res; 230*1fcf4de0SIan Lepore } 231*1fcf4de0SIan Lepore #endif 232*1fcf4de0SIan Lepore 233d67fe28bSAdrian Chadd spigen_sysctl_init(sc); 234d67fe28bSAdrian Chadd 235d67fe28bSAdrian Chadd return (0); 236d67fe28bSAdrian Chadd } 237d67fe28bSAdrian Chadd 238d67fe28bSAdrian Chadd static int 23950868fa6SOleksandr Tymoshenko spigen_open(struct cdev *cdev, int oflags, int devtype, struct thread *td) 240d67fe28bSAdrian Chadd { 24150868fa6SOleksandr Tymoshenko int error; 24250868fa6SOleksandr Tymoshenko device_t dev; 24350868fa6SOleksandr Tymoshenko struct spigen_softc *sc; 244d67fe28bSAdrian Chadd 24550868fa6SOleksandr Tymoshenko error = 0; 24650868fa6SOleksandr Tymoshenko dev = cdev->si_drv1; 24750868fa6SOleksandr Tymoshenko sc = device_get_softc(dev); 24850868fa6SOleksandr Tymoshenko 24950868fa6SOleksandr Tymoshenko mtx_lock(&sc->sc_mtx); 25050868fa6SOleksandr Tymoshenko if (sc->sc_flags & SPIGEN_OPEN) 25150868fa6SOleksandr Tymoshenko error = EBUSY; 25250868fa6SOleksandr Tymoshenko else 25350868fa6SOleksandr Tymoshenko sc->sc_flags |= SPIGEN_OPEN; 25450868fa6SOleksandr Tymoshenko mtx_unlock(&sc->sc_mtx); 25550868fa6SOleksandr Tymoshenko 25650868fa6SOleksandr Tymoshenko return (error); 257d67fe28bSAdrian Chadd } 258d67fe28bSAdrian Chadd 259d67fe28bSAdrian Chadd static int 260d67fe28bSAdrian Chadd spigen_transfer(struct cdev *cdev, struct spigen_transfer *st) 261d67fe28bSAdrian Chadd { 262d67fe28bSAdrian Chadd struct spi_command transfer = SPI_COMMAND_INITIALIZER; 263d67fe28bSAdrian Chadd device_t dev = cdev->si_drv1; 264d67fe28bSAdrian Chadd struct spigen_softc *sc = device_get_softc(dev); 265d67fe28bSAdrian Chadd int error = 0; 266d67fe28bSAdrian Chadd 267d67fe28bSAdrian Chadd mtx_lock(&sc->sc_mtx); 2683c43a826SOleksandr Tymoshenko if (st->st_command.iov_len == 0) 269d67fe28bSAdrian Chadd error = EINVAL; 270d67fe28bSAdrian Chadd else if (st->st_command.iov_len > sc->sc_command_length_max || 271d67fe28bSAdrian Chadd st->st_data.iov_len > sc->sc_data_length_max) 272d67fe28bSAdrian Chadd error = ENOMEM; 273d67fe28bSAdrian Chadd mtx_unlock(&sc->sc_mtx); 274d67fe28bSAdrian Chadd if (error) 275d67fe28bSAdrian Chadd return (error); 276d67fe28bSAdrian Chadd 277d67fe28bSAdrian Chadd #if 0 278d67fe28bSAdrian Chadd device_printf(dev, "cmd %p %u data %p %u\n", st->st_command.iov_base, 279d67fe28bSAdrian Chadd st->st_command.iov_len, st->st_data.iov_base, st->st_data.iov_len); 280d67fe28bSAdrian Chadd #endif 281d67fe28bSAdrian Chadd transfer.tx_cmd = transfer.rx_cmd = malloc(st->st_command.iov_len, 282d67fe28bSAdrian Chadd M_DEVBUF, M_WAITOK); 2833c43a826SOleksandr Tymoshenko if (st->st_data.iov_len > 0) { 284d67fe28bSAdrian Chadd transfer.tx_data = transfer.rx_data = malloc(st->st_data.iov_len, 285d67fe28bSAdrian Chadd M_DEVBUF, M_WAITOK); 2863c43a826SOleksandr Tymoshenko } 2873c43a826SOleksandr Tymoshenko else 2883c43a826SOleksandr Tymoshenko transfer.tx_data = transfer.rx_data = NULL; 289d67fe28bSAdrian Chadd 290d67fe28bSAdrian Chadd error = copyin(st->st_command.iov_base, transfer.tx_cmd, 291d67fe28bSAdrian Chadd transfer.tx_cmd_sz = transfer.rx_cmd_sz = st->st_command.iov_len); 2923c43a826SOleksandr Tymoshenko if ((error == 0) && (st->st_data.iov_len > 0)) 293d67fe28bSAdrian Chadd error = copyin(st->st_data.iov_base, transfer.tx_data, 294d67fe28bSAdrian Chadd transfer.tx_data_sz = transfer.rx_data_sz = 295d67fe28bSAdrian Chadd st->st_data.iov_len); 296d67fe28bSAdrian Chadd if (error == 0) 297d67fe28bSAdrian Chadd error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer); 298d67fe28bSAdrian Chadd if (error == 0) { 299d67fe28bSAdrian Chadd error = copyout(transfer.rx_cmd, st->st_command.iov_base, 300d67fe28bSAdrian Chadd transfer.rx_cmd_sz); 3013c43a826SOleksandr Tymoshenko if ((error == 0) && (st->st_data.iov_len > 0)) 302d67fe28bSAdrian Chadd error = copyout(transfer.rx_data, st->st_data.iov_base, 303d67fe28bSAdrian Chadd transfer.rx_data_sz); 304d67fe28bSAdrian Chadd } 305d67fe28bSAdrian Chadd 306d67fe28bSAdrian Chadd free(transfer.tx_cmd, M_DEVBUF); 307d67fe28bSAdrian Chadd free(transfer.tx_data, M_DEVBUF); 308d67fe28bSAdrian Chadd return (error); 309d67fe28bSAdrian Chadd } 310d67fe28bSAdrian Chadd 311d67fe28bSAdrian Chadd static int 312d67fe28bSAdrian Chadd spigen_transfer_mmapped(struct cdev *cdev, struct spigen_transfer_mmapped *stm) 313d67fe28bSAdrian Chadd { 314d67fe28bSAdrian Chadd struct spi_command transfer = SPI_COMMAND_INITIALIZER; 315d67fe28bSAdrian Chadd device_t dev = cdev->si_drv1; 316d67fe28bSAdrian Chadd struct spigen_softc *sc = device_get_softc(dev); 317d67fe28bSAdrian Chadd int error = 0; 318d67fe28bSAdrian Chadd 319d67fe28bSAdrian Chadd mtx_lock(&sc->sc_mtx); 32050868fa6SOleksandr Tymoshenko if (sc->sc_flags & SPIGEN_MMAP_BUSY) 321d67fe28bSAdrian Chadd error = EBUSY; 322d67fe28bSAdrian Chadd else if (stm->stm_command_length > sc->sc_command_length_max || 323d67fe28bSAdrian Chadd stm->stm_data_length > sc->sc_data_length_max) 324d67fe28bSAdrian Chadd error = E2BIG; 325d67fe28bSAdrian Chadd else if (sc->sc_mmap_buffer == NULL) 326d67fe28bSAdrian Chadd error = EINVAL; 327d67fe28bSAdrian Chadd else if (sc->sc_mmap_buffer_size < 328d67fe28bSAdrian Chadd stm->stm_command_length + stm->stm_data_length) 329d67fe28bSAdrian Chadd error = ENOMEM; 330d67fe28bSAdrian Chadd if (error == 0) 33150868fa6SOleksandr Tymoshenko sc->sc_flags |= SPIGEN_MMAP_BUSY; 332d67fe28bSAdrian Chadd mtx_unlock(&sc->sc_mtx); 333d67fe28bSAdrian Chadd if (error) 334d67fe28bSAdrian Chadd return (error); 335d67fe28bSAdrian Chadd 336d67fe28bSAdrian Chadd transfer.tx_cmd = transfer.rx_cmd = (void *)sc->sc_mmap_kvaddr; 337d67fe28bSAdrian Chadd transfer.tx_cmd_sz = transfer.rx_cmd_sz = stm->stm_command_length; 338d67fe28bSAdrian Chadd transfer.tx_data = transfer.rx_data = 339d67fe28bSAdrian Chadd (void *)(sc->sc_mmap_kvaddr + stm->stm_command_length); 340d67fe28bSAdrian Chadd transfer.tx_data_sz = transfer.rx_data_sz = stm->stm_data_length; 341d67fe28bSAdrian Chadd error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer); 342d67fe28bSAdrian Chadd 343d67fe28bSAdrian Chadd mtx_lock(&sc->sc_mtx); 34450868fa6SOleksandr Tymoshenko KASSERT((sc->sc_flags & SPIGEN_MMAP_BUSY), ("mmap no longer marked busy")); 34550868fa6SOleksandr Tymoshenko sc->sc_flags &= ~(SPIGEN_MMAP_BUSY); 346d67fe28bSAdrian Chadd mtx_unlock(&sc->sc_mtx); 347d67fe28bSAdrian Chadd return (error); 348d67fe28bSAdrian Chadd } 349d67fe28bSAdrian Chadd 350d67fe28bSAdrian Chadd static int 351d67fe28bSAdrian Chadd spigen_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag, 352d67fe28bSAdrian Chadd struct thread *td) 353d67fe28bSAdrian Chadd { 354d67fe28bSAdrian Chadd device_t dev = cdev->si_drv1; 355d67fe28bSAdrian Chadd int error; 356d67fe28bSAdrian Chadd 357d67fe28bSAdrian Chadd switch (cmd) { 358d67fe28bSAdrian Chadd case SPIGENIOC_TRANSFER: 359d67fe28bSAdrian Chadd error = spigen_transfer(cdev, (struct spigen_transfer *)data); 360d67fe28bSAdrian Chadd break; 361d67fe28bSAdrian Chadd case SPIGENIOC_TRANSFER_MMAPPED: 362d67fe28bSAdrian Chadd error = spigen_transfer_mmapped(cdev, (struct spigen_transfer_mmapped *)data); 363d67fe28bSAdrian Chadd break; 364d67fe28bSAdrian Chadd case SPIGENIOC_GET_CLOCK_SPEED: 365197d784bSIan Lepore error = spibus_get_clock(dev, (uint32_t *)data); 366d67fe28bSAdrian Chadd break; 367d67fe28bSAdrian Chadd case SPIGENIOC_SET_CLOCK_SPEED: 368a0e911e0SIan Lepore error = spibus_set_clock(dev, *(uint32_t *)data); 369a0e911e0SIan Lepore break; 370a0e911e0SIan Lepore case SPIGENIOC_GET_SPI_MODE: 371197d784bSIan Lepore error = spibus_get_mode(dev, (uint32_t *)data); 372a0e911e0SIan Lepore break; 373a0e911e0SIan Lepore case SPIGENIOC_SET_SPI_MODE: 374a0e911e0SIan Lepore error = spibus_set_mode(dev, *(uint32_t *)data); 375d67fe28bSAdrian Chadd break; 376d67fe28bSAdrian Chadd default: 377a0e911e0SIan Lepore error = ENOTTY; 378a0e911e0SIan Lepore break; 379d67fe28bSAdrian Chadd } 380d67fe28bSAdrian Chadd return (error); 381d67fe28bSAdrian Chadd } 382d67fe28bSAdrian Chadd 383d67fe28bSAdrian Chadd static int 384d67fe28bSAdrian Chadd spigen_mmap_single(struct cdev *cdev, vm_ooffset_t *offset, 385d67fe28bSAdrian Chadd vm_size_t size, struct vm_object **object, int nprot) 386d67fe28bSAdrian Chadd { 387d67fe28bSAdrian Chadd device_t dev = cdev->si_drv1; 388d67fe28bSAdrian Chadd struct spigen_softc *sc = device_get_softc(dev); 389d67fe28bSAdrian Chadd vm_page_t *m; 390d67fe28bSAdrian Chadd size_t n, pages; 391d67fe28bSAdrian Chadd 392d67fe28bSAdrian Chadd if (size == 0 || 393d67fe28bSAdrian Chadd (nprot & (PROT_EXEC | PROT_READ | PROT_WRITE)) 394d67fe28bSAdrian Chadd != (PROT_READ | PROT_WRITE)) 395d67fe28bSAdrian Chadd return (EINVAL); 396d67fe28bSAdrian Chadd size = roundup2(size, PAGE_SIZE); 397d67fe28bSAdrian Chadd pages = size / PAGE_SIZE; 398d67fe28bSAdrian Chadd 399d67fe28bSAdrian Chadd mtx_lock(&sc->sc_mtx); 400d67fe28bSAdrian Chadd if (sc->sc_mmap_buffer != NULL) { 401d67fe28bSAdrian Chadd mtx_unlock(&sc->sc_mtx); 402d67fe28bSAdrian Chadd return (EBUSY); 403d67fe28bSAdrian Chadd } else if (size > sc->sc_command_length_max + sc->sc_data_length_max) { 404d67fe28bSAdrian Chadd mtx_unlock(&sc->sc_mtx); 405d67fe28bSAdrian Chadd return (E2BIG); 406d67fe28bSAdrian Chadd } 407d67fe28bSAdrian Chadd sc->sc_mmap_buffer_size = size; 408d67fe28bSAdrian Chadd *offset = 0; 409d67fe28bSAdrian Chadd sc->sc_mmap_buffer = *object = vm_pager_allocate(OBJT_PHYS, 0, size, 410d67fe28bSAdrian Chadd nprot, *offset, curthread->td_ucred); 411d67fe28bSAdrian Chadd m = malloc(sizeof(*m) * pages, M_TEMP, M_WAITOK); 412d67fe28bSAdrian Chadd VM_OBJECT_WLOCK(*object); 413d67fe28bSAdrian Chadd vm_object_reference_locked(*object); // kernel and userland both 414d67fe28bSAdrian Chadd for (n = 0; n < pages; n++) { 415d67fe28bSAdrian Chadd m[n] = vm_page_grab(*object, n, 416d67fe28bSAdrian Chadd VM_ALLOC_NOBUSY | VM_ALLOC_ZERO | VM_ALLOC_WIRED); 417d67fe28bSAdrian Chadd m[n]->valid = VM_PAGE_BITS_ALL; 418d67fe28bSAdrian Chadd } 419d67fe28bSAdrian Chadd VM_OBJECT_WUNLOCK(*object); 420d67fe28bSAdrian Chadd sc->sc_mmap_kvaddr = kva_alloc(size); 421d67fe28bSAdrian Chadd pmap_qenter(sc->sc_mmap_kvaddr, m, pages); 422d67fe28bSAdrian Chadd free(m, M_TEMP); 423d67fe28bSAdrian Chadd mtx_unlock(&sc->sc_mtx); 424d67fe28bSAdrian Chadd 425d67fe28bSAdrian Chadd if (*object == NULL) 426d67fe28bSAdrian Chadd return (EINVAL); 427d67fe28bSAdrian Chadd return (0); 428d67fe28bSAdrian Chadd } 429d67fe28bSAdrian Chadd 430d67fe28bSAdrian Chadd static int 431d67fe28bSAdrian Chadd spigen_close(struct cdev *cdev, int fflag, int devtype, struct thread *td) 432d67fe28bSAdrian Chadd { 433d67fe28bSAdrian Chadd device_t dev = cdev->si_drv1; 434d67fe28bSAdrian Chadd struct spigen_softc *sc = device_get_softc(dev); 435d67fe28bSAdrian Chadd 436d67fe28bSAdrian Chadd mtx_lock(&sc->sc_mtx); 437d67fe28bSAdrian Chadd if (sc->sc_mmap_buffer != NULL) { 438d67fe28bSAdrian Chadd pmap_qremove(sc->sc_mmap_kvaddr, 439d67fe28bSAdrian Chadd sc->sc_mmap_buffer_size / PAGE_SIZE); 440d67fe28bSAdrian Chadd kva_free(sc->sc_mmap_kvaddr, sc->sc_mmap_buffer_size); 441d67fe28bSAdrian Chadd sc->sc_mmap_kvaddr = 0; 442d67fe28bSAdrian Chadd vm_object_deallocate(sc->sc_mmap_buffer); 443d67fe28bSAdrian Chadd sc->sc_mmap_buffer = NULL; 444d67fe28bSAdrian Chadd sc->sc_mmap_buffer_size = 0; 445d67fe28bSAdrian Chadd } 44650868fa6SOleksandr Tymoshenko sc->sc_flags &= ~(SPIGEN_OPEN); 447d67fe28bSAdrian Chadd mtx_unlock(&sc->sc_mtx); 448d67fe28bSAdrian Chadd return (0); 449d67fe28bSAdrian Chadd } 450d67fe28bSAdrian Chadd 451d67fe28bSAdrian Chadd static int 452d67fe28bSAdrian Chadd spigen_detach(device_t dev) 453d67fe28bSAdrian Chadd { 45450868fa6SOleksandr Tymoshenko struct spigen_softc *sc; 455d67fe28bSAdrian Chadd 45650868fa6SOleksandr Tymoshenko sc = device_get_softc(dev); 45750868fa6SOleksandr Tymoshenko 45850868fa6SOleksandr Tymoshenko mtx_lock(&sc->sc_mtx); 45950868fa6SOleksandr Tymoshenko if (sc->sc_flags & SPIGEN_OPEN) { 46050868fa6SOleksandr Tymoshenko mtx_unlock(&sc->sc_mtx); 46150868fa6SOleksandr Tymoshenko return (EBUSY); 46250868fa6SOleksandr Tymoshenko } 46350868fa6SOleksandr Tymoshenko mtx_unlock(&sc->sc_mtx); 46450868fa6SOleksandr Tymoshenko 46550868fa6SOleksandr Tymoshenko mtx_destroy(&sc->sc_mtx); 46650868fa6SOleksandr Tymoshenko 467*1fcf4de0SIan Lepore #ifdef SPIGEN_LEGACY_CDEVNAME 468*1fcf4de0SIan Lepore if (sc->sc_adev) 469*1fcf4de0SIan Lepore destroy_dev(sc->sc_adev); 470*1fcf4de0SIan Lepore #endif 471*1fcf4de0SIan Lepore 47250868fa6SOleksandr Tymoshenko if (sc->sc_cdev) 47350868fa6SOleksandr Tymoshenko destroy_dev(sc->sc_cdev); 47450868fa6SOleksandr Tymoshenko 47550868fa6SOleksandr Tymoshenko return (0); 476d67fe28bSAdrian Chadd } 477d67fe28bSAdrian Chadd 478d67fe28bSAdrian Chadd static devclass_t spigen_devclass; 479d67fe28bSAdrian Chadd 480d67fe28bSAdrian Chadd static device_method_t spigen_methods[] = { 481d67fe28bSAdrian Chadd /* Device interface */ 482d67fe28bSAdrian Chadd DEVMETHOD(device_probe, spigen_probe), 483d67fe28bSAdrian Chadd DEVMETHOD(device_attach, spigen_attach), 484d67fe28bSAdrian Chadd DEVMETHOD(device_detach, spigen_detach), 485d67fe28bSAdrian Chadd 486d67fe28bSAdrian Chadd { 0, 0 } 487d67fe28bSAdrian Chadd }; 488d67fe28bSAdrian Chadd 489d67fe28bSAdrian Chadd static driver_t spigen_driver = { 490d67fe28bSAdrian Chadd "spigen", 491d67fe28bSAdrian Chadd spigen_methods, 492d67fe28bSAdrian Chadd sizeof(struct spigen_softc), 493d67fe28bSAdrian Chadd }; 494d67fe28bSAdrian Chadd 495d67fe28bSAdrian Chadd DRIVER_MODULE(spigen, spibus, spigen_driver, spigen_devclass, 0, 0); 496cdfebb9cSIan Lepore MODULE_DEPEND(spigen, spibus, 1, 1, 1); 497