Skip site navigation (1)Skip section navigation (2)
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>