summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--share/man/man4/spigen.4206
-rw-r--r--sys/dev/spibus/ofw_spibus.c6
-rw-r--r--sys/dev/spibus/spibus.c46
-rw-r--r--sys/dev/spibus/spibusvar.h8
-rw-r--r--sys/dev/spibus/spigen.c64
-rw-r--r--sys/modules/spigen/Makefile12
-rw-r--r--sys/sys/spigenio.h2
7 files changed, 299 insertions, 45 deletions
diff --git a/share/man/man4/spigen.4 b/share/man/man4/spigen.4
new file mode 100644
index 0000000..f432fb7
--- /dev/null
+++ b/share/man/man4/spigen.4
@@ -0,0 +1,206 @@
+.\"
+.\" Copyright (c) 2018 Ian Lepore <ian@freebsd.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 ``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 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd April 7, 2018
+.Dt SPIGEN 4
+.Os
+.Sh NAME
+.Nm spigen
+.Nd SPI generic I/O device driver
+.Sh SYNOPSIS
+To compile this driver into the kernel,
+place the following lines in your
+kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device spi"
+.Cd "device spibus"
+.Cd "device spigen"
+.Ed
+.Pp
+Alternatively, to load the driver as a
+module at boot time, place the following line in
+.Xr loader.conf 5 :
+.Bd -literal -offset indent
+spigen_load="YES"
+.Ed
+.Sh DESCRIPTION
+The
+.Nm
+driver provides direct access to a slave device on the SPI bus.
+Each instance of a
+.Nm
+device is associated with a single chip-select
+line on the bus, and all I/O performed through that instance is done
+with that chip-select line asserted.
+.Pp
+SPI data transfers are inherently bi-directional; there are not separate
+read and write operations.
+When commands and data are sent to a device, data also comes back from
+the device, although in some cases the data may not be useful (or even
+documented or predictable for some devices).
+Likewise on a read operation, whatever data is in the buffer at the start
+of the operation is sent to (and typically ignored by) the device, with each
+outgoing byte then replaced in the buffer by the corresponding incoming byte.
+Thus, all buffers passed to the transfer functions are both input and
+output buffers.
+.Pp
+The
+.Nm
+driver provides access to the SPI slave device with the following
+.Xr ioctl 2
+calls, defined in
+.In sys/spigenio.h :
+.Bl -tag -width indent
+.It Dv SPIGENIOC_TRANSFER Pq Vt "struct spigen_transfer"
+Transfer a command and optional associated data to/from the device,
+using the buffers described by the st_command and st_data fields in the
+.Vt spigen_transfer .
+Set
+.Vt st_data.iov_len
+to zero if there is no data associated with the command.
+.Bd -literal
+struct spigen_transfer {
+ struct iovec st_command;
+ struct iovec st_data;
+};
+.Ed
+.It Dv SPIGENIOC_TRANSFER_MMAPPED Pq Vt "spigen_transfer_mmapped"
+Transfer a command and optional associated data to/from the device.
+The buffers for the transfer are a previously-mmap'd region.
+The length of the command and data within that region are described by the
+.Vt stm_command_length
+and
+.Vt stm_data_length
+fields of
+.Vt spigen_transfer_mmapped .
+If
+.Vt stm_data_length
+is non-zero, the data appears in the memory region immediately
+following the command (that is, at offset
+.Vt stm_command_length
+from the start of the mapped region).
+.Bd -literal
+struct spigen_transfer_mmapped {
+ size_t stm_command_length;
+ size_t stm_data_length;
+};
+.Ed
+.It Dv SPIGENIOC_GET_CLOCK_SPEED Pq Vt uint32_t
+Get the maximum clock speed (bus frequency in Hertz) to be used
+when communicating with this slave device.
+.It Dv SPIGENIOC_SET_CLOCK_SPEED Pq Vt uint32_t
+Set the maximum clock speed (bus frequency in Hertz) to be used
+when communicating with this slave device.
+The setting remains in effect for subsequent transfers; it
+is not necessary to reset this before each transfer.
+The actual bus frequency may be lower due to hardware limitiations
+of the SPI bus controller device.
+.It Dv SPIGENIOC_GET_SPI_MODE Pq Vt uint32_t
+Get the SPI mode (clock polarity and phase) to be used
+when communicating with this device.
+.It Dv SPIGENIOC_SET_SPI_MODE Pq Vt uint32_t
+Set the SPI mode (clock polarity and phase) to be used
+when communicating with this device.
+The setting remains in effect for subsequent transfers; it
+is not necessary to reset this before each transfer.
+.El
+.Sh HINTS CONFIGURATION
+On a
+.Xr device.hints 5
+based system, such as
+.Li MIPS ,
+these values are configurable for
+.Nm :
+.Bl -tag -width indent
+.It Va hint.spigen.%d.at
+The spibus the
+.Nm
+instance is attached to.
+.It Va hint.spigen.%d.clock
+The maximum bus frequency to use when communicating with this device.
+Actual bus speed may be lower, depending on the capabilities of the SPI
+bus controller hardware.
+.It Va hint.spigen.%d.cs
+The chip-select number to assert when performing I/O for this device.
+Set the high bit (1 << 31) to invert the logic level of the chip select line.
+.It Va hint.spigen.%d.mode
+The SPI mode (0-3) to use when communicating with this device.
+.El
+.Sh FDT CONFIGURATION
+On an
+.Xr fdt 4
+based system, the spigen device is defined as a slave device subnode
+of the SPI bus controller node.
+All properties documented in the
+.Va spibus.txt
+bindings document can be used with the
+.Nm
+device.
+The most commonly-used ones are documented below.
+.Pp
+The following properties are required in the
+.Nm
+device subnode:
+.Bl -tag -width indent
+.It Va compatible
+Must be the string "freebsd,spigen".
+.It Va reg
+Chip select address of device.
+.It Va spi-max-frequency
+The maximum bus frequency to use when communicating with this slave device.
+Actual bus speed may be lower, depending on the capabilities of the SPI
+bus controller hardware.
+.El
+.Pp
+The following properties are optional for the
+.Nm
+device subnode:
+.Bl -tag -width indent
+.It Va spi-cpha
+Empty property indicating the slave device requires shifted clock
+phase (CPHA) mode.
+.It Va spi-cpol
+Empty property indicating the slave device requires inverse clock
+polarity (CPOL) mode.
+.It Va spi-cs-high
+Empty property indicating the slave device requires chip select active high.
+.El
+.Sh FILES
+.Bl -tag -width -compact
+.It Pa /dev/spigen*
+.El
+.Sh SEE ALSO
+.Xr fdt 4 ,
+.Xr device.hints 5
+.Sh HISTORY
+The
+.Nm
+driver
+appeared in
+.Fx 11.0 .
+FDT support appeared in
+.Fx 11.2 .
diff --git a/sys/dev/spibus/ofw_spibus.c b/sys/dev/spibus/ofw_spibus.c
index c89db2c..1da3dae 100644
--- a/sys/dev/spibus/ofw_spibus.c
+++ b/sys/dev/spibus/ofw_spibus.c
@@ -69,7 +69,7 @@ ofw_spibus_probe(device_t dev)
return (ENXIO);
device_set_desc(dev, "OFW SPI bus");
- return (0);
+ return (BUS_PROBE_DEFAULT);
}
static int
@@ -105,6 +105,10 @@ ofw_spibus_attach(device_t dev)
/*
* Get the maximum clock frequency for device, zero means
* use the default bus speed.
+ *
+ * XXX Note that the current (2018-04-07) dts bindings say that
+ * spi-max-frequency is a required property (but says nothing of
+ * how to interpret a value of zero).
*/
if (OF_getencprop(child, "spi-max-frequency", &clock,
sizeof(clock)) == -1)
diff --git a/sys/dev/spibus/spibus.c b/sys/dev/spibus/spibus.c
index 3c6a631..ec3739b 100644
--- a/sys/dev/spibus/spibus.c
+++ b/sys/dev/spibus/spibus.c
@@ -49,8 +49,9 @@ __FBSDID("$FreeBSD$");
static int
spibus_probe(device_t dev)
{
- device_set_desc(dev, "spibus bus");
- return (BUS_PROBE_GENERIC);
+
+ device_set_desc(dev, "SPI bus");
+ return (BUS_PROBE_DEFAULT);
}
static int
@@ -70,16 +71,11 @@ spibus_attach(device_t dev)
static int
spibus_detach(device_t dev)
{
- int err, ndevs, i;
- device_t *devlist;
+ int err;
if ((err = bus_generic_detach(dev)) != 0)
return (err);
- if ((err = device_get_children(dev, &devlist, &ndevs)) != 0)
- return (err);
- for (i = 0; i < ndevs; i++)
- device_delete_child(dev, devlist[i]);
- free(devlist, M_TEMP);
+ device_delete_children(dev);
return (0);
}
@@ -160,6 +156,37 @@ spibus_read_ivar(device_t bus, device_t child, int which, uintptr_t *result)
return (0);
}
+static int
+spibus_write_ivar(device_t bus, device_t child, int which, uintptr_t value)
+{
+ struct spibus_ivar *devi = SPIBUS_IVAR(child);
+
+ if (devi == NULL || device_get_parent(child) != bus)
+ return (EDOOFUS);
+
+ switch (which) {
+ case SPIBUS_IVAR_CLOCK:
+ /* Any non-zero value is allowed for max clock frequency. */
+ if (value == 0)
+ return (EINVAL);
+ devi->clock = (uint32_t)value;
+ break;
+ case SPIBUS_IVAR_CS:
+ /* Chip select cannot be changed. */
+ return (EINVAL);
+ case SPIBUS_IVAR_MODE:
+ /* Valid SPI modes are 0-3. */
+ if (value > 3)
+ return (EINVAL);
+ devi->mode = (uint32_t)value;
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ return (0);
+}
+
static device_t
spibus_add_child(device_t dev, u_int order, const char *name, int unit)
{
@@ -211,6 +238,7 @@ static device_method_t spibus_methods[] = {
DEVMETHOD(bus_print_child, spibus_print_child),
DEVMETHOD(bus_probe_nomatch, spibus_probe_nomatch),
DEVMETHOD(bus_read_ivar, spibus_read_ivar),
+ DEVMETHOD(bus_write_ivar, spibus_write_ivar),
DEVMETHOD(bus_child_pnpinfo_str, spibus_child_pnpinfo_str),
DEVMETHOD(bus_child_location_str, spibus_child_location_str),
DEVMETHOD(bus_hinted_child, spibus_hinted_child),
diff --git a/sys/dev/spibus/spibusvar.h b/sys/dev/spibus/spibusvar.h
index 15f9049..ffa016a 100644
--- a/sys/dev/spibus/spibusvar.h
+++ b/sys/dev/spibus/spibusvar.h
@@ -60,8 +60,14 @@ spibus_get_ ## A(device_t dev, T *t) \
{ \
return BUS_READ_IVAR(device_get_parent(dev), dev, \
SPIBUS_IVAR_ ## B, (uintptr_t *) t); \
+} \
+static inline int \
+spibus_set_ ## A(device_t dev, T t) \
+{ \
+ return BUS_WRITE_IVAR(device_get_parent(dev), dev, \
+ SPIBUS_IVAR_ ## B, (uintptr_t) t); \
}
-
+
SPIBUS_ACCESSOR(cs, CS, uint32_t)
SPIBUS_ACCESSOR(mode, MODE, uint32_t)
SPIBUS_ACCESSOR(clock, CLOCK, uint32_t)
diff --git a/sys/dev/spibus/spigen.c b/sys/dev/spibus/spigen.c
index 0432dce..46920b3 100644
--- a/sys/dev/spibus/spigen.c
+++ b/sys/dev/spibus/spigen.c
@@ -50,6 +50,11 @@ __FBSDID("$FreeBSD$");
#include <vm/vm_pager.h>
#include <dev/spibus/spi.h>
+#include <dev/spibus/spibusvar.h>
+
+#ifdef FDT
+#include <dev/ofw/ofw_bus_subr.h>
+#endif
#include "spibus_if.h"
@@ -60,7 +65,6 @@ struct spigen_softc {
device_t sc_dev;
struct cdev *sc_cdev;
struct mtx sc_mtx;
- uint32_t sc_clock_speed;
uint32_t sc_command_length_max; /* cannot change while mmapped */
uint32_t sc_data_length_max; /* cannot change while mmapped */
vm_object_t sc_mmap_buffer; /* command, then data */
@@ -70,24 +74,27 @@ struct spigen_softc {
int sc_flags;
};
-#ifdef FDT
-static void
-spigen_identify(driver_t *driver, device_t parent)
-{
- if (device_find_child(parent, "spigen", -1) != NULL)
- return;
- if (BUS_ADD_CHILD(parent, 0, "spigen", -1) == NULL)
- device_printf(parent, "add child failed\n");
-}
-#endif
-
static int
spigen_probe(device_t dev)
{
+ int rv;
+
+ /*
+ * By default we only bid to attach if specifically added by our parent
+ * (usually via hint.spigen.#.at=busname). On FDT systems we bid as the
+ * default driver based on being configured in the FDT data.
+ */
+ rv = BUS_PROBE_NOWILDCARD;
+
+#ifdef FDT
+ if (ofw_bus_status_okay(dev) &&
+ ofw_bus_is_compatible(dev, "freebsd,spigen"))
+ rv = BUS_PROBE_DEFAULT;
+#endif
device_set_desc(dev, "SPI Generic IO");
- return (BUS_PROBE_NOWILDCARD);
+ return (rv);
}
static int spigen_open(struct cdev *, int, int, struct thread *);
@@ -238,15 +245,9 @@ spigen_transfer(struct cdev *cdev, struct spigen_transfer *st)
#endif
transfer.tx_cmd = transfer.rx_cmd = malloc(st->st_command.iov_len,
M_DEVBUF, M_WAITOK);
- if (transfer.tx_cmd == NULL)
- return (ENOMEM);
if (st->st_data.iov_len > 0) {
transfer.tx_data = transfer.rx_data = malloc(st->st_data.iov_len,
M_DEVBUF, M_WAITOK);
- if (transfer.tx_data == NULL) {
- free(transfer.tx_cmd, M_DEVBUF);
- return (ENOMEM);
- }
}
else
transfer.tx_data = transfer.rx_data = NULL;
@@ -316,7 +317,6 @@ spigen_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
struct thread *td)
{
device_t dev = cdev->si_drv1;
- struct spigen_softc *sc = device_get_softc(dev);
int error;
switch (cmd) {
@@ -327,20 +327,20 @@ spigen_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
error = spigen_transfer_mmapped(cdev, (struct spigen_transfer_mmapped *)data);
break;
case SPIGENIOC_GET_CLOCK_SPEED:
- mtx_lock(&sc->sc_mtx);
- *(uint32_t *)data = sc->sc_clock_speed;
- /* XXX TODO: implement spibus ivar call */
- mtx_unlock(&sc->sc_mtx);
- error = 0;
+ error = spibus_get_clock(dev, (uint32_t *)data);
break;
case SPIGENIOC_SET_CLOCK_SPEED:
- mtx_lock(&sc->sc_mtx);
- sc->sc_clock_speed = *(uint32_t *)data;
- mtx_unlock(&sc->sc_mtx);
- error = 0;
+ error = spibus_set_clock(dev, *(uint32_t *)data);
+ break;
+ case SPIGENIOC_GET_SPI_MODE:
+ error = spibus_get_mode(dev, (uint32_t *)data);
+ break;
+ case SPIGENIOC_SET_SPI_MODE:
+ error = spibus_set_mode(dev, *(uint32_t *)data);
break;
default:
- error = EOPNOTSUPP;
+ error = ENOTTY;
+ break;
}
return (error);
}
@@ -439,9 +439,6 @@ static devclass_t spigen_devclass;
static device_method_t spigen_methods[] = {
/* Device interface */
-#ifdef FDT
- DEVMETHOD(device_identify, spigen_identify),
-#endif
DEVMETHOD(device_probe, spigen_probe),
DEVMETHOD(device_attach, spigen_attach),
DEVMETHOD(device_detach, spigen_detach),
@@ -456,3 +453,4 @@ static driver_t spigen_driver = {
};
DRIVER_MODULE(spigen, spibus, spigen_driver, spigen_devclass, 0, 0);
+MODULE_DEPEND(spigen, spibus, 1, 1, 1);
diff --git a/sys/modules/spigen/Makefile b/sys/modules/spigen/Makefile
index f60e117..5a0c8c6 100644
--- a/sys/modules/spigen/Makefile
+++ b/sys/modules/spigen/Makefile
@@ -3,6 +3,16 @@
.PATH: ${SRCTOP}/sys/dev/spibus
KMOD= spigen
SRCS= spigen.c
-SRCS+= device_if.h bus_if.h opt_platform.h spibus_if.h
+
+# Generated files...
+SRCS+= \
+ bus_if.h \
+ device_if.h \
+ opt_platform.h \
+ spibus_if.h \
+
+.if !empty(OPT_FDT)
+SRCS+= ofw_bus_if.h
+.endif
.include <bsd.kmod.mk>
diff --git a/sys/sys/spigenio.h b/sys/sys/spigenio.h
index 9e3ee90..d0bab29 100644
--- a/sys/sys/spigenio.h
+++ b/sys/sys/spigenio.h
@@ -48,5 +48,7 @@ struct spigen_transfer_mmapped {
struct spigen_transfer_mmapped)
#define SPIGENIOC_GET_CLOCK_SPEED _IOR(SPIGENIOC_BASE, 2, uint32_t)
#define SPIGENIOC_SET_CLOCK_SPEED _IOW(SPIGENIOC_BASE, 3, uint32_t)
+#define SPIGENIOC_GET_SPI_MODE _IOR(SPIGENIOC_BASE, 4, uint32_t)
+#define SPIGENIOC_SET_SPI_MODE _IOW(SPIGENIOC_BASE, 5, uint32_t)
#endif /* !_SYS_SPIGENIO_H_ */
OpenPOWER on IntegriCloud