diff options
| author | brad <brad@NetBSD.org> | 2021-12-07 17:39:53 +0000 |
|---|---|---|
| committer | brad <brad@NetBSD.org> | 2021-12-07 17:39:53 +0000 |
| commit | 32757b8342eaa7d0c0b071bf0140dfe8ec836276 (patch) | |
| tree | 4b9e14baab6bcd6456266d77c67d6f3dc3fce104 /sys/dev | |
| parent | f2b233b00bbb56f46f416b79f5fe01dcf34ef481 (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')
| -rw-r--r-- | sys/dev/i2c/files.i2c | 6 | ||||
| -rw-r--r-- | sys/dev/i2c/scmdi2c.c | 303 | ||||
| -rw-r--r-- | sys/dev/ic/Makefile | 5 | ||||
| -rw-r--r-- | sys/dev/ic/scmd.c | 627 | ||||
| -rw-r--r-- | sys/dev/ic/scmdreg.h | 236 | ||||
| -rw-r--r-- | sys/dev/ic/scmdvar.h | 44 | ||||
| -rw-r--r-- | sys/dev/spi/files.spi | 6 | ||||
| -rw-r--r-- | sys/dev/spi/scmdspi.c | 267 |
8 files changed, 1490 insertions, 4 deletions
diff --git a/sys/dev/i2c/files.i2c b/sys/dev/i2c/files.i2c index 99962237981..6d3c45823f0 100644 --- a/sys/dev/i2c/files.i2c +++ b/sys/dev/i2c/files.i2c @@ -1,4 +1,4 @@ -# $NetBSD: files.i2c,v 1.119 2021/11/06 13:34:40 brad Exp $ +# $NetBSD: files.i2c,v 1.120 2021/12/07 17:39:54 brad Exp $ obsolete defflag opt_i2cbus.h I2C_SCAN define i2cbus { } @@ -420,3 +420,7 @@ file dev/i2c/pcagpio.c pcagpio device pcf8574io: leds, sysmon_envsys attach pcf8574io at iic file dev/i2c/pcf8574.c pcf8574io + +# Sparkfun Serial motor controller +attach scmd at iic with scmdi2c +file dev/i2c/scmdi2c.c scmdi2c diff --git a/sys/dev/i2c/scmdi2c.c b/sys/dev/i2c/scmdi2c.c new file mode 100644 index 00000000000..eafe72b0408 --- /dev/null +++ b/sys/dev/i2c/scmdi2c.c @@ -0,0 +1,303 @@ + +/* $NetBSD: scmdi2c.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: scmdi2c.c,v 1.1 2021/12/07 17:39:54 brad Exp $"); + +/* + * I2C driver for the Sparkfun Serial motor controller. + * Uses the common code to do the actual 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 struct cdevsw scmd_cdevsw; + +extern void scmd_attach(struct scmd_sc *); + +static int scmdi2c_poke(i2c_tag_t, i2c_addr_t, bool); +static int scmdi2c_match(device_t, cfdata_t, void *); +static void scmdi2c_attach(device_t, device_t, void *); +static int scmdi2c_detach(device_t, int); +static int scmdi2c_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(scmdi2c, sizeof(struct scmd_sc), + scmdi2c_match, scmdi2c_attach, scmdi2c_detach, scmdi2c_activate); + +static int +scmdi2c_read_reg_direct(i2c_tag_t tag, i2c_addr_t addr, uint8_t reg, + uint8_t *buf) +{ + return iic_exec(tag, I2C_OP_READ_WITH_STOP, addr, ®, 1, buf, + 1, 0); +} + +static int +scmdi2c_read_reg(struct scmd_sc *sc, uint8_t reg, uint8_t *buf) +{ + return scmdi2c_read_reg_direct(sc->sc_tag, sc->sc_addr, reg, buf); +} + +static int +scmdi2c_write_reg_direct(i2c_tag_t tag, i2c_addr_t addr, uint8_t reg, + uint8_t buf) +{ + return iic_exec(tag, I2C_OP_WRITE_WITH_STOP, addr, ®, 1, &buf, + 1, 0); +} + +static int +scmdi2c_write_reg(struct scmd_sc *sc, uint8_t reg, uint8_t buf) +{ + return scmdi2c_write_reg_direct(sc->sc_tag, sc->sc_addr, reg, buf); +} + +static int +scmdi2c_acquire_bus(struct scmd_sc *sc) +{ + return(iic_acquire_bus(sc->sc_tag, 0)); +} + +static void +scmdi2c_release_bus(struct scmd_sc *sc) +{ + iic_release_bus(sc->sc_tag, 0); +} + +static int +scmdi2c_poke(i2c_tag_t tag, i2c_addr_t addr, bool matchdebug) +{ + uint8_t reg = SCMD_REG_ID; + uint8_t buf; + int error; + + error = scmdi2c_read_reg_direct(tag, addr, reg, &buf); + if (matchdebug) { + printf("poke X 1: %d %02x %d\n", addr, buf, error); + } + return error; +} + +static int +scmdi2c_match(device_t parent, cfdata_t match, void *aux) +{ + struct i2c_attach_args *ia = aux; + int error, match_result; + const bool matchdebug = false; + + if (iic_use_direct_match(ia, match, NULL, &match_result)) + return match_result; + + if (matchdebug) { + printf("Looking at ia_addr: %x\n",ia->ia_addr); + } + + /* indirect config - check for configured address */ + if (!(ia->ia_addr >= SCMD_LOW_I2C_ADDR && + ia->ia_addr <= SCMD_HIGH_I2C_ADDR)) + return 0; + + /* + * Check to see if something is really at this i2c address. + * This will keep phantom devices from appearing + */ + if (iic_acquire_bus(ia->ia_tag, 0) != 0) { + if (matchdebug) + printf("in match acquire bus failed\n"); + return 0; + } + + error = scmdi2c_poke(ia->ia_tag, ia->ia_addr, matchdebug); + iic_release_bus(ia->ia_tag, 0); + + return error == 0 ? I2C_MATCH_ADDRESS_AND_PROBE : 0; +} + +static void +scmdi2c_attach(device_t parent, device_t self, void *aux) +{ + struct scmd_sc *sc; + struct i2c_attach_args *ia; + + ia = aux; + sc = device_private(self); + + sc->sc_dev = self; + sc->sc_tag = ia->ia_tag; + sc->sc_addr = ia->ia_addr; + sc->sc_scmddebug = 0; + sc->sc_topaddr = 0xff; + sc->sc_opened = false; + sc->sc_dying = false; + sc->sc_func_acquire_bus = &scmdi2c_acquire_bus; + sc->sc_func_release_bus = &scmdi2c_release_bus; + sc->sc_func_read_register = &scmdi2c_read_reg; + sc->sc_func_write_register = &scmdi2c_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, "scmdi2ccv"); + cv_init(&sc->sc_cond_dying, "scmdi2cdc"); + + scmd_attach(sc); + + return; +} + +static int +scmdi2c_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 +scmdi2c_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; + } +} + +MODULE(MODULE_CLASS_DRIVER, scmdi2c, "i2cexec,scmd"); + +#ifdef _MODULE +/* Like other drivers, we do this because the scmd common + * driver has the definitions already. + */ +#undef CFDRIVER_DECL +#define CFDRIVER_DECL(name, class, attr) +#include "ioconf.c" +#endif + +static int +scmdi2c_modcmd(modcmd_t cmd, void *opaque) +{ +#ifdef _MODULE + int error = 0; + static struct cfdriver * const no_cfdriver_vec[] = { NULL }; +#endif + + switch (cmd) { + case MODULE_CMD_INIT: +#ifdef _MODULE + /* I really do not understand why I had to do this this way. + * If I did not then the module would either not install when + * the SPI driver and hence the scmd dependent driver was compiled + * into the kernel, or it would not install if it was not. + * I suspect something is still not set up correctly somewhere, but + * I am at a lose to see what. + * + * The first config_init_component will fail if the SPI driver and the + * scmd dependent is in the kernel already, but the second one will work. + * Otherwise the other way around. + * + * This all manages to get this module set up in any situation. + */ + error = config_init_component(cfdriver_ioconf_scmdi2c, + cfattach_ioconf_scmdi2c, cfdata_ioconf_scmdi2c); + if (error) { + aprint_error("%s: Trying no_cfdriver_vec method: %d\n", + scmd_cd.cd_name, error); + error = config_init_component(no_cfdriver_vec, + cfattach_ioconf_scmdi2c, cfdata_ioconf_scmdi2c); + } + + return error; +#else + return 0; +#endif + case MODULE_CMD_FINI: +#ifdef _MODULE + /* See above.. same thing */ + error = config_fini_component(cfdriver_ioconf_scmdi2c, + cfattach_ioconf_scmdi2c, cfdata_ioconf_scmdi2c); + if (error) { + aprint_error("%s: Trying no_cfdriver_vec method: %d\n", + scmd_cd.cd_name, error); + error = config_fini_component(no_cfdriver_vec, + cfattach_ioconf_scmdi2c, cfdata_ioconf_scmdi2c); + } + + return error; +#else + return 0; +#endif + default: + return ENOTTY; + } +} diff --git a/sys/dev/ic/Makefile b/sys/dev/ic/Makefile index 0eacd499602..6d9b381de62 100644 --- a/sys/dev/ic/Makefile +++ b/sys/dev/ic/Makefile @@ -1,9 +1,10 @@ -# $NetBSD: Makefile,v 1.27 2020/01/19 06:55:21 thorpej Exp $ +# $NetBSD: Makefile,v 1.28 2021/12/07 17:39:54 brad Exp $ INCSDIR= /usr/include/dev/ic # Only install includes which are used by userland INCS= athioctl.h bt8xx.h hd44780var.h icpreg.h icp_ioctl.h isp_ioctl.h \ - mlxreg.h mlxio.h nvmeio.h nvmereg.h qemufwcfgio.h wdcreg.h wi_ieee.h + mlxreg.h mlxio.h nvmeio.h nvmereg.h qemufwcfgio.h scmdreg.h wdcreg.h \ + wi_ieee.h .include <bsd.kinc.mk> diff --git a/sys/dev/ic/scmd.c b/sys/dev/ic/scmd.c new file mode 100644 index 00000000000..9ac316acf4a --- /dev/null +++ b/sys/dev/ic/scmd.c @@ -0,0 +1,627 @@ + +/* $NetBSD: scmd.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: scmd.c,v 1.1 2021/12/07 17:39:54 brad Exp $"); + +/* + * Common driver for the Sparkfun Serial motor controller. + * Calls out to specific frontends to move bits. +*/ + +#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/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> + +void scmd_attach(struct scmd_sc *); +static void scmd_wait_restart(struct scmd_sc *, bool); +static int scmd_get_topaddr(struct scmd_sc *); +static int scmd_verify_sysctl(SYSCTLFN_ARGS); +static int scmd_local_read(struct scmd_sc *, uint8_t, uint8_t *); +static int scmd_remote_read(struct scmd_sc *, int, uint8_t *); +static int scmd_local_write(struct scmd_sc *, uint8_t, uint8_t); +static int scmd_remote_write(struct scmd_sc *, int, uint8_t); + +#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 + +extern struct cfdriver scmd_cd; + +static dev_type_open(scmd_open); +static dev_type_read(scmd_read); +static dev_type_write(scmd_write); +static dev_type_close(scmd_close); +const struct cdevsw scmd_cdevsw = { + .d_open = scmd_open, + .d_close = scmd_close, + .d_read = scmd_read, + .d_write = scmd_write, + .d_ioctl = noioctl, + .d_stop = nostop, + .d_tty = notty, + .d_poll = nopoll, + .d_mmap = nommap, + .d_kqfilter = nokqfilter, + .d_discard = nodiscard, + .d_flag = D_OTHER +}; + +static int +scmd_verify_sysctl(SYSCTLFN_ARGS) +{ + int error, t; + struct sysctlnode node; + + node = *rnode; + t = *(int *)rnode->sysctl_data; + node.sysctl_data = &t; + error = sysctl_lookup(SYSCTLFN_CALL(&node)); + if (error || newp == NULL) + return error; + + if (t < 0) + return EINVAL; + + *(int *)rnode->sysctl_data = t; + + return 0; +} + +static int +scmd_sysctl_init(struct scmd_sc *sc) +{ + int error; + const struct sysctlnode *cnode; + int sysctlroot_num; + + if ((error = sysctl_createv(&sc->sc_scmdlog, 0, NULL, &cnode, + 0, CTLTYPE_NODE, device_xname(sc->sc_dev), + SYSCTL_DESCR("scmd controls"), NULL, 0, NULL, 0, CTL_HW, + CTL_CREATE, CTL_EOL)) != 0) + return error; + + sysctlroot_num = cnode->sysctl_num; + +#ifdef SCMD_DEBUG + if ((error = sysctl_createv(&sc->sc_scmdlog, 0, NULL, &cnode, + CTLFLAG_READWRITE, CTLTYPE_INT, "debug", + SYSCTL_DESCR("Debug level"), scmd_verify_sysctl, 0, + &sc->sc_scmddebug, 0, CTL_HW, sysctlroot_num, CTL_CREATE, + CTL_EOL)) != 0) + return error; + +#endif + + return 0; +} + +/* Restarts and re-enumeration of the device is a little strange. + * It will take a very long time to complete. It would be more polite + * to use a condvar for this wait, but it was noticed that those may + * not work if done too early in boot and will just hang the boot, so + * delay is also offered as an option. + */ +static void +scmd_wait_restart(struct scmd_sc *sc, bool usedelay) +{ + int error; + uint8_t buf = SCMD_HOLE_VALUE; + int c = 0; + + do { + if (usedelay) { + delay(1000000); + } else { + mutex_enter(&sc->sc_condmutex); + cv_timedwait(&sc->sc_condvar, &sc->sc_condmutex, + mstohz(1000)); + mutex_exit(&sc->sc_condmutex); + } + + error = (*(sc->sc_func_read_register))(sc, SCMD_REG_STATUS_1, &buf); + + DPRINTF(sc, 2, ("%s: Read back status after restart: %02x %d\n", + device_xname(sc->sc_dev), buf, error)); + + c++; + } while (c <= 20 && buf != 0x00); +} + +static int +scmd_get_topaddr(struct scmd_sc *sc) +{ + uint8_t topaddr; + int error; + + error = (*(sc->sc_func_read_register))(sc, SCMD_REG_SLV_TOP_ADDR, &topaddr); + + if (error) { + topaddr = 0; + } + return topaddr; +} + +/* Note that this assumes that you can actually access the device. + * In at least one case right now, SPI on a Raspberry PI 3, the pins + * have not been set up to allow SPI to function, but nothing is + * returned as an error either. We do the best that can be done right + * now. + */ +void +scmd_attach(struct scmd_sc *sc) +{ + int error; + + aprint_normal("\n"); + + if ((error = scmd_sysctl_init(sc)) != 0) { + aprint_error_dev(sc->sc_dev, "Can't setup sysctl tree (%d)\n", error); + goto out; + } + + error = (*(sc->sc_func_acquire_bus))(sc); + if (error) { + aprint_error_dev(sc->sc_dev, "Could not acquire iic bus: %d\n", + error); + goto out; + } + + error = (*(sc->sc_func_write_register))(sc, SCMD_REG_CONTROL_1, SCMD_CONTROL_1_RESTART); + if (error != 0) + aprint_error_dev(sc->sc_dev, "Reset failed: %d\n", error); + + scmd_wait_restart(sc, true); + + sc->sc_topaddr = scmd_get_topaddr(sc); + + DPRINTF(sc, 2, ("%s: Top remote module address: %02x\n", + device_xname(sc->sc_dev), sc->sc_topaddr)); + + uint8_t fwversion; + uint8_t id; + uint8_t pins; + + error = (*(sc->sc_func_read_register))(sc, SCMD_REG_FID, &fwversion); + if (error) { + aprint_error_dev(sc->sc_dev, "Read of FID failed: %d\n", + error); + goto out; + } + + error = (*(sc->sc_func_read_register))(sc, SCMD_REG_ID, &id); + if (error) { + aprint_error_dev(sc->sc_dev, "Read of ID failed: %d\n", + error); + goto out; + } + + error = (*(sc->sc_func_read_register))(sc, SCMD_REG_CONFIG_BITS, &pins); + if (error) { + aprint_error_dev(sc->sc_dev, "Read of CONFIG_BITS failed: %d\n", + error); + goto out; + } + + aprint_normal_dev(sc->sc_dev, "Sparkfun Serial motor controller, " + "Firmware version: %02x, ID: %02x%s, Jumper pins: %02x\n", + fwversion, id, (id == SCMD_EXPECTED_ID) ? " (expected ID)" : " (unexpected ID)", + pins); + + out: + (*(sc->sc_func_release_bus))(sc); + if (error != 0) { + aprint_error_dev(sc->sc_dev, "Unable to setup device\n"); + } + + return; +} + +/* This device has the effect of creating a virtual register space of all + * of the attached modules. All you have to do is read and write to anything + * in that space and you can hit the main module and all chained slave modules + * without having to worry about the view port set up. + * + * 0x00 - 0x7E -- the first and main module + * 0x7F - 0xFD -- the first slaved module + * ...etc... + * + */ +static int +scmd_open(dev_t dev, int flags, int fmt, struct lwp *l) +{ + struct scmd_sc *sc; + + sc = device_lookup_private(&scmd_cd, minor(dev)); + if (!sc) + return ENXIO; + + if (sc->sc_opened) + return EBUSY; + + /* This is a meaningless assigment to keep GCC from + * complaining. + */ + sc->sc_func_attach = &scmd_attach; + + mutex_enter(&sc->sc_mutex); + sc->sc_opened = true; + mutex_exit(&sc->sc_mutex); + + return 0; +} + +static int +scmd_maxregister(int topaddr) +{ + if (topaddr >= SCMD_REMOTE_ADDR_LOW && + topaddr <= SCMD_REMOTE_ADDR_HIGH) { + int i = (topaddr - SCMD_REMOTE_ADDR_LOW) + 2; + return (SCMD_REG_SIZE * i) - 1; + } else { + return SCMD_LAST_REG; + } +} + +/* Please note that that setting up and using the view port + * to get access to SCMD devices that are chained off of the main + * device is not atomic. Hopefully this all happens fast enough + * so that nothing can sneak in and mess with the registers. + */ +static int +scmd_set_view_port(struct scmd_sc *sc, int reg) +{ + int err; + int loc = reg / SCMD_REG_SIZE; + uint8_t vpi2creg = reg % SCMD_REG_SIZE; + uint8_t vpi2caddr = (SCMD_REMOTE_ADDR_LOW + loc) - 1; + + DPRINTF(sc, 2, ("%s: View port addr: %02x ; View port register: %02x ; Orig register: %04x\n", + device_xname(sc->sc_dev), vpi2caddr, vpi2creg, reg)); + + err = (*(sc->sc_func_write_register))(sc, SCMD_REG_REM_ADDR, vpi2caddr); + if (! err) + err = (*(sc->sc_func_write_register))(sc, SCMD_REG_REM_OFFSET, vpi2creg); + + return err; +} + +/* It is not defined what happens if the Not Defined in the datasheet + * registers are accessed, so block them. + */ +static int +scmd_local_read(struct scmd_sc *sc, uint8_t reg, uint8_t *buf) +{ + if (SCMD_IS_HOLE(reg)) { + *buf = SCMD_HOLE_VALUE; + return 0; + } + + return (*(sc->sc_func_read_register))(sc, reg, buf); +} + +static int +scmd_remote_read(struct scmd_sc *sc, int reg, uint8_t *buf) +{ + int err; + int c; + uint8_t b; + + if (SCMD_IS_HOLE(reg % SCMD_REG_SIZE)) { + *buf = SCMD_HOLE_VALUE; + return 0; + } + + err = scmd_set_view_port(sc, reg); + if (! err) { + b = 0xff; /* you can write anything here.. it doesn't matter */ + err = (*(sc->sc_func_write_register))(sc, SCMD_REG_REM_READ, b); + if (! err) { + /* So ... there is no way to really know that the data is ready and + * there is no way to know if there was an error in the master module reading + * the data from the slave module. The data sheet says wait 5ms.. so do that + * and see if the register cleared, but don't wait forever... I can't see how + * it would not be possible to read junk at times. + */ + c = 0; + do { + delay(5000); + err = (*(sc->sc_func_read_register))(sc, SCMD_REG_REM_READ, &b); + c++; + } while ((c < 10) && (b != 0x00) && (!err)); + /* We can only hope that whatever was read from the slave module is there */ + if (! err) + err = (*(sc->sc_func_read_register))(sc, SCMD_REG_REM_DATA_RD, buf); + } + } + + return err; +} + +static int +scmd_read(dev_t dev, struct uio *uio, int flags) +{ + struct scmd_sc *sc; + int error; + + if ((sc = device_lookup_private(&scmd_cd, minor(dev))) == NULL) + return ENXIO; + + /* We do not make this an error. There is nothing wrong with running + * off the end here, just return EOF. + */ + if (uio->uio_offset > scmd_maxregister(sc->sc_topaddr)) + return 0; + + if ((error = (*(sc->sc_func_acquire_bus))(sc)) != 0) + return error; + + while (uio->uio_resid && + uio->uio_offset <= scmd_maxregister(sc->sc_topaddr) && + !sc->sc_dying) { + uint8_t buf; + int reg_addr = uio->uio_offset; + + if (reg_addr <= SCMD_LAST_REG) { + if ((error = scmd_local_read(sc, (uint8_t)reg_addr, &buf)) != 0) { + (*(sc->sc_func_release_bus))(sc); + aprint_error_dev(sc->sc_dev, + "%s: local read failed at 0x%02x: %d\n", + __func__, reg_addr, error); + return error; + } + } else { + if ((error = scmd_remote_read(sc, reg_addr, &buf)) != 0) { + (*(sc->sc_func_release_bus))(sc); + aprint_error_dev(sc->sc_dev, + "%s: remote read failed at 0x%02x: %d\n", + __func__, reg_addr, error); + return error; + } + } + + if (sc->sc_dying) + break; + + if ((error = uiomove(&buf, 1, uio)) != 0) { + (*(sc->sc_func_release_bus))(sc); + return error; + } + } + + (*(sc->sc_func_release_bus))(sc); + + if (sc->sc_dying) { + return EIO; + } + + return 0; +} + +/* Same thing about the undefined registers. Don't actually allow + * writes as it is not clear what happens when you do that. + */ +static int +scmd_local_write(struct scmd_sc *sc, uint8_t reg, uint8_t buf) +{ + if (SCMD_IS_HOLE(reg)) + return 0; + + return (*(sc->sc_func_write_register))(sc, reg, buf); +} + +static int +scmd_remote_write(struct scmd_sc *sc, int reg, uint8_t buf) +{ + int err; + int c; + uint8_t b; + + if (SCMD_IS_HOLE(reg % SCMD_REG_SIZE)) { + return 0; + } + + err = scmd_set_view_port(sc, reg); + if (! err) { + /* We just sort of send this write off and wait to see if the register + * clears. There really isn't any indication that the data made it to the + * slave modules and there really are not any errors reported. + */ + err = (*(sc->sc_func_write_register))(sc, SCMD_REG_REM_DATA_WR, buf); + if (! err) { + b = 0xff; /* you can write anything here.. it doesn't matter */ + err = (*(sc->sc_func_write_register))(sc, SCMD_REG_REM_WRITE, b); + if (! err) { + c = 0; + do { + delay(5000); + err = (*(sc->sc_func_read_register))(sc, SCMD_REG_REM_WRITE, &b); + c++; + } while ((c < 10) && (b != 0x00) && (!err)); + } + } + } + + return err; +} + +static int +scmd_write(dev_t dev, struct uio *uio, int flags) +{ + struct scmd_sc *sc; + int error; + + if ((sc = device_lookup_private(&scmd_cd, minor(dev))) == NULL) + return ENXIO; + + /* Same thing as read, this is not considered an error */ + if (uio->uio_offset > scmd_maxregister(sc->sc_topaddr)) + return 0; + + if ((error = (*(sc->sc_func_acquire_bus))(sc)) != 0) + return error; + + while (uio->uio_resid && + uio->uio_offset <= scmd_maxregister(sc->sc_topaddr) && + !sc->sc_dying) { + uint8_t buf; + int reg_addr = uio->uio_offset; + + if ((error = uiomove(&buf, 1, uio)) != 0) + break; + + if (sc->sc_dying) + break; + + if (reg_addr <= SCMD_LAST_REG) { + if ((error = scmd_local_write(sc, (uint8_t)reg_addr, buf)) != 0) { + (*(sc->sc_func_release_bus))(sc); + aprint_error_dev(sc->sc_dev, + "%s: local write failed at 0x%02x: %d\n", + __func__, reg_addr, error); + return error; + } + + /* If this was a local command to the control register that + * can perform re-enumeration, then do the wait thing. + * It is not as important that this be done for remote module + * access as the only thing that you could really do there is + * a restart and not re-emumeration, which is really what the wait + * is all about. + */ + if (reg_addr == SCMD_REG_CONTROL_1) { + scmd_wait_restart(sc, false); + + sc->sc_topaddr = scmd_get_topaddr(sc); + aprint_normal_dev(sc->sc_dev, "Highest I2C address on expansion bus is: %02x\n", + sc->sc_topaddr); + } + } else { + if ((error = scmd_remote_write(sc, reg_addr, buf)) != 0) { + (*(sc->sc_func_release_bus))(sc); + aprint_error_dev(sc->sc_dev, + "%s: remote write failed at 0x%02x: %d\n", + __func__, reg_addr, error); + return error; + } + } + } + + (*(sc->sc_func_release_bus))(sc); + + if (sc->sc_dying) { + return EIO; + } + + return error; +} + +static int +scmd_close(dev_t dev, int flags, int fmt, struct lwp *l) +{ + struct scmd_sc *sc; + + sc = device_lookup_private(&scmd_cd, minor(dev)); + + if (sc->sc_dying) { + DPRINTF(sc, 2, ("%s: Telling all we are almost dead\n", + device_xname(sc->sc_dev))); + mutex_enter(&sc->sc_dying_mutex); + cv_signal(&sc->sc_cond_dying); + mutex_exit(&sc->sc_dying_mutex); + return EIO; + } + + mutex_enter(&sc->sc_mutex); + sc->sc_opened = false; + mutex_exit(&sc->sc_mutex); + + return(0); +} + +MODULE(MODULE_CLASS_DRIVER, scmd, NULL); + +#ifdef _MODULE +CFDRIVER_DECL(scmd, DV_DULL, NULL); +#include "ioconf.c" +#endif + +static int +scmd_modcmd(modcmd_t cmd, void *opaque) +{ +#ifdef _MODULE + int error = 0; + int bmaj = -1, cmaj = -1; +#endif + + switch (cmd) { + case MODULE_CMD_INIT: +#ifdef _MODULE + error = config_init_component(cfdriver_ioconf_scmd, + cfattach_ioconf_scmd, cfdata_ioconf_scmd); + if (error) + aprint_error("%s: unable to init component: %d\n", + scmd_cd.cd_name, error); + + error = devsw_attach("scmd", NULL, &bmaj, + &scmd_cdevsw, &cmaj); + if (error) { + aprint_error("%s: unable to attach devsw: %d\n", + scmd_cd.cd_name, error); + config_fini_component(cfdriver_ioconf_scmd, + cfattach_ioconf_scmd, cfdata_ioconf_scmd); + } + + return error; +#else + return 0; +#endif + case MODULE_CMD_FINI: +#ifdef _MODULE + devsw_detach(NULL, &scmd_cdevsw); + error = config_fini_component(cfdriver_ioconf_scmd, + cfattach_ioconf_scmd, cfdata_ioconf_scmd); + + return error; +#else + return 0; +#endif + default: + return ENOTTY; + } +} diff --git a/sys/dev/ic/scmdreg.h b/sys/dev/ic/scmdreg.h new file mode 100644 index 00000000000..f97fe32ae09 --- /dev/null +++ b/sys/dev/ic/scmdreg.h @@ -0,0 +1,236 @@ +/* $NetBSD: scmdreg.h,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. + */ + +#ifndef _DEV_SCMDREG_H_ +#define _DEV_SCMDREG_H_ + +/* The device can occupy a large number of I2C addresses */ + +#define SCMD_LOW_I2C_ADDR 0x58 +#define SCMD_HIGH_I2C_ADDR 0x61 + +/* The register address space for each module */ + +/* Some config and info registers */ +#define SCMD_REG_FID 0x00 /* Firmware version */ +#define SCMD_REG_ID 0x01 /* ID.. always 0xA9 */ +#define SCMD_EXPECTED_ID 0xA9 /* What is always expected from the ID register */ +#define SCMD_REG_SLAVE_ADDR 0x02 /* If a slave module, the I2C address */ +#define SCMD_REG_CONFIG_BITS 0x03 /* Bit pattern of the jumpters on the board */ +/* Diagnostics and debug registers */ +#define SCMD_REG_U_I2C_RD_ERR 0x04 /* RD_ERR bits on USER port */ +#define SCMD_REG_U_I2C_WR_ERR 0x05 /* WR_ERR bits on USER port */ +#define SCMD_REG_U_BUF_DUMPED 0x06 /* Count of dumped buffers */ +#define SCMD_REG_E_I2C_RD_ERR 0x07 /* Slave I2C read errors */ +#define SCMD_REG_E_I2C_WR_ERR 0x08 /* Slave I2C write errors */ +#define SCMD_REG_LOOP_TIME 0x09 /* Reports time in 100us of main loop */ +#define SCMD_REG_SLV_POLL_CNT 0x0A /* Polls looking for slave modules */ +#define SCMD_REG_SLV_TOP_ADDR 0x0B /* Highest slave I2C address */ +#define SCMD_REG_MST_E_ERR 0x0C /* Number of expansion port I2C errors seen + * by the master module + */ +#define SCMD_REG_MST_E_STATUS 0x0D /* Status of master controller board, expansion + * port I2C (write status) + */ +#define SCMD_REG_FSAFE_FAULTS 0x0E /* Number of failsafe conditions seen */ +#define SCMD_REG_REG_OOR_CNT 0x0F /* Register access attempts outside of range */ +#define SCMD_REG_REG_RO_WRITE_CNT 0x10 /* Write lock write attempts */ +#define SCMD_REG_GEN_TEST_WORD 0x11 /* Write causes data to be applied to + * REM_DATA_RD. + * 0x01: Read config bit pins + */ +/* Local configuration registers */ +#define SCMD_REG_MOTOR_A_INVERT 0x12 /* Invert direction of motor A on local module, + * or both if in bridged mode + */ +#define SCMD_REG_MOTOR_B_INVERT 0x13 /* Invert direction of motor B on local module */ +#define SCMD_REG_BRIDGE 0x14 /* Bridge motor A and B on local module */ +#define SCMD_REG_LOCAL_MASTER_LOCK 0x15 /* Unlocked when set to 0x9B. Allows writes to + * nearly any and every register. + */ +#define SCMD_REG_LOCAL_USER_LOCK 0x16 /* Unlocked when set to 0x5C. Allows writes to + * registers that are marked as user lockable + */ +#define SCMD_REG_MST_E_IN_FN 0x17 /* Set action when master module config-in pin + * goes high. + * B1-0: Restarts + * 0x00 - do nothing + * 0x01 - reboot module + * 0x02 - re-enumerate + * B2: User port behavior + * 0x00 - do nothing + * 0x01 - reinitialize user port + * B3: Expansion port behavior + * 0x00 - do nothing + * 0x01 - reinitialize expansion port + */ +#define SCMD_REG_U_PORT_CLKDIV_U 0x18 /* Clock divisor for the user port */ +#define SCMD_REG_U_PORT_CLKDIV_L 0x19 /* See data sheet for more details */ +#define SCMD_REG_U_PORT_CLKDIV_CTRL 0x1A /* See data sheet for more details */ +#define SCMD_REG_E_PORT_CLKDIV_U 0x1B /* Clock divisor for the expansion port */ +#define SCMD_REG_E_PORT_CLKDIV_L 0x1C /* See data sheet for more details */ +#define SCMD_REG_E_PORT_CLKDIV_CTRL 0x1D /* See data sheet for more details */ +#define SCMD_REG_U_BUS_UART_BAUD 0x1E /* Current baud rate when the module is + * communicating as a UART. This register + * can only be read and never set directly. + * Use the 'U' command when communicating as + * an UART. + */ +#define SCMD_REG_FSAFE_CTRL 0x1F /* Configure what happens when a failsafe + * condition happens: + * B0: output behavior + * 0x00 - maintain last motor drive levels + * 0x01 - set output level to 0 drive + * B2-1: Restart operation on master module + * 0x00 - do nothing + * 0x01 - reboot + * 0x02 - re-enumerate + * B3 - user port behavior + * 0x00 - do nothing + * 0x01 - reinitialize user port + * B4 - expansion port behavior + * 0x00 - do nothing + * 0x01 - reinitialize expansion port + */ +/* Motor drive levels, local and remote */ +#define SCMD_REG_MA_DRIVE 0x20 /* Drive level on master module motor A */ +#define SCMD_REG_MB_DRIVE 0x21 /* Drive level on master module motor B */ +#define SCMD_REG_S1A_DRIVE 0x22 /* Drive level on slave 1 module motor A */ +#define SCMD_REG_S1B_DRIVE 0x23 /* Drive level on slave 1 module motor B */ +#define SCMD_REG_S2A_DRIVE 0x24 /* Drive level on slave 2 module motor A */ +#define SCMD_REG_S2B_DRIVE 0x25 /* Drive level on slave 2 module motor B */ +#define SCMD_REG_S3A_DRIVE 0x26 /* Drive level on slave 3 module motor A */ +#define SCMD_REG_S3B_DRIVE 0x27 /* Drive level on slave 3 module motor B */ +#define SCMD_REG_S4A_DRIVE 0x28 /* Drive level on slave 4 module motor A */ +#define SCMD_REG_S4B_DRIVE 0x29 /* Drive level on slave 4 module motor B */ +#define SCMD_REG_S5A_DRIVE 0x2A /* Drive level on slave 5 module motor A */ +#define SCMD_REG_S5B_DRIVE 0x2B /* Drive level on slave 5 module motor B */ +#define SCMD_REG_S6A_DRIVE 0x2C /* Drive level on slave 6 module motor A */ +#define SCMD_REG_S6B_DRIVE 0x2D /* Drive level on slave 6 module motor B */ +#define SCMD_REG_S7A_DRIVE 0x2E /* Drive level on slave 7 module motor A */ +#define SCMD_REG_S7B_DRIVE 0x2F /* Drive level on slave 7 module motor B */ +#define SCMD_REG_S8A_DRIVE 0x30 /* Drive level on slave 8 module motor A */ +#define SCMD_REG_S8B_DRIVE 0x31 /* Drive level on slave 8 module motor B */ +#define SCMD_REG_S9A_DRIVE 0x32 /* Drive level on slave 9 module motor A */ +#define SCMD_REG_S9B_DRIVE 0x33 /* Drive level on slave 9 module motor B */ +#define SCMD_REG_S10A_DRIVE 0x34 /* Drive level on slave 10 module motor A */ +#define SCMD_REG_S10B_DRIVE 0x35 /* Drive level on slave 10 module motor B */ +#define SCMD_REG_S11A_DRIVE 0x36 /* Drive level on slave 11 module motor A */ +#define SCMD_REG_S11B_DRIVE 0x37 /* Drive level on slave 11 module motor B */ +#define SCMD_REG_S12A_DRIVE 0x38 /* Drive level on slave 12 module motor A */ +#define SCMD_REG_S12B_DRIVE 0x39 /* Drive level on slave 12 module motor B */ +#define SCMD_REG_S13A_DRIVE 0x3A /* Drive level on slave 13 module motor A */ +#define SCMD_REG_S13B_DRIVE 0x3B /* Drive level on slave 13 module motor B */ +#define SCMD_REG_S14A_DRIVE 0x3C /* Drive level on slave 14 module motor A */ +#define SCMD_REG_S14B_DRIVE 0x3D /* Drive level on slave 14 module motor B */ +#define SCMD_REG_S15A_DRIVE 0x3E /* Drive level on slave 15 module motor A */ +#define SCMD_REG_S15B_DRIVE 0x3F /* Drive level on slave 15 module motor B */ +#define SCMD_REG_S16A_DRIVE 0x40 /* Drive level on slave 16 module motor A */ +#define SCMD_REG_S16B_DRIVE 0x41 /* Drive level on slave 16 module motor B */ +/* A hole in the register space */ +#define SCMD_REG_HOLE_1_LOW 0x42 /* A hole in the register space */ +#define SCMD_REG_HOLE_1_HIGH 0x4F +/* Remote inversion and bridging */ +#define SCMD_REG_INV_2_9 0x50 /* Invert the motors on the slave modules. + * Each bit is a motor. Bit 0 is slave 1 + * module, motor A. Bit 8 is slave 4 module, + * motor B. + */ +#define SCMD_REG_INV_10_17 0x51 /* Invert motors 10 - 17 */ +#define SCMD_REG_INV_18_25 0x52 /* Invert motors 18 - 25 */ +#define SCMD_REG_INV_26_33 0x53 /* Invert motors 26 - 33 */ +#define SCMD_REG_BRIDGE_SLV_L 0x54 /* Bridge slave module outputs. Bit 0 is slave 1 + * module. Bit 8 is slave 8 module. + */ +#define SCMD_REG_BRIDGE_SLV_H 0x55 /* Brige slave module outputs. Slave module 9 to + * 16 + */ +/* Another hole in the register space */ +#define SCMD_REG_HOLE_2_LOW 0x56 /* Another hole in the register space */ +#define SCMD_REG_PAGE_SELECT 0x6F /* Usused function to select the register space + * for use + */ +#define SCMD_REG_HOLE_2_HIGH 0x6F /* End of the second hole */ +/* System configuration registers. + * Most are passed to the slave modules. + */ +#define SCMD_REG_DRIVER_ENABLE 0x70 /* Enable / disable all motor drivers. + * Set to 0x01 to enable, 0x00 to disable. + */ +#define SCMD_DRIVER_ENABLE 0x01 /* Enable all motors */ +#define SCMD_DRIVER_DISABLE 0x00 /* Disable all motors */ +#define SCMD_REG_UPDATE_RATE 0x71 /* Update motors every UPDATE_RATE ms. + * Use 0x00 to require FORCE_UPDATE. + */ +#define SCMD_REG_FORCE_UPDATE 0x72 /* Set to 0x01 to force a update of the motors. + * Auto resets to 0x00. + */ +#define SCMD_REG_E_BUS_SPEED 0x73 /* Expansion bus speed: + * 0x00 - 50kHz + * 0x01 - 100kHz + * 0x02 - 400kHz + */ +#define SCMD_REG_MASTER_LOCK 0x74 /* Unlocked when set to 0x9B. Unlocks local and + * remote modules. + */ +#define SCMD_REG_USER_LOCK 0x75 /* Unlocked when set to 0x5C. Unlocks the user lock + on the local and remote modules. + */ +#define SCMD_REG_FSAFE_TIME 0x76 /* Program status, if set: + * B0 - Enumeration complete + * B1 - Device busy + * B2 - Remote module read in progress + * B3 - Remote module write in progress + * B4 - The state of enable pin U2.5 + */ +#define SCMD_REG_STATUS_1 0x77 /* Another way to get basic program status, if set: + * B0 - Enumeration complete + * B1 - Device busy + */ +#define SCMD_REG_CONTROL_1 0x78 /* Restart and re-enumeration control. If set: + * B0 - Restart module + * B1 - Re-enumerate, look for modules (self clears) + */ +#define SCMD_CONTROL_1_RESTART 0x01 /* Mask to perform a restart using CONTROL_1 */ +#define SCMD_CONTROL_1_REENUMERATE 0x02 /* Mask to perform a re-enumeration using + * CONTROL_1 + */ +/* Remote module I2C bus write / read window */ +#define SCMD_REG_REM_ADDR 0x79 /* The slave module I2C address. This starts at 0x50 */ +#define SCMD_REG_REM_OFFSET 0x7A /* Remote module I2C register for write / read */ +#define SCMD_REG_REM_DATA_WR 0x7B /* Data staged for write to remote module */ +#define SCMD_REG_REM_DATA_RD 0x7C /* Data returned from remote module */ +#define SCMD_REG_REM_WRITE 0x7D /* Write REM_DATA_WR to REM_OFFSET on remote module */ +#define SCMD_REG_REM_READ 0x7E /* Read from REM_OFFSET into REM_DATA_RD on remote + * module + */ + +#define SCMD_LAST_REG SCMD_REG_REM_READ /* The last register address on a module */ +#define SCMD_REG_SIZE 0x7F /* Size of the register space including the holes */ +#define SCMD_REMOTE_ADDR_LOW 0x50 /* The first remote I2C addreess */ +#define SCMD_REMOTE_ADDR_HIGH 0x5F /* The last remote I2C address */ +#define SCMD_HOLE_VALUE 0x55 /* Artifical value on read for a hole register */ +#define SCMD_IS_HOLE(r) \ +((r >= SCMD_REG_HOLE_1_LOW && r <= SCMD_REG_HOLE_1_HIGH) || \ + (r >= SCMD_REG_HOLE_2_LOW && r <= SCMD_REG_HOLE_2_HIGH)) + +#define SCMD_MASTER_LOCK_UNLOCKED 0x9B +#define SCMD_USER_LOCK_UNLOCKED 0x5C +#define SCMD_ANY_LOCK_LOCKED 0x00 + +#endif diff --git a/sys/dev/ic/scmdvar.h b/sys/dev/ic/scmdvar.h new file mode 100644 index 00000000000..e17c0ffa3a9 --- /dev/null +++ b/sys/dev/ic/scmdvar.h @@ -0,0 +1,44 @@ +/* $NetBSD: scmdvar.h,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. + */ + +#ifndef _DEV_SCMDVAR_H_ +#define _DEV_SCMDVAR_H_ + +struct scmd_sc { + int sc_scmddebug; + device_t sc_dev; + i2c_tag_t sc_tag; + i2c_addr_t sc_addr; + struct spi_handle *sc_sh; + kmutex_t sc_mutex; /* for reading the i2c or spi bus */ + kmutex_t sc_dying_mutex; /* for cleaning up */ + kmutex_t sc_condmutex; /* for waiting a long time */ + kcondvar_t sc_condvar; + kcondvar_t sc_cond_dying; /* interlock when cleaning up */ + struct sysctllog *sc_scmdlog; + uint8_t sc_topaddr; + bool sc_dying; + bool sc_opened; + void (*sc_func_attach)(struct scmd_sc *); + int (*sc_func_acquire_bus)(struct scmd_sc *); + void (*sc_func_release_bus)(struct scmd_sc *); + int (*sc_func_read_register)(struct scmd_sc *, uint8_t, uint8_t *); + int (*sc_func_write_register)(struct scmd_sc *, uint8_t, uint8_t); +}; + +#endif 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; + } +} |
