Index: head/sys/arm/conf/GENERIC =================================================================== --- head/sys/arm/conf/GENERIC +++ head/sys/arm/conf/GENERIC @@ -112,6 +112,7 @@ # GPIO device gpio device gpioled +device gpioregulator # SPI device spibus Index: head/sys/conf/files =================================================================== --- head/sys/conf/files +++ head/sys/conf/files @@ -1573,6 +1573,7 @@ dependency "gpio_if.h" dev/gpio/gpioiic.c optional gpioiic dev/gpio/gpioled.c optional gpioled +dev/gpio/gpioregulator.c optional gpioregulator fdt ext_resources dev/gpio/gpiospi.c optional gpiospi dev/gpio/gpio_if.m optional gpio dev/gpio/gpiobus_if.m optional gpio Index: head/sys/dev/gpio/gpioregulator.c =================================================================== --- head/sys/dev/gpio/gpioregulator.c +++ head/sys/dev/gpio/gpioregulator.c @@ -0,0 +1,348 @@ +/*- + * 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$ + */ + +/* + * GPIO controlled regulators + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include "regdev_if.h" + +struct gpioregulator_state { + int val; + uint32_t mask; +}; + +struct gpioregulator_init_def { + struct regnode_init_def reg_init_def; + struct gpiobus_pin *enable_pin; + int enable_pin_valid; + int startup_delay_us; + int nstates; + struct gpioregulator_state *states; + int npins; + struct gpiobus_pin **pins; +}; + +struct gpioregulator_reg_sc { + struct regnode *regnode; + device_t base_dev; + struct regnode_std_param *param; + struct gpioregulator_init_def *def; +}; + +struct gpioregulator_softc { + device_t dev; + struct gpioregulator_reg_sc *reg_sc; + struct gpioregulator_init_def init_def; +}; + +static int +gpioregulator_regnode_init(struct regnode *regnode) +{ + struct gpioregulator_reg_sc *sc; + int error, n; + + sc = regnode_get_softc(regnode); + + if (sc->def->enable_pin_valid == 1) { + error = gpio_pin_setflags(sc->def->enable_pin, GPIO_PIN_OUTPUT); + if (error != 0) + return (error); + } + + for (n = 0; n < sc->def->npins; n++) { + error = gpio_pin_setflags(sc->def->pins[n], GPIO_PIN_OUTPUT); + if (error != 0) + return (error); + } + + return (0); +} + +static int +gpioregulator_regnode_enable(struct regnode *regnode, bool enable, int *udelay) +{ + struct gpioregulator_reg_sc *sc; + bool active; + int error; + + sc = regnode_get_softc(regnode); + + if (sc->def->enable_pin_valid == 1) { + active = enable; + if (!sc->param->enable_active_high) + active = !active; + error = gpio_pin_set_active(sc->def->enable_pin, active); + if (error != 0) + return (error); + } + + *udelay = sc->def->startup_delay_us; + + return (0); +} + +static int +gpioregulator_regnode_set_voltage(struct regnode *regnode, int min_uvolt, + int max_uvolt, int *udelay) +{ + struct gpioregulator_reg_sc *sc; + const struct gpioregulator_state *state; + int error, n; + + sc = regnode_get_softc(regnode); + state = NULL; + + for (n = 0; n < sc->def->nstates; n++) { + if (sc->def->states[n].val >= min_uvolt && + sc->def->states[n].val <= max_uvolt) { + state = &sc->def->states[n]; + break; + } + } + if (state == NULL) + return (EINVAL); + + for (n = 0; n < sc->def->npins; n++) { + error = gpio_pin_set_active(sc->def->pins[n], + (state->mask >> n) & 1); + if (error != 0) + return (error); + } + + *udelay = sc->def->startup_delay_us; + + return (0); +} + +static int +gpioregulator_regnode_get_voltage(struct regnode *regnode, int *uvolt) +{ + struct gpioregulator_reg_sc *sc; + uint32_t mask; + int error, n; + bool active; + + sc = regnode_get_softc(regnode); + mask = 0; + + for (n = 0; n < sc->def->npins; n++) { + error = gpio_pin_is_active(sc->def->pins[n], &active); + if (error != 0) + return (error); + mask |= (active << n); + } + + for (n = 0; n < sc->def->nstates; n++) { + if (sc->def->states[n].mask == mask) { + *uvolt = sc->def->states[n].val; + return (0); + } + } + + return (EIO); +} + +static regnode_method_t gpioregulator_regnode_methods[] = { + /* Regulator interface */ + REGNODEMETHOD(regnode_init, gpioregulator_regnode_init), + REGNODEMETHOD(regnode_enable, gpioregulator_regnode_enable), + REGNODEMETHOD(regnode_set_voltage, gpioregulator_regnode_set_voltage), + REGNODEMETHOD(regnode_get_voltage, gpioregulator_regnode_get_voltage), + REGNODEMETHOD_END +}; +DEFINE_CLASS_1(gpioregulator_regnode, gpioregulator_regnode_class, + gpioregulator_regnode_methods, sizeof(struct gpioregulator_reg_sc), + regnode_class); + +static int +gpioregulator_parse_fdt(struct gpioregulator_softc *sc) +{ + uint32_t *pstates, mask; + phandle_t node; + ssize_t len; + int error, n; + + node = ofw_bus_get_node(sc->dev); + pstates = NULL; + mask = 0; + + error = regulator_parse_ofw_stdparam(sc->dev, node, + &sc->init_def.reg_init_def); + if (error != 0) + return (error); + + /* "states" property (required) */ + len = OF_getencprop_alloc(node, "states", sizeof(*pstates), + (void **)&pstates); + if (len < 2) { + device_printf(sc->dev, "invalid 'states' property\n"); + error = EINVAL; + goto done; + } + sc->init_def.nstates = len / 2; + sc->init_def.states = malloc(sc->init_def.nstates * + sizeof(*sc->init_def.states), M_DEVBUF, M_WAITOK); + for (n = 0; n < sc->init_def.nstates; n++) { + sc->init_def.states[n].val = pstates[n * 2 + 0]; + sc->init_def.states[n].mask = pstates[n * 2 + 1]; + mask |= sc->init_def.states[n].mask; + } + + /* "startup-delay-us" property (optional) */ + len = OF_getencprop(node, "startup-delay-us", + &sc->init_def.startup_delay_us, + sizeof(sc->init_def.startup_delay_us)); + if (len <= 0) + sc->init_def.startup_delay_us = 0; + + /* "enable-gpio" property (optional) */ + error = gpio_pin_get_by_ofw_property(sc->dev, node, "enable-gpio", + &sc->init_def.enable_pin); + if (error == 0) + sc->init_def.enable_pin_valid = 1; + + /* "gpios" property */ + sc->init_def.npins = 32 - __builtin_clz(mask); + sc->init_def.pins = malloc(sc->init_def.npins * + sizeof(sc->init_def.pins), M_DEVBUF, M_WAITOK); + for (n = 0; n < sc->init_def.npins; n++) { + error = gpio_pin_get_by_ofw_idx(sc->dev, node, n, + &sc->init_def.pins[n]); + if (error != 0) { + device_printf(sc->dev, "cannot get pin %d\n", n); + goto done; + } + } + +done: + if (error != 0) { + for (n = 0; n < sc->init_def.npins; n++) { + if (sc->init_def.pins[n] != NULL) + gpio_pin_release(sc->init_def.pins[n]); + } + + free(sc->init_def.states, M_DEVBUF); + free(sc->init_def.pins, M_DEVBUF); + + } + OF_prop_free(pstates); + + return (error); +} + +static int +gpioregulator_probe(device_t dev) +{ + + if (!ofw_bus_is_compatible(dev, "regulator-gpio")) + return (ENXIO); + + device_set_desc(dev, "GPIO controlled regulator"); + return (BUS_PROBE_GENERIC); +} + +static int +gpioregulator_attach(device_t dev) +{ + struct gpioregulator_softc *sc; + struct regnode *regnode; + phandle_t node; + int error; + + sc = device_get_softc(dev); + sc->dev = dev; + node = ofw_bus_get_node(dev); + + error = gpioregulator_parse_fdt(sc); + if (error != 0) { + device_printf(dev, "cannot parse parameters\n"); + return (ENXIO); + } + sc->init_def.reg_init_def.id = 1; + sc->init_def.reg_init_def.ofw_node = node; + + regnode = regnode_create(dev, &gpioregulator_regnode_class, + &sc->init_def.reg_init_def); + if (regnode == NULL) { + device_printf(dev, "cannot create regulator\n"); + return (ENXIO); + } + + sc->reg_sc = regnode_get_softc(regnode); + sc->reg_sc->regnode = regnode; + sc->reg_sc->base_dev = dev; + sc->reg_sc->param = regnode_get_stdparam(regnode); + sc->reg_sc->def = &sc->init_def; + + regnode_register(regnode); + + return (0); +} + + +static device_method_t gpioregulator_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, gpioregulator_probe), + DEVMETHOD(device_attach, gpioregulator_attach), + + /* Regdev interface */ + DEVMETHOD(regdev_map, regdev_default_ofw_map), + + DEVMETHOD_END +}; + +static driver_t gpioregulator_driver = { + "gpioregulator", + gpioregulator_methods, + sizeof(struct gpioregulator_softc), +}; + +static devclass_t gpioregulator_devclass; + +EARLY_DRIVER_MODULE(gpioregulator, simplebus, gpioregulator_driver, + gpioregulator_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LAST); +MODULE_VERSION(gpioregulator, 1);