/* $NetBSD: scmd.c,v 1.3 2022/10/06 19:38:54 andvar Exp $ */ /* * Copyright (c) 2021 Brad Spencer * * 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 __KERNEL_RCSID(0, "$NetBSD: scmd.c,v 1.3 2022/10/06 19:38:54 andvar Exp $"); /* * Common driver for the Sparkfun Serial motor controller. * Calls out to specific frontends to move bits. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 assignment 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-enumeration, 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 = devsw_attach("scmd", NULL, &bmaj, &scmd_cdevsw, &cmaj); if (error) { aprint_error("%s: unable to attach devsw: %d\n", scmd_cd.cd_name, error); return error; } 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); devsw_detach(NULL, &scmd_cdevsw); } return error; #else return 0; #endif case MODULE_CMD_FINI: #ifdef _MODULE error = config_fini_component(cfdriver_ioconf_scmd, cfattach_ioconf_scmd, cfdata_ioconf_scmd); devsw_detach(NULL, &scmd_cdevsw); return error; #else return 0; #endif default: return ENOTTY; } }