diff options
Diffstat (limited to 'sys/arm/nvidia/as3722_gpio.c')
-rw-r--r-- | sys/arm/nvidia/as3722_gpio.c | 577 |
1 files changed, 577 insertions, 0 deletions
diff --git a/sys/arm/nvidia/as3722_gpio.c b/sys/arm/nvidia/as3722_gpio.c new file mode 100644 index 0000000..8e53bce --- /dev/null +++ b/sys/arm/nvidia/as3722_gpio.c @@ -0,0 +1,577 @@ +/*- + * Copyright (c) 2016 Michal Meloun <mmel@FreeBSD.org> + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/gpio.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/sx.h> + +#include <machine/bus.h> + +#include <dev/fdt/fdt_common.h> +#include <dev/gpio/gpiobusvar.h> + +#include "as3722.h" + +MALLOC_DEFINE(M_AS3722_GPIO, "AS3722 gpio", "AS3722 GPIO"); + +/* AS3722_GPIOx_CONTROL MODE and IOSF definition. */ +#define AS3722_IOSF_GPIO 0x00 +#define AS3722_IOSF_INTERRUPT_OUT 0x01 +#define AS3722_IOSF_VSUP_VBAT_LOW_UNDEBOUNCE_OUT 0x02 +#define AS3722_IOSF_GPIO_IN_INTERRUPT 0x03 +#define AS3722_IOSF_PWM_IN 0x04 +#define AS3722_IOSF_VOLTAGE_IN_STANDBY 0x05 +#define AS3722_IOSF_OC_PG_SD0 0x06 +#define AS3722_IOSF_POWERGOOD_OUT 0x07 +#define AS3722_IOSF_CLK32K_OUT 0x08 +#define AS3722_IOSF_WATCHDOG_IN 0x09 +#define AS3722_IOSF_SOFT_RESET_IN 0x0b +#define AS3722_IOSF_PWM_OUT 0x0c +#define AS3722_IOSF_VSUP_VBAT_LOW_DEBOUNCE_OUT 0x0d +#define AS3722_IOSF_OC_PG_SD6 0x0e + +#define AS3722_MODE_INPUT 0 +#define AS3722_MODE_PUSH_PULL 1 +#define AS3722_MODE_OPEN_DRAIN 2 +#define AS3722_MODE_TRISTATE 3 +#define AS3722_MODE_INPUT_PULL_UP_LV 4 +#define AS3722_MODE_INPUT_PULL_DOWN 5 +#define AS3722_MODE_OPEN_DRAIN_LV 6 +#define AS3722_MODE_PUSH_PULL_LV 7 + +#define NGPIO 8 + +#define GPIO_LOCK(_sc) sx_slock(&(_sc)->gpio_lock) +#define GPIO_UNLOCK(_sc) sx_unlock(&(_sc)->gpio_lock) +#define GPIO_ASSERT(_sc) sx_assert(&(_sc)->gpio_lock, SA_LOCKED) + +#define AS3722_CFG_BIAS_DISABLE 0x0001 +#define AS3722_CFG_BIAS_PULL_UP 0x0002 +#define AS3722_CFG_BIAS_PULL_DOWN 0x0004 +#define AS3722_CFG_BIAS_HIGH_IMPEDANCE 0x0008 +#define AS3722_CFG_OPEN_DRAIN 0x0010 + +static const struct { + const char *name; + int config; /* AS3722_CFG_ */ +} as3722_cfg_names[] = { + {"bias-disable", AS3722_CFG_BIAS_DISABLE}, + {"bias-pull-up", AS3722_CFG_BIAS_PULL_UP}, + {"bias-pull-down", AS3722_CFG_BIAS_PULL_DOWN}, + {"bias-high-impedance", AS3722_CFG_BIAS_HIGH_IMPEDANCE}, + {"drive-open-drain", AS3722_CFG_OPEN_DRAIN}, +}; + +static struct { + const char *name; + int fnc_val; +} as3722_fnc_table[] = { + {"gpio", AS3722_IOSF_GPIO}, + {"interrupt-out", AS3722_IOSF_INTERRUPT_OUT}, + {"vsup-vbat-low-undebounce-out", AS3722_IOSF_VSUP_VBAT_LOW_UNDEBOUNCE_OUT}, + {"gpio-in-interrupt", AS3722_IOSF_GPIO_IN_INTERRUPT}, + {"pwm-in", AS3722_IOSF_PWM_IN}, + {"voltage-in-standby", AS3722_IOSF_VOLTAGE_IN_STANDBY}, + {"oc-pg-sd0", AS3722_IOSF_OC_PG_SD0}, + {"powergood-out", AS3722_IOSF_POWERGOOD_OUT}, + {"clk32k-out", AS3722_IOSF_CLK32K_OUT}, + {"watchdog-in", AS3722_IOSF_WATCHDOG_IN}, + {"soft-reset-in", AS3722_IOSF_SOFT_RESET_IN}, + {"pwm-out", AS3722_IOSF_PWM_OUT}, + {"vsup-vbat-low-debounce-out", AS3722_IOSF_VSUP_VBAT_LOW_DEBOUNCE_OUT}, + {"oc-pg-sd6", AS3722_IOSF_OC_PG_SD6}, +}; + +struct as3722_pincfg { + char *function; + int flags; +}; + +struct as3722_gpio_pin { + int pin_caps; + uint8_t pin_ctrl_reg; + char pin_name[GPIOMAXNAME]; + int pin_cfg_flags; +}; + + +/* -------------------------------------------------------------------------- + * + * Pinmux functions. + */ +static int +as3722_pinmux_get_function(struct as3722_softc *sc, char *name) +{ + int i; + + for (i = 0; i < nitems(as3722_fnc_table); i++) { + if (strcmp(as3722_fnc_table[i].name, name) == 0) + return (as3722_fnc_table[i].fnc_val); + } + return (-1); +} + + + +static int +as3722_pinmux_config_node(struct as3722_softc *sc, char *pin_name, + struct as3722_pincfg *cfg) +{ + uint8_t ctrl; + int rv, fnc, pin; + + for (pin = 0; pin < sc->gpio_npins; pin++) { + if (strcmp(sc->gpio_pins[pin]->pin_name, pin_name) == 0) + break; + } + if (pin >= sc->gpio_npins) { + device_printf(sc->dev, "Unknown pin: %s\n", pin_name); + return (ENXIO); + } + + ctrl = sc->gpio_pins[pin]->pin_ctrl_reg; + sc->gpio_pins[pin]->pin_cfg_flags = cfg->flags; + if (cfg->function != NULL) { + fnc = as3722_pinmux_get_function(sc, cfg->function); + if (fnc == -1) { + device_printf(sc->dev, + "Unknown function %s for pin %s\n", cfg->function, + sc->gpio_pins[pin]->pin_name); + return (ENXIO); + } + switch (fnc) { + case AS3722_IOSF_INTERRUPT_OUT: + case AS3722_IOSF_VSUP_VBAT_LOW_UNDEBOUNCE_OUT: + case AS3722_IOSF_OC_PG_SD0: + case AS3722_IOSF_POWERGOOD_OUT: + case AS3722_IOSF_CLK32K_OUT: + case AS3722_IOSF_PWM_OUT: + case AS3722_IOSF_OC_PG_SD6: + ctrl &= ~(AS3722_GPIO_MODE_MASK << + AS3722_GPIO_MODE_SHIFT); + ctrl |= AS3722_MODE_PUSH_PULL << AS3722_GPIO_MODE_SHIFT; + /* XXX Handle flags (OC + pullup) */ + break; + case AS3722_IOSF_GPIO_IN_INTERRUPT: + case AS3722_IOSF_PWM_IN: + case AS3722_IOSF_VOLTAGE_IN_STANDBY: + case AS3722_IOSF_WATCHDOG_IN: + case AS3722_IOSF_SOFT_RESET_IN: + ctrl &= ~(AS3722_GPIO_MODE_MASK << + AS3722_GPIO_MODE_SHIFT); + ctrl |= AS3722_MODE_INPUT << AS3722_GPIO_MODE_SHIFT; + /* XXX Handle flags (pulldown + pullup) */ + + default: + break; + } + ctrl &= ~(AS3722_GPIO_IOSF_MASK << AS3722_GPIO_IOSF_SHIFT); + ctrl |= fnc << AS3722_GPIO_IOSF_SHIFT; + } + rv = 0; + if (ctrl != sc->gpio_pins[pin]->pin_ctrl_reg) { + rv = WR1(sc, AS3722_GPIO0_CONTROL + pin, ctrl); + sc->gpio_pins[pin]->pin_ctrl_reg = ctrl; + } + return (rv); +} + +static int +as3722_pinmux_read_node(struct as3722_softc *sc, phandle_t node, + struct as3722_pincfg *cfg, char **pins, int *lpins) +{ + int rv, i; + + *lpins = OF_getprop_alloc(node, "pins", 1, (void **)pins); + if (*lpins <= 0) + return (ENOENT); + + /* Read function (mux) settings. */ + rv = OF_getprop_alloc(node, "function", 1, (void **)&cfg->function); + if (rv <= 0) + cfg->function = NULL; + + /* Read boolean properties. */ + for (i = 0; i < nitems(as3722_cfg_names); i++) { + if (OF_hasprop(node, as3722_cfg_names[i].name)) + cfg->flags |= as3722_cfg_names[i].config; + } + return (0); +} + +static int +as3722_pinmux_process_node(struct as3722_softc *sc, phandle_t node) +{ + struct as3722_pincfg cfg; + char *pins, *pname; + int i, len, lpins, rv; + + rv = as3722_pinmux_read_node(sc, node, &cfg, &pins, &lpins); + if (rv != 0) + return (rv); + + len = 0; + pname = pins; + do { + i = strlen(pname) + 1; + rv = as3722_pinmux_config_node(sc, pname, &cfg); + if (rv != 0) { + device_printf(sc->dev, + "Cannot configure pin: %s: %d\n", pname, rv); + } + len += i; + pname += i; + } while (len < lpins); + + if (pins != NULL) + free(pins, M_OFWPROP); + if (cfg.function != NULL) + free(cfg.function, M_OFWPROP); + + return (rv); +} + +int as3722_pinmux_configure(device_t dev, phandle_t cfgxref) +{ + struct as3722_softc *sc; + phandle_t node, cfgnode; + int rv; + + sc = device_get_softc(dev); + cfgnode = OF_node_from_xref(cfgxref); + + for (node = OF_child(cfgnode); node != 0; node = OF_peer(node)) { + if (!fdt_is_enabled(node)) + continue; + rv = as3722_pinmux_process_node(sc, node); + if (rv != 0) + device_printf(dev, "Failed to process pinmux"); + + } + return (0); +} + +/* -------------------------------------------------------------------------- + * + * GPIO + */ +device_t +as3722_gpio_get_bus(device_t dev) +{ + struct as3722_softc *sc; + + sc = device_get_softc(dev); + return (sc->gpio_busdev); +} + +int +as3722_gpio_pin_max(device_t dev, int *maxpin) +{ + + *maxpin = NGPIO - 1; + return (0); +} + +int +as3722_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) +{ + struct as3722_softc *sc; + + sc = device_get_softc(dev); + if (pin >= sc->gpio_npins) + return (EINVAL); + GPIO_LOCK(sc); + *caps = sc->gpio_pins[pin]->pin_caps; + GPIO_UNLOCK(sc); + return (0); +} + +int +as3722_gpio_pin_getname(device_t dev, uint32_t pin, char *name) +{ + struct as3722_softc *sc; + + sc = device_get_softc(dev); + if (pin >= sc->gpio_npins) + return (EINVAL); + GPIO_LOCK(sc); + memcpy(name, sc->gpio_pins[pin]->pin_name, GPIOMAXNAME); + GPIO_UNLOCK(sc); + return (0); +} + +int +as3722_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *out_flags) +{ + struct as3722_softc *sc; + uint8_t tmp, mode, iosf; + uint32_t flags; + bool inverted; + + sc = device_get_softc(dev); + if (pin >= sc->gpio_npins) + return (EINVAL); + + GPIO_LOCK(sc); + tmp = sc->gpio_pins[pin]->pin_ctrl_reg; + GPIO_UNLOCK(sc); + iosf = (tmp >> AS3722_GPIO_IOSF_SHIFT) & AS3722_GPIO_IOSF_MASK; + mode = (tmp >> AS3722_GPIO_MODE_SHIFT) & AS3722_GPIO_MODE_MASK; + inverted = (tmp & AS3722_GPIO_INVERT) != 0; + /* Is pin in GPIO mode ? */ + if (iosf != AS3722_IOSF_GPIO) + return (ENXIO); + + flags = 0; + switch (mode) { + case AS3722_MODE_INPUT: + flags = GPIO_PIN_INPUT; + break; + case AS3722_MODE_PUSH_PULL: + case AS3722_MODE_PUSH_PULL_LV: + flags = GPIO_PIN_OUTPUT; + break; + case AS3722_MODE_OPEN_DRAIN: + case AS3722_MODE_OPEN_DRAIN_LV: + flags = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | GPIO_PIN_OPENDRAIN; + break; + case AS3722_MODE_TRISTATE: + flags = GPIO_PIN_TRISTATE; + break; + case AS3722_MODE_INPUT_PULL_UP_LV: + flags = GPIO_PIN_INPUT | GPIO_PIN_PULLUP; + break; + + case AS3722_MODE_INPUT_PULL_DOWN: + flags = GPIO_PIN_OUTPUT | GPIO_PIN_PULLDOWN; + break; + } + if (inverted) + flags |= GPIO_PIN_INVIN | GPIO_PIN_INVOUT; + *out_flags = flags; + return (0); +} + +static int +as3722_gpio_get_mode(struct as3722_softc *sc, uint32_t pin, uint32_t gpio_flags) +{ + uint8_t ctrl; + int flags; + + ctrl = sc->gpio_pins[pin]->pin_ctrl_reg; + flags = sc->gpio_pins[pin]->pin_cfg_flags; + + /* Tristate mode. */ + if (flags & AS3722_CFG_BIAS_HIGH_IMPEDANCE || + gpio_flags & GPIO_PIN_TRISTATE) + return (AS3722_MODE_TRISTATE); + + /* Open drain modes. */ + if (flags & AS3722_CFG_OPEN_DRAIN || gpio_flags & GPIO_PIN_OPENDRAIN) { + /* Only pull up have effect */ + if (flags & AS3722_CFG_BIAS_PULL_UP || + gpio_flags & GPIO_PIN_PULLUP) + return (AS3722_MODE_OPEN_DRAIN_LV); + return (AS3722_MODE_OPEN_DRAIN); + } + /* Input modes. */ + if (gpio_flags & GPIO_PIN_INPUT) { + /* Accept pull up or pull down. */ + if (flags & AS3722_CFG_BIAS_PULL_UP || + gpio_flags & GPIO_PIN_PULLUP) + return (AS3722_MODE_INPUT_PULL_UP_LV); + + if (flags & AS3722_CFG_BIAS_PULL_DOWN || + gpio_flags & GPIO_PIN_PULLDOWN) + return (AS3722_MODE_INPUT_PULL_DOWN); + return (AS3722_MODE_INPUT); + } + /* + * Output modes. + * Pull down is used as indicator of low voltage output. + */ + if (flags & AS3722_CFG_BIAS_PULL_DOWN || + gpio_flags & GPIO_PIN_PULLDOWN) + return (AS3722_MODE_PUSH_PULL_LV); + return (AS3722_MODE_PUSH_PULL); +} + +int +as3722_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) +{ + struct as3722_softc *sc; + uint8_t ctrl, mode, iosf; + int rv; + + sc = device_get_softc(dev); + if (pin >= sc->gpio_npins) + return (EINVAL); + + GPIO_LOCK(sc); + ctrl = sc->gpio_pins[pin]->pin_ctrl_reg; + iosf = (ctrl >> AS3722_GPIO_IOSF_SHIFT) & AS3722_GPIO_IOSF_MASK; + /* Is pin in GPIO mode ? */ + if (iosf != AS3722_IOSF_GPIO) { + GPIO_UNLOCK(sc); + return (ENXIO); + } + mode = as3722_gpio_get_mode(sc, pin, flags); + ctrl &= ~(AS3722_GPIO_MODE_MASK << AS3722_GPIO_MODE_SHIFT); + ctrl |= AS3722_MODE_PUSH_PULL << AS3722_GPIO_MODE_SHIFT; + rv = 0; + if (ctrl != sc->gpio_pins[pin]->pin_ctrl_reg) { + rv = WR1(sc, AS3722_GPIO0_CONTROL + pin, ctrl); + sc->gpio_pins[pin]->pin_ctrl_reg = ctrl; + } + GPIO_UNLOCK(sc); + return (rv); +} + +int +as3722_gpio_pin_set(device_t dev, uint32_t pin, uint32_t val) +{ + struct as3722_softc *sc; + uint8_t tmp; + int rv; + + sc = device_get_softc(dev); + if (pin >= sc->gpio_npins) + return (EINVAL); + + tmp = (val != 0) ? 1 : 0; + if (sc->gpio_pins[pin]->pin_ctrl_reg & AS3722_GPIO_INVERT) + tmp ^= 1; + + GPIO_LOCK(sc); + rv = RM1(sc, AS3722_GPIO_SIGNAL_OUT, (1 << pin), (tmp << pin)); + GPIO_UNLOCK(sc); + return (rv); +} + +int +as3722_gpio_pin_get(device_t dev, uint32_t pin, uint32_t *val) +{ + struct as3722_softc *sc; + uint8_t tmp, mode, ctrl; + int rv; + + sc = device_get_softc(dev); + if (pin >= sc->gpio_npins) + return (EINVAL); + + GPIO_LOCK(sc); + ctrl = sc->gpio_pins[pin]->pin_ctrl_reg; + mode = (ctrl >> AS3722_GPIO_MODE_SHIFT) & AS3722_GPIO_MODE_MASK; + if ((mode == AS3722_MODE_PUSH_PULL) || + (mode == AS3722_MODE_PUSH_PULL_LV)) + rv = RD1(sc, AS3722_GPIO_SIGNAL_OUT, &tmp); + else + rv = RD1(sc, AS3722_GPIO_SIGNAL_IN, &tmp); + GPIO_UNLOCK(sc); + if (rv != 0) + return (rv); + + *val = tmp & (1 << pin) ? 1 : 0; + if (ctrl & AS3722_GPIO_INVERT) + *val ^= 1; + return (0); +} + +int +as3722_gpio_pin_toggle(device_t dev, uint32_t pin) +{ + struct as3722_softc *sc; + uint8_t tmp; + int rv; + + sc = device_get_softc(dev); + if (pin >= sc->gpio_npins) + return (EINVAL); + + GPIO_LOCK(sc); + rv = RD1(sc, AS3722_GPIO_SIGNAL_OUT, &tmp); + if (rv != 0) { + GPIO_UNLOCK(sc); + return (rv); + } + tmp ^= (1 <<pin); + rv = RM1(sc, AS3722_GPIO_SIGNAL_OUT, (1 << pin), tmp); + GPIO_UNLOCK(sc); + return (0); +} + +int +as3722_gpio_map_gpios(device_t dev, phandle_t pdev, phandle_t gparent, + int gcells, pcell_t *gpios, uint32_t *pin, uint32_t *flags) +{ + + if (gcells != 2) + return (ERANGE); + *pin = gpios[0]; + *flags= gpios[1]; + return (0); +} + +int +as3722_gpio_attach(struct as3722_softc *sc, phandle_t node) +{ + struct as3722_gpio_pin *pin; + int i, rv; + + sx_init(&sc->gpio_lock, "AS3722 GPIO lock"); + sc->gpio_npins = NGPIO; + sc->gpio_pins = malloc(sizeof(struct as3722_gpio_pin *) * + sc->gpio_npins, M_AS3722_GPIO, M_WAITOK | M_ZERO); + + + sc->gpio_busdev = gpiobus_attach_bus(sc->dev); + if (sc->gpio_busdev == NULL) + return (ENXIO); + for (i = 0; i < sc->gpio_npins; i++) { + sc->gpio_pins[i] = malloc(sizeof(struct as3722_gpio_pin), + M_AS3722_GPIO, M_WAITOK | M_ZERO); + pin = sc->gpio_pins[i]; + sprintf(pin->pin_name, "gpio%d", i); + pin->pin_caps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | + GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL | GPIO_PIN_TRISTATE | + GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN | GPIO_PIN_INVIN | + GPIO_PIN_INVOUT; + rv = RD1(sc, AS3722_GPIO0_CONTROL + i, &pin->pin_ctrl_reg); + if (rv != 0) { + device_printf(sc->dev, + "Cannot read configuration for pin %s\n", + sc->gpio_pins[i]->pin_name); + } + } + return (0); +} |