summaryrefslogtreecommitdiff
path: root/sys/dev/spi
diff options
context:
space:
mode:
authorbrad <brad@NetBSD.org>2021-12-07 17:39:53 +0000
committerbrad <brad@NetBSD.org>2021-12-07 17:39:53 +0000
commit32757b8342eaa7d0c0b071bf0140dfe8ec836276 (patch)
tree4b9e14baab6bcd6456266d77c67d6f3dc3fce104 /sys/dev/spi
parentf2b233b00bbb56f46f416b79f5fe01dcf34ef481 (diff)
A driver and user land utility for the Sparkfun Serial Controlled Motor
Driver module as illustrated here: https://www.sparkfun.com/products/13911 A SCMD module is a ARM SOC simular to a Arduino in front of a motor driver chip. The single SCMD module can control two motors and up to 16 additional modules can be chained together using an internal I2C bus. One can interface with the SCMD using tty uart commands, SPI or I2C. The driver in this commit adds a kernel driver for the I2C and SPI interfaces. The command line utility provides a set of convenience commands that support most of the functions of the SCMD and is able to use the tty uart mode, SPI user land or the included kernel driver in a uniform manor. The use of the SCMD module is mostly for small robots and the like, but it can control anything that is controllable by voltage.
Diffstat (limited to 'sys/dev/spi')
-rw-r--r--sys/dev/spi/files.spi6
-rw-r--r--sys/dev/spi/scmdspi.c267
2 files changed, 272 insertions, 1 deletions
diff --git a/sys/dev/spi/files.spi b/sys/dev/spi/files.spi
index 1fe6a8609a9..d8dcb13592d 100644
--- a/sys/dev/spi/files.spi
+++ b/sys/dev/spi/files.spi
@@ -1,4 +1,4 @@
-# $NetBSD: files.spi,v 1.7 2019/11/02 14:33:27 tnn Exp $
+# $NetBSD: files.spi,v 1.8 2021/12/07 17:39:54 brad Exp $
define spibus { }
@@ -44,3 +44,7 @@ file dev/spi/ssdfb_spi.c ssdfb_spi
device mcp3kadc: sysmon_envsys
attach mcp3kadc at spi
file dev/spi/mcp3k.c mcp3kadc
+
+# Sparkfun Serial motor controller
+attach scmd at spi with scmdspi
+file dev/spi/scmdspi.c scmdspi
diff --git a/sys/dev/spi/scmdspi.c b/sys/dev/spi/scmdspi.c
new file mode 100644
index 00000000000..dd3ef314a10
--- /dev/null
+++ b/sys/dev/spi/scmdspi.c
@@ -0,0 +1,267 @@
+
+/* $NetBSD: scmdspi.c,v 1.1 2021/12/07 17:39:54 brad Exp $ */
+
+/*
+ * Copyright (c) 2021 Brad Spencer <brad@anduin.eldar.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: scmdspi.c,v 1.1 2021/12/07 17:39:54 brad Exp $");
+
+/*
+ * SPI driver for the Sparkfun Serial motor controller.
+ * Uses the common scmd driver to do the real work.
+*/
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/device.h>
+#include <sys/module.h>
+#include <sys/conf.h>
+#include <sys/sysctl.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/pool.h>
+#include <sys/kmem.h>
+
+#include <dev/i2c/i2cvar.h>
+#include <dev/spi/spivar.h>
+#include <dev/ic/scmdreg.h>
+#include <dev/ic/scmdvar.h>
+
+extern void scmd_attach(struct scmd_sc *);
+
+static int scmdspi_match(device_t, cfdata_t, void *);
+static void scmdspi_attach(device_t, device_t, void *);
+static int scmdspi_detach(device_t, int);
+static int scmdspi_activate(device_t, enum devact);
+
+#define SCMD_DEBUG
+#ifdef SCMD_DEBUG
+#define DPRINTF(s, l, x) \
+ do { \
+ if (l <= s->sc_scmddebug) \
+ printf x; \
+ } while (/*CONSTCOND*/0)
+#else
+#define DPRINTF(s, l, x)
+#endif
+
+CFATTACH_DECL_NEW(scmdspi, sizeof(struct scmd_sc),
+ scmdspi_match, scmdspi_attach, scmdspi_detach, scmdspi_activate);
+
+/* For the SPI interface on this device, the reads are done in an odd
+ * manor. The first part is normal enough, you send the register binary
+ * or'ed with 0x80 and then the receive the data. However, you MUST also
+ * then receive a dummy value otherwise, everything gets out of sync and
+ * no further reads appear to work unless you do a SPI receive all by itself.
+ * This is documented in the data sheet for this device.
+ *
+ * Please note that the Ardunio code does this a little differently. What is
+ * below works on a Raspberry PI 3 without any apparent problems.
+ *
+ * The delays are also mentioned in the datasheet as being 20us, however, the
+ * Ardunio code does 50us, so do likewise.
+ */
+static int
+scmdspi_read_reg_direct(struct spi_handle *sh, uint8_t reg,
+ uint8_t *buf)
+{
+ int err;
+ uint8_t b;
+ uint8_t rreg = reg | 0x80;
+
+ err = spi_send(sh, 1, &rreg);
+ if (err)
+ return err;
+
+ delay(50);
+
+ b = SCMD_HOLE_VALUE;
+ err = spi_recv(sh, 1, &b);
+ if (err)
+ return err;
+
+ *buf = b;
+
+ delay(50);
+
+ b = SCMD_HOLE_VALUE;
+ err = spi_recv(sh, 1, &b);
+ delay(50);
+
+ return err;
+}
+
+static int
+scmdspi_read_reg(struct scmd_sc *sc, uint8_t reg, uint8_t *buf)
+{
+ return scmdspi_read_reg_direct(sc->sc_sh, reg, buf);
+}
+
+/* SPI writes to this device are normal enough. You send the register
+ * you want making sure that the high bit, 0x80, is clear and then the
+ * data.
+ *
+ * The rule about waiting between operations appears to not apply, however.
+ * This does more or less what the Ardunio code does.
+ */
+static int
+scmdspi_write_reg_direct(struct spi_handle *sh, uint8_t reg,
+ uint8_t buf)
+{
+ uint8_t rreg = reg & 0x7F;
+ int err;
+
+ err = spi_send(sh, 1, &rreg);
+ if (err)
+ return err;
+
+ err = spi_send(sh, 1, &buf);
+ if (err)
+ return err;
+
+ delay(50);
+
+ return err;
+}
+
+static int
+scmdspi_write_reg(struct scmd_sc *sc, uint8_t reg, uint8_t buf)
+{
+ return scmdspi_write_reg_direct(sc->sc_sh, reg, buf);
+}
+
+/* These are to satisfy the common code */
+static int
+scmdspi_acquire_bus(struct scmd_sc *sc)
+{
+ return 0;
+}
+
+static void
+scmdspi_release_bus(struct scmd_sc *sc)
+{
+ return;
+}
+
+/* Nothing more is done here. It would be nice if the device was
+ * actually checked to make sure it was there, but at least on the
+ * Raspberry PI 3 the SPI pins were not set up in ALT0 mode yet and
+ * everything acts like it succeeds. No errors are ever produced while
+ * in that state.
+ */
+static int
+scmdspi_match(device_t parent, cfdata_t match, void *aux)
+{
+ struct spi_attach_args *sa = aux;
+ const bool matchdebug = true;
+
+ if (matchdebug) {
+ printf("Trying to match\n");
+ }
+
+ /* configure for 1MHz and SPI mode 0 according to the data sheet */
+ if (spi_configure(sa->sa_handle, SPI_MODE_0, 1000000))
+ return 0;
+
+ return 1;
+}
+
+static void
+scmdspi_attach(device_t parent, device_t self, void *aux)
+{
+ struct scmd_sc *sc;
+ struct spi_attach_args *sa;
+
+ sa = aux;
+ sc = device_private(self);
+
+ sc->sc_dev = self;
+ sc->sc_sh = sa->sa_handle;
+ sc->sc_scmddebug = 0;
+ sc->sc_topaddr = 0xff;
+ sc->sc_opened = false;
+ sc->sc_dying = false;
+ sc->sc_func_acquire_bus = &scmdspi_acquire_bus;
+ sc->sc_func_release_bus = &scmdspi_release_bus;
+ sc->sc_func_read_register = &scmdspi_read_reg;
+ sc->sc_func_write_register = &scmdspi_write_reg;
+
+ mutex_init(&sc->sc_mutex, MUTEX_DEFAULT, IPL_NONE);
+ mutex_init(&sc->sc_condmutex, MUTEX_DEFAULT, IPL_NONE);
+ mutex_init(&sc->sc_dying_mutex, MUTEX_DEFAULT, IPL_NONE);
+ cv_init(&sc->sc_condvar, "scmdspicv");
+ cv_init(&sc->sc_cond_dying, "scmdspidc");
+
+ /* Please note that if the pins are not set up for SPI, the attachment
+ * will work, but it will not figure out that there are slave modules.
+ * It is likely required that a re-enumeration be performed after the pins
+ * are set. This can be done from userland later.
+ */
+ scmd_attach(sc);
+
+ return;
+}
+
+/* These really do not do a whole lot, as SPI devices do not seem to work
+ * as modules.
+ */
+static int
+scmdspi_detach(device_t self, int flags)
+{
+ struct scmd_sc *sc;
+
+ sc = device_private(self);
+
+ mutex_enter(&sc->sc_mutex);
+ sc->sc_dying = true;
+ /* If this is true we are still open, destroy the condvar */
+ if (sc->sc_opened) {
+ mutex_enter(&sc->sc_dying_mutex);
+ DPRINTF(sc, 2, ("%s: Will wait for anything to exit\n",
+ device_xname(sc->sc_dev)));
+ /* In the worst case this will time out after 5 seconds.
+ * It really should not take that long for the drain / whatever
+ * to happen
+ */
+ cv_timedwait_sig(&sc->sc_cond_dying,
+ &sc->sc_dying_mutex, mstohz(5000));
+ mutex_exit(&sc->sc_dying_mutex);
+ cv_destroy(&sc->sc_cond_dying);
+ }
+ cv_destroy(&sc->sc_condvar);
+ mutex_exit(&sc->sc_mutex);
+
+ mutex_destroy(&sc->sc_mutex);
+ mutex_destroy(&sc->sc_condmutex);
+
+ return 0;
+}
+
+int
+scmdspi_activate(device_t self, enum devact act)
+{
+ struct scmd_sc *sc = device_private(self);
+
+ switch (act) {
+ case DVACT_DEACTIVATE:
+ sc->sc_dying = true;
+ return 0;
+ default:
+ return EOPNOTSUPP;
+ }
+}