root/sys/bus/gpio/gpio_intel/gpio_intel.c
/*
 * Copyright (c) 2016 The DragonFly Project.  All rights reserved.
 *
 * This code is derived from software contributed to The DragonFly Project
 * by Imre Vadász <imre@vdsz.com>
 *
 * 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.
 * 3. Neither the name of The DragonFly Project nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific, prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
 * COPYRIGHT HOLDERS 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.
 */
/*
 * Intel SoC gpio driver.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/errno.h>
#include <sys/lock.h>
#include <sys/bus.h>

#include <sys/rman.h>

#include "opt_acpi.h"
#include "acpi.h"
#include <dev/acpica/acpivar.h>

#include <bus/pci/pcivar.h>

#include "gpio_intel_var.h"

#include "gpio_if.h"

static int      gpio_intel_probe(device_t dev);
static int      gpio_intel_attach(device_t dev);
static int      gpio_intel_detach(device_t dev);
static int      gpio_intel_alloc_intr(device_t dev, u_int pin, int trigger,
                    int polarity, int termination, void **cookiep);
static void     gpio_intel_setup_intr(device_t dev, void *cookie, void *arg,
                    driver_intr_t *handler);
static void     gpio_intel_teardown_intr(device_t dev, void *cookie);
static void     gpio_intel_free_intr(device_t dev, void *cookie);
static int      gpio_intel_alloc_io_pin(device_t dev, u_int pin, int flags,
                    void **cookiep);
static void     gpio_intel_release_io_pin(device_t dev, void *cookie);
static int      gpio_intel_read_pin(device_t dev, void *cookie);
static void     gpio_intel_write_pin(device_t dev, void *cookie, int value);

static void     gpio_intel_intr(void *arg);
static int      gpio_intel_pin_exists(struct gpio_intel_softc *sc,
                    uint16_t pin);

static char *cherryview_ids[] = { "INT33FF", NULL };

static int
gpio_intel_probe(device_t dev)
{

        if (acpi_disabled("gpio_intel") ||
            ACPI_ID_PROBE(device_get_parent(dev), dev, cherryview_ids) == NULL)
                return (ENXIO);

        device_set_desc(dev, "Intel Cherry Trail GPIO Controller");

        return (BUS_PROBE_DEFAULT);
}

static int
gpio_intel_attach(device_t dev)
{
        struct gpio_intel_softc *sc = device_get_softc(dev);
        int error, i, rid;

        lockinit(&sc->lk, "gpio_intel", 0, LK_CANRECURSE);

        sc->dev = dev;

        if (ACPI_ID_PROBE(device_get_parent(dev), dev, cherryview_ids)
            != NULL) {
                error = gpio_cherryview_matchuid(sc);
                if (error) {
                        error = ENXIO;
                        goto err;
                }
        } else {
                error = ENXIO;
                goto err;
        }

        for (i = 0; i < NELEM(sc->intrmaps); i++) {
                sc->intrmaps[i].pin = -1;
        }
        for (i = 0; i < NELEM(sc->iomaps); i++) {
                sc->iomaps[i].pin = -1;
        }

        rid = 0;
        sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
            &rid, RF_ACTIVE);
        if (sc->mem_res == NULL) {
                device_printf(dev, "unable to map registers");
                error = ENXIO;
                goto err;
        }
        rid = 0;
        sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ,
            &rid, RF_ACTIVE);
        if (sc->irq_res == NULL) {
                device_printf(dev, "unable to map interrupt");
                error = ENXIO;
                goto err;
        }

        lockmgr(&sc->lk, LK_EXCLUSIVE);
        /* Activate the interrupt */
        error = bus_setup_intr(dev, sc->irq_res, INTR_MPSAFE,
            gpio_intel_intr, sc, &sc->intrhand, NULL);
        if (error)
                device_printf(dev, "Can't setup IRQ\n");

        /* power up the controller */
        pci_set_powerstate(dev, PCI_POWERSTATE_D0);

        sc->fns->init(sc);
        lockmgr(&sc->lk, LK_RELEASE);

        device_add_child(dev, "gpio_acpi", -1);
        bus_generic_attach(dev);

        return (0);

err:
        if (sc->irq_res) {
                bus_release_resource(dev, SYS_RES_IRQ,
                    rman_get_rid(sc->irq_res), sc->irq_res);
                sc->irq_res = NULL;
        }
        if (sc->mem_res) {
                bus_release_resource(dev, SYS_RES_MEMORY,
                    rman_get_rid(sc->mem_res), sc->mem_res);
                sc->mem_res = NULL;
        }
        return (error);
}

