/* $NetBSD: gpiopps.c,v 1.2 2018/06/01 13:42:14 thorpej Exp $ */
/*
* Copyright (c) 2016 Brad Spencer <brad@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.2 2018/06/01 13:42:14 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, 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);
}
}