/*- * Copyright (c) 2016 Jared McNeill * 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 ``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 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. * * $FreeBSD$ */ /* * X-Powers AXP813/818 PMU for Allwinner SoCs */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" #include "gpio_if.h" #define AXP_ICTYPE 0x03 #define AXP_POWERBAT 0x32 #define AXP_POWERBAT_SHUTDOWN (1 << 7) #define AXP_IRQEN1 0x40 #define AXP_IRQEN2 0x41 #define AXP_IRQEN3 0x42 #define AXP_IRQEN4 0x43 #define AXP_IRQEN5 0x44 #define AXP_IRQEN5_POKSIRQ (1 << 4) #define AXP_IRQEN6 0x45 #define AXP_IRQSTAT5 0x4c #define AXP_IRQSTAT5_POKSIRQ (1 << 4) #define AXP_GPIO0_CTRL 0x90 #define AXP_GPIO1_CTRL 0x92 #define AXP_GPIO_FUNC (0x7 << 0) #define AXP_GPIO_FUNC_SHIFT 0 #define AXP_GPIO_FUNC_DRVLO 0 #define AXP_GPIO_FUNC_DRVHI 1 #define AXP_GPIO_FUNC_INPUT 2 #define AXP_GPIO_SIGBIT 0x94 #define AXP_GPIO_PD 0x97 static const struct { const char *name; uint8_t ctrl_reg; } axp81x_pins[] = { { "GPIO0", AXP_GPIO0_CTRL }, { "GPIO1", AXP_GPIO1_CTRL }, }; static struct ofw_compat_data compat_data[] = { { "x-powers,axp813", 1 }, { "x-powers,axp818", 1 }, { NULL, 0 } }; static struct resource_spec axp81x_spec[] = { { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0 } }; struct axp81x_softc { struct resource *res; uint16_t addr; void *ih; device_t gpiodev; struct mtx mtx; int busy; }; #define AXP_LOCK(sc) mtx_lock(&(sc)->mtx) #define AXP_UNLOCK(sc) mtx_unlock(&(sc)->mtx) static int axp81x_read(device_t dev, uint8_t reg, uint8_t *data, uint8_t size) { struct axp81x_softc *sc; struct iic_msg msg[2]; sc = device_get_softc(dev); msg[0].slave = sc->addr; msg[0].flags = IIC_M_WR; msg[0].len = 1; msg[0].buf = ® msg[1].slave = sc->addr; msg[1].flags = IIC_M_RD; msg[1].len = size; msg[1].buf = data; return (iicbus_transfer(dev, msg, 2)); } static int axp81x_write(device_t dev, uint8_t reg, uint8_t val) { struct axp81x_softc *sc; struct iic_msg msg[2]; sc = device_get_softc(dev); msg[0].slave = sc->addr; msg[0].flags = IIC_M_WR; msg[0].len = 1; msg[0].buf = ® msg[1].slave = sc->addr; msg[1].flags = IIC_M_WR; msg[1].len = 1; msg[1].buf = &val; return (iicbus_transfer(dev, msg, 2)); } static void axp81x_shutdown(void *devp, int howto) { device_t dev; if ((howto & RB_POWEROFF) == 0) return; dev = devp; if (bootverbose) device_printf(dev, "Shutdown AXP81x\n"); axp81x_write(dev, AXP_POWERBAT, AXP_POWERBAT_SHUTDOWN); } static void axp81x_intr(void *arg) { struct axp81x_softc *sc; device_t dev; uint8_t val; int error; dev = arg; sc = device_get_softc(dev); error = axp81x_read(dev, AXP_IRQSTAT5, &val, 1); if (error != 0) return; if (val != 0) { if ((val & AXP_IRQSTAT5_POKSIRQ) != 0) { if (bootverbose) device_printf(dev, "Power button pressed\n"); shutdown_nice(RB_POWEROFF); } /* Acknowledge */ axp81x_write(dev, AXP_IRQSTAT5, val); } } static device_t axp81x_gpio_get_bus(device_t dev) { struct axp81x_softc *sc; sc = device_get_softc(dev); return (sc->gpiodev); } static int axp81x_gpio_pin_max(device_t dev, int *maxpin) { *maxpin = nitems(axp81x_pins) - 1; return (0); } static int axp81x_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { if (pin >= nitems(axp81x_pins)) return (EINVAL); snprintf(name, GPIOMAXNAME, "%s", axp81x_pins[pin].name); return (0); } static int axp81x_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { if (pin >= nitems(axp81x_pins)) return (EINVAL); *caps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT; return (0); } static int axp81x_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct axp81x_softc *sc; uint8_t data, func; int error; if (pin >= nitems(axp81x_pins)) return (EINVAL); sc = device_get_softc(dev); AXP_LOCK(sc); error = axp81x_read(dev, axp81x_pins[pin].ctrl_reg, &data, 1); if (error == 0) { func = (data & AXP_GPIO_FUNC) >> AXP_GPIO_FUNC_SHIFT; if (func == AXP_GPIO_FUNC_INPUT) *flags = GPIO_PIN_INPUT; else if (func == AXP_GPIO_FUNC_DRVLO || func == AXP_GPIO_FUNC_DRVHI) *flags = GPIO_PIN_OUTPUT; else *flags = 0; } AXP_UNLOCK(sc); return (error); } static int axp81x_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct axp81x_softc *sc; uint8_t data; int error; if (pin >= nitems(axp81x_pins)) return (EINVAL); sc = device_get_softc(dev); AXP_LOCK(sc); error = axp81x_read(dev, axp81x_pins[pin].ctrl_reg, &data, 1); if (error == 0) { data &= ~AXP_GPIO_FUNC; if ((flags & (GPIO_PIN_INPUT|GPIO_PIN_OUTPUT)) != 0) { if ((flags & GPIO_PIN_OUTPUT) == 0) data |= AXP_GPIO_FUNC_INPUT; } error = axp81x_write(dev, axp81x_pins[pin].ctrl_reg, data); } AXP_UNLOCK(sc); return (error); } static int axp81x_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val) { struct axp81x_softc *sc; uint8_t data, func; int error; if (pin >= nitems(axp81x_pins)) return (EINVAL); sc = device_get_softc(dev); AXP_LOCK(sc); error = axp81x_read(dev, axp81x_pins[pin].ctrl_reg, &data, 1); if (error == 0) { func = (data & AXP_GPIO_FUNC) >> AXP_GPIO_FUNC_SHIFT; switch (func) { case AXP_GPIO_FUNC_DRVLO: *val = 0; break; case AXP_GPIO_FUNC_DRVHI: *val = 1; break; case AXP_GPIO_FUNC_INPUT: error = axp81x_read(dev, AXP_GPIO_SIGBIT, &data, 1); if (error == 0) *val = (data & (1 << pin)) ? 1 : 0; break; default: error = EIO; break; } } AXP_UNLOCK(sc); return (error); } static int axp81x_gpio_pin_set(device_t dev, uint32_t pin, unsigned int val) { struct axp81x_softc *sc; uint8_t data, func; int error; if (pin >= nitems(axp81x_pins)) return (EINVAL); sc = device_get_softc(dev); AXP_LOCK(sc); error = axp81x_read(dev, axp81x_pins[pin].ctrl_reg, &data, 1); if (error == 0) { func = (data & AXP_GPIO_FUNC) >> AXP_GPIO_FUNC_SHIFT; switch (func) { case AXP_GPIO_FUNC_DRVLO: case AXP_GPIO_FUNC_DRVHI: data &= ~AXP_GPIO_FUNC; data |= (val << AXP_GPIO_FUNC_SHIFT); break; default: error = EIO; break; } } if (error == 0) error = axp81x_write(dev, axp81x_pins[pin].ctrl_reg, data); AXP_UNLOCK(sc); return (error); } static int axp81x_gpio_pin_toggle(device_t dev, uint32_t pin) { struct axp81x_softc *sc; uint8_t data, func; int error; if (pin >= nitems(axp81x_pins)) return (EINVAL); sc = device_get_softc(dev); AXP_LOCK(sc); error = axp81x_read(dev, axp81x_pins[pin].ctrl_reg, &data, 1); if (error == 0) { func = (data & AXP_GPIO_FUNC) >> AXP_GPIO_FUNC_SHIFT; switch (func) { case AXP_GPIO_FUNC_DRVLO: data &= ~AXP_GPIO_FUNC; data |= (AXP_GPIO_FUNC_DRVHI << AXP_GPIO_FUNC_SHIFT); break; case AXP_GPIO_FUNC_DRVHI: data &= ~AXP_GPIO_FUNC; data |= (AXP_GPIO_FUNC_DRVLO << AXP_GPIO_FUNC_SHIFT); break; default: error = EIO; break; } } if (error == 0) error = axp81x_write(dev, axp81x_pins[pin].ctrl_reg, data); AXP_UNLOCK(sc); return (error); } static int axp81x_gpio_map_gpios(device_t bus, phandle_t dev, phandle_t gparent, int gcells, pcell_t *gpios, uint32_t *pin, uint32_t *flags) { if (gpios[0] >= nitems(axp81x_pins)) return (EINVAL); *pin = gpios[0]; *flags = gpios[1]; return (0); } static phandle_t axp81x_get_node(device_t dev, device_t bus) { return (ofw_bus_get_node(dev)); } static int axp81x_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "X-Powers AXP81x Power Management Unit"); return (BUS_PROBE_DEFAULT); } static int axp81x_attach(device_t dev) { struct axp81x_softc *sc; uint8_t chip_id; int error; sc = device_get_softc(dev); sc->addr = iicbus_get_addr(dev); mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF); error = bus_alloc_resources(dev, axp81x_spec, &sc->res); if (error != 0) { device_printf(dev, "cannot allocate resources for device\n"); return (error); } if (bootverbose) { axp81x_read(dev, AXP_ICTYPE, &chip_id, 1); device_printf(dev, "chip ID 0x%02x\n", chip_id); } /* Enable IRQ on short power key press */ axp81x_write(dev, AXP_IRQEN1, 0); axp81x_write(dev, AXP_IRQEN2, 0); axp81x_write(dev, AXP_IRQEN3, 0); axp81x_write(dev, AXP_IRQEN4, 0); axp81x_write(dev, AXP_IRQEN5, AXP_IRQEN5_POKSIRQ); axp81x_write(dev, AXP_IRQEN6, 0); /* Install interrupt handler */ error = bus_setup_intr(dev, sc->res, INTR_TYPE_MISC | INTR_MPSAFE, NULL, axp81x_intr, dev, &sc->ih); if (error != 0) { device_printf(dev, "cannot setup interrupt handler\n"); return (error); } EVENTHANDLER_REGISTER(shutdown_final, axp81x_shutdown, dev, SHUTDOWN_PRI_LAST); sc->gpiodev = gpiobus_attach_bus(dev); return (0); } static device_method_t axp81x_methods[] = { /* Device interface */ DEVMETHOD(device_probe, axp81x_probe), DEVMETHOD(device_attach, axp81x_attach), /* GPIO interface */ DEVMETHOD(gpio_get_bus, axp81x_gpio_get_bus), DEVMETHOD(gpio_pin_max, axp81x_gpio_pin_max), DEVMETHOD(gpio_pin_getname, axp81x_gpio_pin_getname), DEVMETHOD(gpio_pin_getcaps, axp81x_gpio_pin_getcaps), DEVMETHOD(gpio_pin_getflags, axp81x_gpio_pin_getflags), DEVMETHOD(gpio_pin_setflags, axp81x_gpio_pin_setflags), DEVMETHOD(gpio_pin_get, axp81x_gpio_pin_get), DEVMETHOD(gpio_pin_set, axp81x_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, axp81x_gpio_pin_toggle), DEVMETHOD(gpio_map_gpios, axp81x_gpio_map_gpios), /* OFW bus interface */ DEVMETHOD(ofw_bus_get_node, axp81x_get_node), DEVMETHOD_END }; static driver_t axp81x_driver = { "axp81x_pmu", axp81x_methods, sizeof(struct axp81x_softc), }; static devclass_t axp81x_devclass; extern devclass_t ofwgpiobus_devclass, gpioc_devclass; extern driver_t ofw_gpiobus_driver, gpioc_driver; DRIVER_MODULE(axp81x, iicbus, axp81x_driver, axp81x_devclass, 0, 0); DRIVER_MODULE(ofw_gpiobus, axp81x_pmu, ofw_gpiobus_driver, ofwgpiobus_devclass, 0, 0); DRIVER_MODULE(gpioc, axp81x_pmu, gpioc_driver, gpioc_devclass, 0, 0); MODULE_VERSION(axp81x, 1); MODULE_DEPEND(axp81x, iicbus, 1, 1, 1);