/* $NetBSD: gpiopps.c,v 1.2 2018/06/01 13:42:14 thorpej Exp $ */ /* * Copyright (c) 2016 Brad Spencer * * 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 __KERNEL_RCSID(0, "$NetBSD: gpiopps.c,v 1.2 2018/06/01 13:42:14 thorpej Exp $"); /* * GPIO interface to the pps subsystem for ntp support. */ #include #include #include #include #include #include #include #include #include #include #include #define GPIOPPS_NPINS 2 struct gpiopps_softc { device_t sc_dev; void * sc_gpio; struct gpio_pinmap sc_map; int _map[GPIOPPS_NPINS]; struct { char sc_intrstr[128]; void * sc_ih; int sc_irqmode; } sc_intrs[GPIOPPS_NPINS]; int sc_assert_val; int sc_npins; struct pps_state sc_pps_state; bool sc_functional; bool sc_busy; }; #define GPIOPPS_FLAGS_ASSERT_NEG_EDGE 0x01 #define GPIOPPS_FLAGS_NO_DOUBLE_EDGE 0x02 static int gpiopps_match(device_t, cfdata_t, void *); static void gpiopps_attach(device_t, device_t, void *); static int gpiopps_detach(device_t, int); CFATTACH_DECL_NEW(gpiopps, sizeof(struct gpiopps_softc), gpiopps_match, gpiopps_attach, gpiopps_detach, NULL /*activate*/); extern struct cfdriver gpiopps_cd; static dev_type_open(gpioppsopen); static dev_type_close(gpioppsclose); static dev_type_ioctl(gpioppsioctl); const struct cdevsw gpiopps_cdevsw = { .d_open = gpioppsopen, .d_close = gpioppsclose, .d_read = noread, .d_write = nowrite, .d_ioctl = gpioppsioctl, .d_stop = nostop, .d_tty = notty, .d_poll = nopoll, .d_mmap = nommap, .d_kqfilter = nokqfilter, .d_discard = nodiscard, .d_flag = D_OTHER }; static int gpiopps_match(device_t parent, cfdata_t cf, void *aux) { struct gpio_attach_args *ga = aux; int bits; if (strcmp(ga->ga_dvname, cf->cf_name)) return (0); if (ga->ga_offset == -1) return (0); /* One or 2 pins (unspecified, assume 1) */ bits = gpio_npins(ga->ga_mask); if (bits > 2) return (0); return (1); } static void gpiopps_attach(device_t parent, device_t self, void *aux) { struct gpiopps_softc *sc = device_private(self); struct gpio_attach_args *ga = aux; int flags, intrcaps, npins; int assert_edge = GPIO_INTR_POS_EDGE; int clear_edge = GPIO_INTR_NEG_EDGE; int mask = ga->ga_mask; sc->sc_dev = self; sc->sc_assert_val = GPIO_PIN_HIGH; /* Map pins */ sc->sc_gpio = ga->ga_gpio; sc->sc_map.pm_map = sc->_map; /* Determine our pin configuation. */ npins = gpio_npins(mask); if (npins == 0) { npins = 1; mask = 0x1; } /* * Here's the different pin configurations we handle: * * 1 pin, single-edge capable pin -- interrupt on single-edge, * only trigger ASSERT signal. * * 1 pin, double-edge capable pin -- interrupt on double-edge, * trigger ASSERT and CLEAR signals, unless 0x2 is set in ga_flags, * in which case we degrade to ASSERT only. * * 2 pins -- pin #0 is ASSERT signal, pin #1 is CLEAR signal. * * If 0x1 is set in ga_flags, ASSERT is negative edge, otherwise * assert is positive edge. */ if (npins < 1 || npins > 2) { aprint_error(": invalid pin configuration\n"); return; } if (ga->ga_flags & GPIOPPS_FLAGS_ASSERT_NEG_EDGE) { assert_edge = GPIO_INTR_NEG_EDGE; clear_edge = GPIO_INTR_POS_EDGE; sc->sc_assert_val = GPIO_PIN_LOW; } if (gpio_pin_map(sc->sc_gpio, ga->ga_offset, mask, &sc->sc_map)) { aprint_error(": can't map pins\n"); return; } sc->sc_npins = npins; aprint_normal("\n"); if (sc->sc_npins == 2) { intrcaps = gpio_pin_intrcaps(sc->sc_gpio, &sc->sc_map, 0); if ((intrcaps & assert_edge) == 0) { aprint_error_dev(sc->sc_dev, "%s edge interrupt not supported for ASSERT\n", assert_edge == GPIO_INTR_POS_EDGE ? "positive" : "negative"); gpio_pin_unmap(sc->sc_gpio, &sc->sc_map); return; } sc->sc_intrs[0].sc_irqmode = assert_edge; if (!gpio_intr_str(sc->sc_gpio, &sc->sc_map, 0, sc->sc_intrs[0].sc_irqmode, sc->sc_intrs[0].sc_intrstr, sizeof(sc->sc_intrs[0].sc_intrstr))) { aprint_error_dev(self, "failed to decode ASSERT interrupt\n"); gpio_pin_unmap(sc->sc_gpio, &sc->sc_map); return; } flags = gpio_pin_get_conf(sc->sc_gpio, &sc->sc_map, 0); flags = (flags & ~(GPIO_PIN_OUTPUT|GPIO_PIN_INOUT)) | GPIO_PIN_INPUT; if (!gpio_pin_set_conf(sc->sc_gpio, &sc->sc_map, 0, flags)) { aprint_error_dev(sc->sc_dev, "ASSERT pin not capable of input\n"); gpio_pin_unmap(sc->sc_gpio, &sc->sc_map); return; } intrcaps = gpio_pin_intrcaps(sc->sc_gpio, &sc->sc_map, 1); if ((intrcaps & clear_edge) == 0) { aprint_error_dev(sc->sc_dev, "%s edge interrupt not supported for CLEAR\n", clear_edge == GPIO_INTR_POS_EDGE ? "positive" : "negative"); gpio_pin_unmap(sc->sc_gpio, &sc->sc_map); return; } sc->sc_intrs[1].sc_irqmode = clear_edge; if (!gpio_intr_str(sc->sc_gpio, &sc->sc_map, 1, sc->sc_intrs[1].sc_irqmode, sc->sc_intrs[1].sc_intrstr, sizeof(sc->sc_intrs[1].sc_intrstr))) { aprint_error_dev(self, "failed to decode CLEAR interrupt\n"); gpio_pin_unmap(sc->sc_gpio, &sc->sc_map); return; } flags = gpio_pin_get_conf(sc->sc_gpio, &sc->sc_map, 1); flags = (flags & ~(GPIO_PIN_OUTPUT|GPIO_PIN_INOUT)) | GPIO_PIN_INPUT; if (!gpio_pin_set_conf(sc->sc_gpio, &sc->sc_map, 1, flags)) { aprint_error_dev(sc->sc_dev, "CLEAR pin not capable of input\n"); gpio_pin_unmap(sc->sc_gpio, &sc->sc_map); return; } aprint_normal_dev(self, "ASSERT interrupting on %s\n", sc->sc_intrs[0].sc_intrstr); aprint_normal_dev(self, "CLEAR interrupting on %s\n", sc->sc_intrs[1].sc_intrstr); } else { intrcaps = gpio_pin_intrcaps(sc->sc_gpio, &sc->sc_map, 0); bool double_edge = false; if ((intrcaps & GPIO_INTR_DOUBLE_EDGE) && (ga->ga_flags & GPIOPPS_FLAGS_NO_DOUBLE_EDGE) == 0) { sc->sc_intrs[0].sc_irqmode = GPIO_INTR_DOUBLE_EDGE; double_edge = true; } else if (intrcaps & assert_edge) { sc->sc_intrs[0].sc_irqmode = assert_edge; } else { aprint_error_dev(sc->sc_dev, "%s edge interrupt not supported for ASSERT\n", assert_edge == GPIO_INTR_POS_EDGE ? "positive" : "negative"); gpio_pin_unmap(sc->sc_gpio, &sc->sc_map); return; } if (!gpio_intr_str(sc->sc_gpio, &sc->sc_map, 0, sc->sc_intrs[0].sc_irqmode, sc->sc_intrs[0].sc_intrstr, sizeof(sc->sc_intrs[0].sc_intrstr))) { aprint_error_dev(self, "failed to decode interrupt\n"); gpio_pin_unmap(sc->sc_gpio, &sc->sc_map); return; } flags = gpio_pin_get_conf(sc->sc_gpio, &sc->sc_map, 0); flags = (flags & ~(GPIO_PIN_OUTPUT|GPIO_PIN_INOUT)) | GPIO_PIN_INPUT; if (!gpio_pin_set_conf(sc->sc_gpio, &sc->sc_map, 0, flags)) { aprint_error_dev(sc->sc_dev, "ASSERT%s pin not capable of input\n", double_edge ? "+CLEAR" : ""); gpio_pin_unmap(sc->sc_gpio, &sc->sc_map); return; } aprint_normal_dev(self, "ASSERT%s interrupting on %s\n", double_edge ? "+CLEAR" : "", sc->sc_intrs[0].sc_intrstr); } /* Interrupt will be registered when device is opened for use. */ sc->sc_functional = true; } static int gpiopps_assert_intr(void *arg) { struct gpiopps_softc *sc = arg; mutex_spin_enter(&timecounter_lock); pps_capture(&sc->sc_pps_state); pps_event(&sc->sc_pps_state, PPS_CAPTUREASSERT); mutex_spin_exit(&timecounter_lock); return (1); } static int gpiopps_clear_intr(void *arg) { struct gpiopps_softc *sc = arg; mutex_spin_enter(&timecounter_lock); pps_capture(&sc->sc_pps_state); pps_event(&sc->sc_pps_state, PPS_CAPTURECLEAR); mutex_spin_exit(&timecounter_lock); return (1); } static int gpiopps_double_intr(void *arg) { struct gpiopps_softc *sc = arg; int val = gpio_pin_read(sc->sc_gpio, &sc->sc_map, 0); if (val == sc->sc_assert_val) return (gpiopps_assert_intr(arg)); return (gpiopps_clear_intr(arg)); } static void gpiopps_disable_interrupts(struct gpiopps_softc *sc) { int i; for (i = 0; i < GPIOPPS_NPINS; i++) { if (sc->sc_intrs[i].sc_ih != NULL) { gpio_intr_disestablish(sc->sc_gpio, sc->sc_intrs[i].sc_ih); sc->sc_intrs[i].sc_ih = NULL; } } } static void gpiopps_reset(struct gpiopps_softc *sc) { mutex_spin_enter(&timecounter_lock); sc->sc_pps_state.ppsparam.mode = 0; sc->sc_busy = false; mutex_spin_exit(&timecounter_lock); } static int gpiopps_detach(device_t self, int flags) { struct gpiopps_softc *sc = device_private(self); if (!sc->sc_functional) { /* Attach failed, no work to do; resources already released. */ return (0); } if (sc->sc_busy) return (EBUSY); /* * Clear the handler and disable the interrupt. * NOTE: This should never be true, because we * register the interrupt handler at open, and * remove it at close. We keep this as a backstop. */ gpiopps_disable_interrupts(sc); /* Release the pin. */ gpio_pin_unmap(sc->sc_gpio, &sc->sc_map); return (0); } static int gpioppsopen(dev_t dev, int flags, int fmt, struct lwp *l) { struct gpiopps_softc *sc; int error = EIO; sc = device_lookup_private(&gpiopps_cd, minor(dev)); if (sc == NULL) return (ENXIO); if (!sc->sc_functional) return (EIO); mutex_spin_enter(&timecounter_lock); if (sc->sc_busy) { mutex_spin_exit(&timecounter_lock); return (0); } memset(&sc->sc_pps_state, 0, sizeof(sc->sc_pps_state)); sc->sc_pps_state.ppscap = PPS_CAPTUREASSERT; if (sc->sc_npins == 2 || sc->sc_intrs[0].sc_irqmode == GPIO_INTR_DOUBLE_EDGE) sc->sc_pps_state.ppscap |= PPS_CAPTURECLEAR; pps_init(&sc->sc_pps_state); sc->sc_busy = true; mutex_spin_exit(&timecounter_lock); if (sc->sc_npins == 2) { sc->sc_intrs[0].sc_ih = gpio_intr_establish(sc->sc_gpio, &sc->sc_map, 0, IPL_VM, sc->sc_intrs[0].sc_irqmode | GPIO_INTR_MPSAFE, gpiopps_assert_intr, sc); if (sc->sc_intrs[0].sc_ih == NULL) { aprint_error_dev(sc->sc_dev, "unable to establish ASSERT interrupt on %s\n", sc->sc_intrs[0].sc_intrstr); goto out; } sc->sc_intrs[1].sc_ih = gpio_intr_establish(sc->sc_gpio, &sc->sc_map, 1, IPL_VM, sc->sc_intrs[1].sc_irqmode | GPIO_INTR_MPSAFE, gpiopps_clear_intr, sc); if (sc->sc_intrs[1].sc_ih == NULL) { aprint_error_dev(sc->sc_dev, "unable to establish CLEAR interrupt on %s\n", sc->sc_intrs[0].sc_intrstr); gpio_intr_disestablish(sc->sc_gpio, sc->sc_intrs[0].sc_ih); goto out; } } else { bool double_edge = sc->sc_intrs[0].sc_irqmode == GPIO_INTR_DOUBLE_EDGE; sc->sc_intrs[0].sc_ih = gpio_intr_establish(sc->sc_gpio, &sc->sc_map, 0, IPL_VM, sc->sc_intrs[0].sc_irqmode | GPIO_INTR_MPSAFE, double_edge ? gpiopps_double_intr : gpiopps_assert_intr, sc); if (sc->sc_intrs[0].sc_ih == NULL) { aprint_error_dev(sc->sc_dev, "unable to establish ASSERT%s interrupt on %s\n", double_edge ? "+CLEAR" : "", sc->sc_intrs[0].sc_intrstr); goto out; } } error = 0; out: if (error) { gpiopps_disable_interrupts(sc); gpiopps_reset(sc); } return (error); } static int gpioppsclose(dev_t dev, int flags, int fmt, struct lwp *l) { struct gpiopps_softc *sc; sc = device_lookup_private(&gpiopps_cd, minor(dev)); gpiopps_disable_interrupts(sc); gpiopps_reset(sc); return (0); } static int gpioppsioctl(dev_t dev, u_long cmd, void *data, int flags, struct lwp *l) { struct gpiopps_softc *sc; int error = 0; sc = device_lookup_private(&gpiopps_cd, minor(dev)); switch (cmd) { case PPS_IOC_CREATE: case PPS_IOC_DESTROY: case PPS_IOC_GETPARAMS: case PPS_IOC_SETPARAMS: case PPS_IOC_GETCAP: case PPS_IOC_FETCH: case PPS_IOC_KCBIND: mutex_spin_enter(&timecounter_lock); error = pps_ioctl(cmd, data, &sc->sc_pps_state); mutex_spin_exit(&timecounter_lock); break; default: error = EPASSTHROUGH; } return (error); } MODULE(MODULE_CLASS_DRIVER, gpiopps, NULL); #ifdef _MODULE #include "ioconf.c" #endif static int gpiopps_modcmd(modcmd_t cmd, void *opaque) { int error = 0; #ifdef _MODULE int bmaj = -1, cmaj = -1; #endif switch (cmd) { case MODULE_CMD_INIT: #ifdef _MODULE error = config_init_component(cfdriver_ioconf_gpiopps, cfattach_ioconf_gpiopps, cfdata_ioconf_gpiopps); if (error) { aprint_error("%s: unable to init component\n", gpiopps_cd.cd_name); return (error); } error = devsw_attach("gpiopps", NULL, &bmaj, &gpiopps_cdevsw, &cmaj); if (error) { aprint_error("%s: unable to attach devsw\n", gpiopps_cd.cd_name); config_fini_component(cfdriver_ioconf_gpiopps, cfattach_ioconf_gpiopps, cfdata_ioconf_gpiopps); } #endif return (error); case MODULE_CMD_FINI: #ifdef _MODULE devsw_detach(NULL, &gpiopps_cdevsw); config_fini_component(cfdriver_ioconf_gpiopps, cfattach_ioconf_gpiopps, cfdata_ioconf_gpiopps); #endif return (0); default: return (ENOTTY); } }