static int
gpio_intel_detach(device_t dev)
{
        struct gpio_intel_softc *sc = device_get_softc(dev);
        int error;

        error = bus_generic_detach(dev);
        if (error)
                return (error);

        device_delete_children(dev);

        bus_teardown_intr(dev, sc->irq_res, sc->intrhand);
        if (sc->irq_res) {
                bus_release_resource(dev, SYS_RES_IRQ,
                    rman_get_rid(sc->irq_res), sc->irq_res);
                sc->irq_res = NULL;
        }
        if (sc->mem_res) {
                bus_release_resource(dev, SYS_RES_MEMORY,
                    rman_get_rid(sc->mem_res), sc->mem_res);
                sc->mem_res = NULL;
        }
        lockuninit(&sc->lk);

        pci_set_powerstate(dev, PCI_POWERSTATE_D3);

        return 0;
}

/*
 * The trigger, polarity and termination parameters are only used for
 * sanity checking. The gpios should already be configured correctly by
 * the firmware.
 */
static int
gpio_intel_alloc_intr(device_t dev, u_int pin, int trigger, int polarity,
    int termination, void **cookiep)
{
        struct gpio_intel_softc *sc = device_get_softc(dev);
        struct pin_intr_map *map = NULL;
        int i, ret;

        if (cookiep == NULL)
                return (EINVAL);

        lockmgr(&sc->lk, LK_EXCLUSIVE);

        if (gpio_intel_pin_exists(sc, pin)) {
                /* Make sure this pin isn't mapped yet */
                for (i = 0; i < NELEM(sc->intrmaps); i++) {
                        if (sc->intrmaps[i].pin == pin) {
                                lockmgr(&sc->lk, LK_RELEASE);
                                return (EBUSY);
                        }
                }
                ret = sc->fns->map_intr(sc, pin, trigger, polarity,
                    termination);
                if (ret == 0) {
                        /* XXX map_intr should return the pin_intr_map */
                        for (i = 0; i < NELEM(sc->intrmaps); i++) {
                                if (sc->intrmaps[i].pin == pin)
                                        map = &sc->intrmaps[i];
                        }
                        if (map != NULL) {
                                *cookiep = map;
                                map->arg = NULL;
                                map->handler = NULL;
                        }
                }
        } else {
                device_printf(sc->dev, "%s: Invalid pin %d\n", __func__, pin);
                ret = ENOENT;
        }

        lockmgr(&sc->lk, LK_RELEASE);

        return (ret);
}

static void
gpio_intel_free_intr(device_t dev, void *cookie)
{
        struct gpio_intel_softc *sc = device_get_softc(dev);
        struct pin_intr_map *map = (struct pin_intr_map *)cookie;

        KKASSERT(gpio_intel_pin_exists(sc, map->pin));

        lockmgr(&sc->lk, LK_EXCLUSIVE);
        map->arg = NULL;
        map->handler = NULL;
        sc->fns->unmap_intr(sc, map);
        lockmgr(&sc->lk, LK_RELEASE);
}

static void
gpio_intel_setup_intr(device_t dev, void *cookie, void *arg,
    driver_intr_t *handler)
{
        struct gpio_intel_softc *sc = device_get_softc(dev);
        struct pin_intr_map *map = (struct pin_intr_map *)cookie;

        KKASSERT(gpio_intel_pin_exists(sc, map->pin));

        lockmgr(&sc->lk, LK_EXCLUSIVE);
        map->arg = arg;
        map->handler = handler;
        sc->fns->enable_intr(sc, map);
        lockmgr(&sc->lk, LK_RELEASE);
}

static void
gpio_intel_teardown_intr(device_t dev, void *cookie)
{
        struct gpio_intel_softc *sc = device_get_softc(dev);
        struct pin_intr_map *map = (struct pin_intr_map *)cookie;

        KKASSERT(gpio_intel_pin_exists(sc, map->pin));

        lockmgr(&sc->lk, LK_EXCLUSIVE);
        sc->fns->disable_intr(sc, map);
        map->arg = NULL;
        map->handler = NULL;
        lockmgr(&sc->lk, LK_RELEASE);
}

