Module Name: src Committed By: thorpej Date: Sun May 20 14:08:33 UTC 2018
Modified Files: src/distrib/sets/lists/man: mi src/distrib/sets/lists/modules: mi src/etc: MAKEDEV.tmpl src/share/man/man4: Makefile src/share/man/man8: MAKEDEV.8 src/sys/conf: majors src/sys/dev/gpio: files.gpio src/sys/modules: Makefile Added Files: src/sys/dev/gpio: gpiopps.c Log Message: Add a 1PPS-over-GPIO driver, originally by Brad Spencer, with changes by me to adapt to the new GPIO interrupt interface and support a wider variety of GPIO pin configuations. PR kern/51676 To generate a diff of this commit: cvs rdiff -u -r1.1587 -r1.1588 src/distrib/sets/lists/man/mi cvs rdiff -u -r1.115 -r1.116 src/distrib/sets/lists/modules/mi cvs rdiff -u -r1.189 -r1.190 src/etc/MAKEDEV.tmpl cvs rdiff -u -r1.653 -r1.654 src/share/man/man4/Makefile cvs rdiff -u -r1.47 -r1.48 src/share/man/man8/MAKEDEV.8 cvs rdiff -u -r1.78 -r1.79 src/sys/conf/majors cvs rdiff -u -r1.15 -r1.16 src/sys/dev/gpio/files.gpio cvs rdiff -u -r0 -r1.1 src/sys/dev/gpio/gpiopps.c cvs rdiff -u -r1.203 -r1.204 src/sys/modules/Makefile Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/distrib/sets/lists/man/mi diff -u src/distrib/sets/lists/man/mi:1.1587 src/distrib/sets/lists/man/mi:1.1588 --- src/distrib/sets/lists/man/mi:1.1587 Sat May 19 14:15:39 2018 +++ src/distrib/sets/lists/man/mi Sun May 20 14:08:32 2018 @@ -1,4 +1,4 @@ -# $NetBSD: mi,v 1.1587 2018/05/19 14:15:39 thorpej Exp $ +# $NetBSD: mi,v 1.1588 2018/05/20 14:08:32 thorpej Exp $ # # Note: don't delete entries from here - mark them as "obsolete" instead. # @@ -1124,6 +1124,7 @@ ./usr/share/man/cat4/gpiolock.0 man-sys-catman .cat ./usr/share/man/cat4/gpioow.0 man-sys-catman .cat ./usr/share/man/cat4/gpioirq.0 man-sys-catman .cat +./usr/share/man/cat4/gpiopps.0 man-sys-catman .cat ./usr/share/man/cat4/gpiopwm.0 man-sys-catman .cat ./usr/share/man/cat4/gpiosim.0 man-sys-catman .cat ./usr/share/man/cat4/gre.0 man-sys-catman .cat @@ -4265,6 +4266,7 @@ ./usr/share/man/html4/gpiolock.html man-sys-htmlman html ./usr/share/man/html4/gpioow.html man-sys-htmlman html ./usr/share/man/html4/gpioirq.html man-sys-htmlman html +./usr/share/man/html4/gpiopps.html man-sys-htmlman html ./usr/share/man/html4/gpiopwm.html man-sys-htmlman html ./usr/share/man/html4/gpiosim.html man-sys-htmlman html ./usr/share/man/html4/gre.html man-sys-htmlman html @@ -7180,6 +7182,7 @@ ./usr/share/man/man4/gpiolock.4 man-sys-man .man ./usr/share/man/man4/gpioow.4 man-sys-man .man ./usr/share/man/man4/gpioirq.4 man-sys-man .man +./usr/share/man/man4/gpiopps.4 man-sys-man .man ./usr/share/man/man4/gpiopwm.4 man-sys-man .man ./usr/share/man/man4/gpiosim.4 man-sys-man .man ./usr/share/man/man4/gre.4 man-sys-man .man Index: src/distrib/sets/lists/modules/mi diff -u src/distrib/sets/lists/modules/mi:1.115 src/distrib/sets/lists/modules/mi:1.116 --- src/distrib/sets/lists/modules/mi:1.115 Sat May 19 14:15:39 2018 +++ src/distrib/sets/lists/modules/mi Sun May 20 14:08:33 2018 @@ -1,4 +1,4 @@ -# $NetBSD: mi,v 1.115 2018/05/19 14:15:39 thorpej Exp $ +# $NetBSD: mi,v 1.116 2018/05/20 14:08:33 thorpej Exp $ # # Note: don't delete entries from here - mark them as "obsolete" instead. # @@ -136,6 +136,8 @@ ./@MODULEDIR@/gpioirq/gpioirq.kmod base-kernel-modules kmod ./@MODULEDIR@/gpioow base-kernel-modules kmod ./@MODULEDIR@/gpioow/gpioow.kmod base-kernel-modules kmod +./@MODULEDIR@/gpiopps base-kernel-modules kmod +./@MODULEDIR@/gpiopps/gpiopps.kmod base-kernel-modules kmod ./@MODULEDIR@/gpiosim base-kernel-modules kmod ./@MODULEDIR@/gpiosim/gpiosim.kmod base-kernel-modules kmod ./@MODULEDIR@/hfs base-kernel-modules kmod Index: src/etc/MAKEDEV.tmpl diff -u src/etc/MAKEDEV.tmpl:1.189 src/etc/MAKEDEV.tmpl:1.190 --- src/etc/MAKEDEV.tmpl:1.189 Tue Jan 9 03:31:14 2018 +++ src/etc/MAKEDEV.tmpl Sun May 20 14:08:33 2018 @@ -1,5 +1,5 @@ #!/bin/sh - -# $NetBSD: MAKEDEV.tmpl,v 1.189 2018/01/09 03:31:14 christos Exp $ +# $NetBSD: MAKEDEV.tmpl,v 1.190 2018/05/20 14:08:33 thorpej Exp $ # # Copyright (c) 2003,2007,2008 The NetBSD Foundation, Inc. # All rights reserved. @@ -242,6 +242,7 @@ # dtv* Digital TV interface # fb* PMAX generic framebuffer pseudo-device # fd file descriptors +# gpiopps* 1PPS signals on GPIO pins # grf* graphics frame buffer device # hdaudio* High Definition audio control device # hdmicec* HDMI CEC devices @@ -829,6 +830,7 @@ all) makedev atabus0 atabus1 atabus2 atabus3 atabus4 atabus5 atabus6 atabus7 makedev tap tap0 tap1 tap2 tap3 makedev gpio gpio0 gpio1 gpio2 gpio3 gpio4 gpio5 gpio6 gpio7 + makedev gpiopps0 makedev pad pad0 pad1 pad2 pad3 makedev bthub makedev putter @@ -866,6 +868,11 @@ gpio) lndev gpio0 gpio ;; +gpiopps) + makedev gpiopps0 + lndev gpiopps0 gpiopps + ;; + lua) makedev lua0 lndev lua0 lua @@ -1535,6 +1542,11 @@ gpio[0-9]*) mkdev gpio$unit c %gpio_chr% $unit 664 $g_gpio ;; +gpiopps[0-9]*) + unit=${i#gpiopps} + mkdev gpiopps$unit c %gpiopps_chr% $unit 664 $g_gpio + ;; + lua[0-9]*) unit=${i#lua} mkdev lua$unit c %lua_chr% $unit 664 Index: src/share/man/man4/Makefile diff -u src/share/man/man4/Makefile:1.653 src/share/man/man4/Makefile:1.654 --- src/share/man/man4/Makefile:1.653 Sat May 19 14:15:39 2018 +++ src/share/man/man4/Makefile Sun May 20 14:08:33 2018 @@ -1,4 +1,4 @@ -# $NetBSD: Makefile,v 1.653 2018/05/19 14:15:39 thorpej Exp $ +# $NetBSD: Makefile,v 1.654 2018/05/20 14:08:33 thorpej Exp $ # @(#)Makefile 8.1 (Berkeley) 6/18/93 MAN= aac.4 ac97.4 acardide.4 aceride.4 acphy.4 \ @@ -26,7 +26,7 @@ MAN= aac.4 ac97.4 acardide.4 aceride.4 a fast_ipsec.4 fd.4 filemon.4 finsio.4 flash.4 fpa.4 fms.4 fss.4 \ fujbp.4 full.4 fxp.4 \ gcscaudio.4 gem.4 genfb.4 gentbi.4 geodeide.4 \ - glxtphy.4 gpib.4 gpio.4 gpioirq.4 gpiolock.4 gpiopwm.4 \ + glxtphy.4 gpib.4 gpio.4 gpioirq.4 gpiolock.4 gpiopps.4 gpiopwm.4 \ gpiosim.4 gre.4 gphyter.4 gsip.4 \ hdaudio.4 hifn.4 hme.4 hpacel.4 hpqlb.4 hptide.4 hythygtemp.4 \ ibmcd.4 ibmhawk.4 ichsmb.4 icmp.4 icp.4 icsphy.4 iee.4 ieee80211.4 \ Index: src/share/man/man8/MAKEDEV.8 diff -u src/share/man/man8/MAKEDEV.8:1.47 src/share/man/man8/MAKEDEV.8:1.48 --- src/share/man/man8/MAKEDEV.8:1.47 Tue Jan 23 19:14:09 2018 +++ src/share/man/man8/MAKEDEV.8 Sun May 20 14:08:33 2018 @@ -6,7 +6,7 @@ .\" *** DO NOT EDIT - any changes will be lost!!! .\" *** ------------------------------------------------------------------ .\" -.\" $NetBSD: MAKEDEV.8,v 1.47 2018/01/23 19:14:09 sevan Exp $ +.\" $NetBSD: MAKEDEV.8,v 1.48 2018/05/20 14:08:33 thorpej Exp $ .\" .\" Copyright (c) 2001, 2003, 2007, 2008 The NetBSD Foundation, Inc. .\" All rights reserved. @@ -35,7 +35,7 @@ .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" -.Dd January 23, 2018 +.Dd May 11, 2018 .Dt MAKEDEV 8 .Os .Sh NAME @@ -736,6 +736,9 @@ Digital TV interface, see PMAX generic framebuffer pseudo-device . It Ar fd File descriptors +. It Ar gpiopps# +1PPS signals on GPIO pins, see +.Xr \&gpiopps 4 . It Ar grf# Graphics frame buffer device, see .Xr \&amiga/grf 4 Index: src/sys/conf/majors diff -u src/sys/conf/majors:1.78 src/sys/conf/majors:1.79 --- src/sys/conf/majors:1.78 Tue Jan 9 03:31:12 2018 +++ src/sys/conf/majors Sun May 20 14:08:33 2018 @@ -1,4 +1,4 @@ -# $NetBSD: majors,v 1.78 2018/01/09 03:31:12 christos Exp $ +# $NetBSD: majors,v 1.79 2018/05/20 14:08:33 thorpej Exp $ # # Device majors for Machine-Independent drivers. # @@ -77,3 +77,4 @@ device-major hdmicec char 340 hdmi device-major nvme char 341 nvme device-major qemufwcfg char 342 qemufwcfg device-major autofs char 343 autofs +device-major gpiopps char 344 gpiopps Index: src/sys/dev/gpio/files.gpio diff -u src/sys/dev/gpio/files.gpio:1.15 src/sys/dev/gpio/files.gpio:1.16 --- src/sys/dev/gpio/files.gpio:1.15 Sat May 19 14:15:39 2018 +++ src/sys/dev/gpio/files.gpio Sun May 20 14:08:33 2018 @@ -1,4 +1,4 @@ -# $NetBSD: files.gpio,v 1.15 2018/05/19 14:15:39 thorpej Exp $ +# $NetBSD: files.gpio,v 1.16 2018/05/20 14:08:33 thorpej Exp $ define gpio {[offset = -1], [mask = 0], [flag = 0]} @@ -44,3 +44,8 @@ file dev/gpio/gpiobutton.c gpiobutton device gpioirq: gpiobus attach gpioirq at gpio file dev/gpio/gpioirq.c gpioirq + +# GPIO PPS +device gpiopps: gpiobus +attach gpiopps at gpio +file dev/gpio/gpiopps.c gpiopps Index: src/sys/modules/Makefile diff -u src/sys/modules/Makefile:1.203 src/sys/modules/Makefile:1.204 --- src/sys/modules/Makefile:1.203 Sat May 19 14:15:40 2018 +++ src/sys/modules/Makefile Sun May 20 14:08:33 2018 @@ -1,4 +1,4 @@ -# $NetBSD: Makefile,v 1.203 2018/05/19 14:15:40 thorpej Exp $ +# $NetBSD: Makefile,v 1.204 2018/05/20 14:08:33 thorpej Exp $ .include <bsd.own.mk> @@ -48,6 +48,7 @@ SUBDIR+= gpioiic SUBDIR+= gpioow SUBDIR+= gpiosim SUBDIR+= gpioirq +SUBDIR+= gpiopps SUBDIR+= hfs SUBDIR+= hythygtemp SUBDIR+= si70xxtemp Added files: Index: src/sys/dev/gpio/gpiopps.c diff -u /dev/null src/sys/dev/gpio/gpiopps.c:1.1 --- /dev/null Sun May 20 14:08:33 2018 +++ src/sys/dev/gpio/gpiopps.c Sun May 20 14:08:33 2018 @@ -0,0 +1,540 @@ +/* $NetBSD: gpiopps.c,v 1.1 2018/05/20 14:08:33 thorpej Exp $ */ + +/* + * Copyright (c) 2016 Brad Spencer <b...@anduin.eldar.org> + * + * 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 <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: gpiopps.c,v 1.1 2018/05/20 14:08:33 thorpej Exp $"); + +/* + * GPIO interface to the pps subsystem for ntp support. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bitops.h> +#include <sys/device.h> +#include <sys/module.h> +#include <sys/conf.h> +#include <sys/proc.h> +#include <sys/ioctl.h> +#include <sys/timepps.h> + +#include <sys/gpio.h> +#include <dev/gpio/gpiovar.h> + +#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, 0, + sc->sc_intrs[0].sc_irqmode, + sc->sc_intrs[0].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); + } +}