Date: Sun, 26 Apr 2015 21:33:47 +0100 From: Tom Jones <jones@sdf.org> To: freebsd-embedded@freebsd.org Subject: SPI User Space Interface Message-ID: <20150426203344.GA27490@gmail.com>
next in thread | raw e-mail | index | archive | help
--6c2NcOVqGQ03X4Wi Content-Type: text/plain; charset=us-ascii Content-Disposition: inline This atteched patch adds user space access to spi drivers. Access is possible via read, write and an ioctl call. I have tested this patch on a raspberry-pi B against an avr running a simple program and a small monochrome oled screen based on the ssd1306 display driver. The ioctl requires the use of two new structs. The spi_rdwr_data struct takes a number of spi_ioc_transfer structs, this is modeled on the iic driver. The spi_ioc_transfer struct is compatible with the linux spidev implementation. There is no support for ioctls to set spi device settings are there is in linux, this is due to the limited driver interface in spibus. Only the buffer and length options are used from the spi_ioc_transfer struct, this is from the lack of driver interface on spibus. struct spi_ioc_transfer { void *tx_buf; void *rx_buf; uint32_t len; uint32_t speed_hz; uint16_t delay_usecs; uint8_t bits_per_word; uint8_t cs_change; uint32_t pad; }; struct spi_rdwr_data { struct spi_ioc_transfer *msgs; uint32_t nmsgs; }; #define SPIRDWR _IOW('S', 1, struct spi_rdwr_data) I have only managed to test this code on one platform and against a limited number of spi devices. I think it needs more testing against real hardware, I don't have any lying around. -- Tom @adventureloop adventurist.me :wq --6c2NcOVqGQ03X4Wi Content-Type: text/x-diff; charset=us-ascii Content-Disposition: inline; filename="spidev.patch" Index: sys/conf/files =================================================================== --- sys/conf/files (revision 281862) +++ sys/conf/files (working copy) @@ -2295,6 +2295,7 @@ dev/spibus/spibus.c optional spibus \ dependency "spibus_if.h" dev/spibus/spibus_if.m optional spibus +dev/spibus/spidev.c optional spibus dev/ste/if_ste.c optional ste pci dev/stg/tmc18c30.c optional stg dev/stg/tmc18c30_isa.c optional stg isa Index: sys/dev/spibus/spi.h =================================================================== --- sys/dev/spibus/spi.h (revision 281862) +++ sys/dev/spibus/spi.h (working copy) @@ -1,5 +1,6 @@ /*- * Copyright (c) 2006 M. Warner Losh + * Copyright (c) 2014 Tom Jones <jones@sdf.org> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,3 +39,23 @@ }; #define SPI_CHIP_SELECT_HIGH 0x1 /* Chip select high (else low) */ + +struct spi_ioc_transfer { + void *tx_buf; + void *rx_buf; + + uint32_t len; + uint32_t speed_hz; + + uint16_t delay_usecs; + uint8_t bits_per_word; + uint8_t cs_change; + uint32_t pad; +}; + +struct spi_rdwr_data { + struct spi_ioc_transfer *msgs; + uint32_t nmsgs; +}; + +#define SPIRDWR _IOW('S', 1, struct spi_rdwr_data) Index: sys/dev/spibus/spidev.c =================================================================== --- sys/dev/spibus/spidev.c (revision 0) +++ sys/dev/spibus/spidev.c (working copy) @@ -0,0 +1,310 @@ +/*- + * Copyright (c) 2015 Tom Jones <jones@sdf.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/errno.h> +#include <sys/param.h> +#include <sys/conf.h> +#include <sys/fcntl.h> +#include <sys/lock.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/sx.h> +#include <sys/systm.h> +#include <sys/uio.h> +#include <sys/ioccom.h> +#include <sys/bus.h> +#include <sys/rman.h> +#include <machine/bus.h> +#include <machine/resource.h> + +#include <dev/spibus/spibusvar.h> +#include <dev/spibus/spi.h> + +#include "spibus_if.h" + +#define BUFSIZE 1024 + +static MALLOC_DEFINE(M_SPIDEV, "spidev", "SPI data"); + +struct spidev_softc { + + device_t sc_dev; + device_t spi_dev; + int sc_count; /* >0 if device opened */ + + char sc_buffer[BUFSIZE]; /* output buffer */ + char sc_inbuf[BUFSIZE]; /* input buffer */ + + struct cdev *sc_devnode; + struct sx sc_lock; +}; + +static int spidev_probe(device_t); +static int spidev_attach(device_t); +static int spidev_detach(device_t); +static void spidev_identify(driver_t *driver, device_t parent); + +static devclass_t spidev_devclass; + +static device_method_t spidev_methods[] = { + /* device interface */ + DEVMETHOD(device_identify, spidev_identify), + DEVMETHOD(device_probe, spidev_probe), + DEVMETHOD(device_attach, spidev_attach), + DEVMETHOD(device_detach, spidev_detach), + + { 0, 0 } +}; + +static driver_t spidev_driver = { + "spidev", + spidev_methods, + sizeof(struct spidev_softc), +}; + +static d_open_t spidevopen; +static d_close_t spidevclose; +static d_write_t spidevwrite; +static d_read_t spidevread; +static d_ioctl_t spidevioctl; + +static struct cdevsw spidev_cdevsw = { + .d_version = D_VERSION, + .d_flags = D_TRACKCLOSE, + .d_open = spidevopen, + .d_close = spidevclose, + .d_read = spidevread, + .d_write = spidevwrite, + .d_ioctl = spidevioctl, + .d_name = "spidev", +}; + +static void +spidev_identify(driver_t *driver, device_t parent) +{ + if (device_find_child(parent, "spidev", -1) == NULL) + BUS_ADD_CHILD(parent, 0, "spidev", -1); + else + uprintf("SPIDEV IDENTIFY nothing found\n"); +} + +static int +spidev_probe(device_t dev) +{ + device_set_desc(dev, "SPI generic I/O"); + return (0); +} + +static int +spidev_attach(device_t dev) +{ + struct spidev_softc *sc = (struct spidev_softc *)device_get_softc(dev); + sc->sc_dev = dev; + + device_t spibus = device_get_parent(dev); + sc->spi_dev = device_get_parent(spibus); + + if(sc->spi_dev == NULL) { + device_printf(dev, "failed to attach to spi device\n"); + return (ENXIO); + } + sc->sc_devnode = make_dev(&spidev_cdevsw, device_get_unit(dev), + UID_ROOT, GID_WHEEL, + 0600, "spi%d", device_get_unit(dev)); + + if (sc->sc_devnode == NULL) { + device_printf(dev, "failed to create character device\n"); + return (ENXIO); + } + sc->sc_devnode->si_drv1 = sc; + + return (0); +} + +static int +spidev_detach(device_t dev) +{ + struct spidev_softc *sc = (struct spidev_softc *)device_get_softc(dev); + + if (sc->sc_devnode) + destroy_dev(sc->sc_devnode); + + return (0); +} + +static int +spidevopen(struct cdev *dev, int flags, int fmt, struct thread *td) +{ + struct spidev_softc *sc = dev->si_drv1; + + if(sc->sc_count > 0) + return (EBUSY); + sc->sc_count++; + + return (0); +} + +static int +spidevclose(struct cdev *dev, int flags, int fmt, struct thread *td) +{ + struct spidev_softc *sc = dev->si_drv1; + + sc->sc_count--; + if(sc->sc_count < 0) + panic("%s: spidev_count < 0!",__func__); + + return (0); +} + +static int +spidevwrite(struct cdev *dev, struct uio * uio, int ioflag) +{ + struct spidev_softc *sc = dev->si_drv1; + device_t spi_hw = sc->spi_dev; + int error; + + struct spi_command cmd; + size_t size = MIN(uio->uio_resid,BUFSIZE); + + cmd.rx_cmd_sz = cmd.tx_cmd_sz = 0; + + cmd.tx_data = sc->sc_inbuf; + cmd.rx_data = sc->sc_buffer; + cmd.rx_data_sz = cmd.tx_data_sz = size; + + memset(sc->sc_buffer, 0, size); + + error = uiomove(sc->sc_inbuf, size, uio); + if(error == 0) + error = SPIBUS_TRANSFER(spi_hw, sc->sc_dev, &cmd); + + return (error); +} + +static int +spidevread(struct cdev *dev, struct uio * uio, int ioflag) +{ + struct spidev_softc *sc = dev->si_drv1; + struct spi_command cmd; + device_t spi_hw = sc->spi_dev; + int error; + + size_t size = MIN(uio->uio_resid,BUFSIZE); + + cmd.rx_cmd_sz = cmd.tx_cmd_sz = 0; + + cmd.tx_data = sc->sc_inbuf; + cmd.rx_data = sc->sc_buffer; + cmd.rx_data_sz = cmd.tx_data_sz = size; + + error = SPIBUS_TRANSFER(spi_hw,sc->sc_dev, &cmd); + + if(error == 0) + error = uiomove(sc->sc_buffer, size, uio); + + return (error); +} + +static int +spidevioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *td) +{ + struct spidev_softc *sc = dev->si_drv1; + struct spi_command spicmd; + struct spi_ioc_transfer *s, *operations = NULL; + struct spi_rdwr_data *spi_data = (struct spi_rdwr_data *)data; + device_t spi_hw = sc->spi_dev; + int error; + + switch (cmd) { + case SPIRDWR: + if(spi_data->nmsgs <= 0) + return EINVAL; + + operations = malloc(spi_data->nmsgs * sizeof(struct spi_ioc_transfer), + M_SPIDEV, + M_WAITOK); + + error = copyin(spi_data->msgs, operations, + spi_data->nmsgs * sizeof(struct spi_ioc_transfer)); + + if(error) + goto errout; + + for(int i = 0; i < spi_data->nmsgs; i++) { + s = &operations[i]; + + spicmd.tx_cmd_sz = spicmd.rx_cmd_sz = 0; + + spicmd.tx_data_sz = s->len; + spicmd.rx_data_sz = s->len; + + spicmd.tx_data = malloc(s->len, M_SPIDEV, M_WAITOK); + spicmd.rx_data = malloc(s->len, M_SPIDEV, M_WAITOK); + + error = copyin(s->tx_buf, spicmd.tx_data, spicmd.tx_data_sz); + + if(error) + goto errout; + + error = SPIBUS_TRANSFER(spi_hw,sc->sc_dev, &spicmd); + + if(error) + goto errout; + + error = copyout(spicmd.rx_data, s->rx_buf, spicmd.rx_data_sz); + + if(error) + goto errout; + + if(spicmd.tx_data) { + free(spicmd.tx_data, M_SPIDEV); + spicmd.tx_data = NULL; + } + if(spicmd.rx_data) { + free(spicmd.rx_data, M_SPIDEV); + spicmd.rx_data = NULL; + } + } + + break; + default: + error = ENOTTY; + } + + errout: + if(operations != NULL) + free(operations, M_SPIDEV); + if(spicmd.tx_data) + free(spicmd.tx_data, M_SPIDEV); + if(spicmd.rx_data) + free(spicmd.rx_data, M_SPIDEV); + return (error); +} + +DRIVER_MODULE(spidev, spibus, spidev_driver, spidev_devclass, 0, 0); +MODULE_VERSION(spidev, 1); Property changes on: sys/dev/spibus/spidev.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property --6c2NcOVqGQ03X4Wi--
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20150426203344.GA27490>