static int
gpio_intel_alloc_io_pin(device_t dev, u_int pin, int flags, void **cookiep)
{
        struct gpio_intel_softc *sc = device_get_softc(dev);
        struct pin_io_map *map = NULL;
        int i, ret;

        if (cookiep == NULL)
                return (EINVAL);

        if (!gpio_intel_pin_exists(sc, pin))
                return (ENOENT);

        lockmgr(&sc->lk, LK_EXCLUSIVE);

        for (i = 0; i < NELEM(sc->iomaps); i++) {
                if (sc->iomaps[i].pin == pin) {
                        lockmgr(&sc->lk, LK_RELEASE);
                        return (EBUSY);
                }
        }
        for (i = 0; i < NELEM(sc->iomaps); i++) {
                if (sc->iomaps[i].pin == -1) {
                        map = &sc->iomaps[i];
                        break;
                }
        }
        if (map == NULL) {
                ret = EBUSY;
        } else if (sc->fns->check_io_pin(sc, pin, flags)) {
                /*
                 * XXX It's possible that RX gets disabled when the interrupt
                 *     on this pin gets released.
                 */
                map->pin = pin;
                map->flags = flags;
                *cookiep = map;
                ret = 0;
        } else {
                ret = EBUSY;
        }

        lockmgr(&sc->lk, LK_RELEASE);

        return (ret);
}

static void
gpio_intel_release_io_pin(device_t dev, void *cookie)
{
        struct gpio_intel_softc *sc = device_get_softc(dev);
        struct pin_io_map *map = (struct pin_io_map *)cookie;

        lockmgr(&sc->lk, LK_EXCLUSIVE);
        map->pin = -1;
        map->flags = 0;
        lockmgr(&sc->lk, LK_RELEASE);
}

static int
gpio_intel_read_pin(device_t dev, void *cookie)
{
        struct gpio_intel_softc *sc = device_get_softc(dev);
        struct pin_io_map *map = (struct pin_io_map *)cookie;
        int val;

        KKASSERT(map->pin >= 0);
        KKASSERT(gpio_intel_pin_exists(sc, map->pin));

        lockmgr(&sc->lk, LK_EXCLUSIVE);
        val = sc->fns->read_pin(sc, map->pin);
        lockmgr(&sc->lk, LK_RELEASE);

        return (val);
}

static void
gpio_intel_write_pin(device_t dev, void *cookie, int value)
{
        struct gpio_intel_softc *sc = device_get_softc(dev);
        struct pin_io_map *map = (struct pin_io_map *)cookie;

        KKASSERT(map->pin >= 0);
        KKASSERT(gpio_intel_pin_exists(sc, map->pin));

        lockmgr(&sc->lk, LK_EXCLUSIVE);
        sc->fns->write_pin(sc, map->pin, value);
        lockmgr(&sc->lk, LK_RELEASE);
}

static void
gpio_intel_intr(void *arg)
{
        struct gpio_intel_softc *sc = (struct gpio_intel_softc *)arg;

        lockmgr(&sc->lk, LK_EXCLUSIVE);
        sc->fns->intr(arg);
        lockmgr(&sc->lk, LK_RELEASE);
}

static int
gpio_intel_pin_exists(struct gpio_intel_softc *sc, uint16_t pin)
{
        struct pinrange *r;

        for (r = sc->ranges; r->start != -1 && r->end != -1; r++) {
                if (r->start <= (int)pin && r->end >= (int)pin)
                        return (TRUE);
        }

        return (FALSE);
}

static device_method_t gpio_intel_methods[] = {
        /* Device interface */
        DEVMETHOD(device_probe, gpio_intel_probe),
        DEVMETHOD(device_attach, gpio_intel_attach),
        DEVMETHOD(device_detach, gpio_intel_detach),

        /* GPIO methods */
        DEVMETHOD(gpio_alloc_intr, gpio_intel_alloc_intr),
        DEVMETHOD(gpio_free_intr, gpio_intel_free_intr),
        DEVMETHOD(gpio_setup_intr, gpio_intel_setup_intr),
        DEVMETHOD(gpio_teardown_intr, gpio_intel_teardown_intr),
        DEVMETHOD(gpio_alloc_io_pin, gpio_intel_alloc_io_pin),
        DEVMETHOD(gpio_release_io_pin, gpio_intel_release_io_pin),
        DEVMETHOD(gpio_read_pin, gpio_intel_read_pin),
        DEVMETHOD(gpio_write_pin, gpio_intel_write_pin),

        DEVMETHOD_END
};

static driver_t gpio_intel_driver = {
        "gpio_intel",
        gpio_intel_methods,
        sizeof(struct gpio_intel_softc),
        .gpri = KOBJ_GPRI_ACPI+2
};

static devclass_t gpio_intel_devclass;

DRIVER_MODULE(gpio_intel, acpi, gpio_intel_driver, gpio_intel_devclass,
    NULL, NULL);
MODULE_DEPEND(gpio_intel, acpi, 1, 1, 1);
MODULE_DEPEND(gpio_intel, gpio_acpi, 1, 1, 1);
MODULE_VERSION(gpio_intel, 1);