/*- * Copyright (c) 2020, 2021 Soren Schmidt * All rights reserved. * * 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 __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpiobus_if.h" struct rk_gpiokeys_softc { device_t dev; char name[16]; int irq_rid; struct resource *irq_res; void *ihandle; struct mtx mtx; gpio_pin_t pin; struct callout callout; }; static void rk_gpiokeys_lidswitch(void *arg) { struct rk_gpiokeys_softc *sc = arg; struct gpiobus_pin lcdpin; static bool on = 1; bool active; mtx_lock(&sc->mtx); /* * The LCD power control is on pin "lcdvcc_en_pin" in the DTS. * Instead of going through all the hoopla to get at it from the DTS, * cheat and use the fact it's on the same GPIO block as the lid switch. */ lcdpin.dev = sc->pin->dev; lcdpin.flags = GPIO_PIN_OUTPUT; lcdpin.pin = 0; gpio_pin_is_active(sc->pin, &active); /* * The Lid switch on my unit is for some reason not able to generate * an interrupt when the Lid is opened. * Instead I poll the status once a second to recover status. */ if (active) { if (on) gpio_pin_set_active(&lcdpin, (on = false)); callout_reset(&sc->callout, hz, rk_gpiokeys_lidswitch, sc); } else gpio_pin_set_active(&lcdpin, (on = true)); mtx_unlock(&sc->mtx); } static void rk_gpiokeys_powerkey(void *arg) { static struct bintime pdwn_push, pdwn_release; struct rk_gpiokeys_softc *sc = arg; bool active; mtx_lock(&sc->mtx); gpio_pin_is_active(sc->pin, &active); if (active) binuptime(&pdwn_push); else { binuptime(&pdwn_release); bintime_sub(&pdwn_release, &pdwn_push); if (pdwn_release.sec >= 1) shutdown_nice(RB_HALT | RB_POWEROFF); } mtx_unlock(&sc->mtx); } static int rk_gpiokeys_probe(device_t dev) { struct rk_gpiokeys_softc *sc = device_get_softc(dev); phandle_t node; if (!ofw_bus_is_compatible(dev, "gpio-keys")) return ENXIO; if ((node = ofw_bus_get_node(dev)) == -1) return ENXIO; if (OF_getprop(node, "label", sc->name, sizeof(sc->name)) == -1) OF_getprop(node, "name", sc->name, sizeof(sc->name)); if (sc->name[0]) device_set_desc(dev, sc->name); return 0; } static int rk_gpiokeys_attach(device_t dev) { struct rk_gpiokeys_softc *sc = device_get_softc(dev); void *ithread; phandle_t child = 0; phandle_t node; pcell_t prop; int err; if ((node = ofw_bus_get_node(dev)) == -1) return ENXIO; if (!((child = OF_child(node)) && OF_hasprop(child, "gpios"))) return ENXIO; if (!OF_getprop(child, "linux,code", &prop, sizeof(prop))) { device_printf(dev, "<%s> no linux,code property\n", sc->name); return ENXIO; } if ((err = gpio_pin_get_by_ofw_idx(dev, child, 0, &sc->pin))) { device_printf(dev, "<%s> failed to map pin\n", sc->name); return ENXIO; } mtx_init(&sc->mtx, "rk_gpiokey", "rk_gpiokey", MTX_DEF); switch (fdt32_to_cpu(prop)) { case SW_LID: callout_init_mtx(&sc->callout, &sc->mtx, 0); ithread = rk_gpiokeys_lidswitch; break; case KEY_POWER: ithread = rk_gpiokeys_powerkey; break; default: device_printf(dev, "<%s> unknown key/button\n", sc->name); gpio_pin_release(sc->pin); return ENXIO; } if (!(sc->irq_res = gpio_alloc_intr_resource(dev, &sc->irq_rid, RF_ACTIVE, sc->pin, GPIO_INTR_EDGE_FALLING))) { device_printf(dev, "Cannot allocate interrupt\n"); gpio_pin_release(sc->pin); return ENXIO; } if (bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE, NULL, ithread, sc, &sc->ihandle)) { device_printf(dev, "Unable to setup the irq handler\n"); bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid,sc->irq_res); gpio_pin_release(sc->pin); return ENXIO; } return 0; } static int rk_gpiokeys_detach(device_t dev) { struct rk_gpiokeys_softc *sc = device_get_softc(dev); mtx_lock(&sc->mtx); if (sc->ihandle) bus_teardown_intr(dev, sc->irq_res, sc->ihandle); if (sc->irq_res) bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid,sc->irq_res); if (callout_pending(&sc->callout)) callout_drain(&sc->callout); if (sc->pin) gpio_pin_release(sc->pin); mtx_unlock(&sc->mtx); mtx_destroy(&sc->mtx); return 0; } static devclass_t rk_gpiokeys_devclass; static device_method_t rk_gpiokeys_methods[] = { /* Device interface */ DEVMETHOD(device_probe, rk_gpiokeys_probe), DEVMETHOD(device_attach, rk_gpiokeys_attach), DEVMETHOD(device_detach, rk_gpiokeys_detach), DEVMETHOD_END }; static driver_t rk_gpiokeys_driver = { "rk_gpiokeys", rk_gpiokeys_methods, sizeof(struct rk_gpiokeys_softc), }; DRIVER_MODULE(rk_gpiokeys, simplebus, rk_gpiokeys_driver, rk_gpiokeys_devclass, 0, 0); MODULE_VERSION(rk_gpiokeys, 1);